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