t52-t45-baseline-check.mjs 4.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. /**
  2. * 票 52 收口自查:t45 的 4 条 C3x 失败是否为「既有」。
  3. *
  4. * t45 的 C3b/C3c/C3d/C3f 是对 `markerOverlay.js` 的**静态源码断言**,而该文件在本
  5. * 工作树是并行在制品(脏)。本票只改后端仓库 7 个 Java 文件,从未碰前端 src。
  6. * 这里把 HEAD 版本的 markerOverlay.js 取出来,逐条复跑同一批断言:
  7. * 若 HEAD 版本同样失败 → 既有失败,与本票零交集。
  8. *
  9. * 用法:node .scratch/cockpit-real-data-wayfinding/verify/t52-t45-baseline-check.mjs
  10. */
  11. import { execFileSync } from 'node:child_process'
  12. import { readFileSync, writeFileSync, mkdtempSync } from 'node:fs'
  13. import { tmpdir } from 'node:os'
  14. import { join } from 'node:path'
  15. const REPO = 'E:/City-Lifeline-Cockpit-TvT'
  16. const OVERLAY_REL = 'src/modules/cockpit/services/markerOverlay.js'
  17. const T45_REL = '.scratch/cockpit-real-data-wayfinding/verify/t45-state-axis-and-effects.mjs'
  18. // 1. HEAD 版 markerOverlay.js
  19. const headOverlay = execFileSync('git', ['show', `HEAD:${OVERLAY_REL}`],
  20. { cwd: REPO, encoding: 'utf8' })
  21. // 2. 在仓库内建一个临时副本,脚本相对路径保持可用(放同目录层级)
  22. const dir = mkdtempSync(join(REPO, '.t52-tmp-'))
  23. const overlayCopy = join(dir, 'markerOverlay.HEAD.js')
  24. writeFileSync(overlayCopy, headOverlay)
  25. // 3. 取 t45 脚本,把读 markerOverlay 的那一行指向 HEAD 副本;其余读取保持原样
  26. const t45 = readFileSync(join(REPO, T45_REL), 'utf8')
  27. const ANCHOR = "read('src/modules/cockpit/services/markerOverlay.js')"
  28. if (!t45.includes(ANCHOR)) {
  29. console.log('锚点未命中:t45 脚本已变,需同步本自查脚本')
  30. process.exit(1)
  31. }
  32. // 脚本用 new URL(`../../../${rel}`) 基于脚本位置解析;临时脚本仍放回原目录以便相对导入
  33. const patched = t45.replace(ANCHOR, `read('.t52-tmp-${dir.split('.t52-tmp-')[1]}/markerOverlay.HEAD.js')`)
  34. const patchedPath = join(REPO, '.scratch/cockpit-real-data-wayfinding/verify/.t52-t45-baseline.mjs')
  35. writeFileSync(patchedPath, patched)
  36. let out = ''
  37. try {
  38. out = execFileSync('node', [patchedPath], { cwd: REPO, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] })
  39. } catch (error) {
  40. out = `${error.stdout ?? ''}${error.stderr ?? ''}`
  41. }
  42. const fails = out.split('\n').filter((line) => line.startsWith('FAIL C3'))
  43. const headFailed = new Set(fails.map((line) => line.split(' ')[1]))
  44. const summary = out.split('\n').find((line) => line.includes('状态双轴与特效检查')) ?? '(无汇总行)'
  45. console.log(`HEAD 版 markerOverlay.js 上,C3x 静态断言失败 ${fails.length} 条:`)
  46. fails.forEach((line) => console.log(` ${line}`))
  47. console.log(`汇总:${summary}`)
  48. // 再跑一次**当前工作树**,取它失败的那批
  49. let worktreeOut = ''
  50. try {
  51. worktreeOut = execFileSync('node', [T45_REL], { cwd: REPO, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] })
  52. } catch (error) {
  53. worktreeOut = `${error.stdout ?? ''}${error.stderr ?? ''}`
  54. }
  55. const wtFails = new Set(worktreeOut.split('\n')
  56. .filter((line) => line.startsWith('FAIL C3'))
  57. .map((line) => line.split(' ')[1]))
  58. const wtSummary = worktreeOut.split('\n').find((line) => line.includes('状态双轴与特效检查')) ?? '(无汇总行)'
  59. console.log(`\n当前工作树(并行在制品)失败 ${wtFails.size} 条:${[...wtFails].join(', ') || '(无)'}`)
  60. console.log(`汇总:${wtSummary}`)
  61. // 清理
  62. execFileSync('node', ['-e', `require('fs').rmSync(${JSON.stringify(dir)},{recursive:true,force:true});require('fs').rmSync(${JSON.stringify(patchedPath)},{force:true})`])
  63. console.log('\n已清理临时文件。')
  64. // 判据:工作树失败集必须是 HEAD 失败集的**子集**(本票没引入任何新失败)。
  65. // 注意工作树是并行在制品,逐条数可以比 HEAD 少(正在修),但不能多出 HEAD 没有的。
  66. const newFails = [...wtFails].filter((id) => !headFailed.has(id))
  67. if (newFails.length === 0) {
  68. console.log(`判定:工作树 ${wtFails.size} 条失败 ⊂ HEAD ${headFailed.size} 条失败 → 全部既有;`
  69. + '票 52 是纯后端改动(本会话未碰前端 src),未引入任何新前端失败。')
  70. process.exit(0)
  71. }
  72. console.log(`注意:工作树出现 HEAD 上没有的新失败 ${newFails.join(', ')} → 需人工复核。`)
  73. process.exit(1)