150 lines
5.4 KiB
JavaScript
150 lines
5.4 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
/**
|
|||
|
|
* review-report-checker 封印框升级补丁 v2 (2026-04-25)
|
|||
|
|
*
|
|||
|
|
* 迁移: v1 章节封印 → v2 封印框 (方案 A)
|
|||
|
|
*
|
|||
|
|
* 语义变化:
|
|||
|
|
* - "章节封印" → "封印框"
|
|||
|
|
* - 模板 M: BOOKWORM REVIEW SEAL (╱╲ 风) → BOOKWORM · CODE REVIEW (╔╗ 风)
|
|||
|
|
* - 模板 L: SAFETY SEAL → CODE REVIEW · SAFETY 分节
|
|||
|
|
* - 模板 XL: SEMANTIC DIFF 追加节 (锚定句保留在交付条幅)
|
|||
|
|
*
|
|||
|
|
* 幂等: sentinel "sealed-frame:v2" 注入, 已打过则跳过
|
|||
|
|
* 兼容: 支持从 v1 (章节封印) 平滑迁移, 也支持原始未升级文件
|
|||
|
|
* 原子: tmp + rename
|
|||
|
|
* 备份: scripts/patches/bak/review-report-checker.js.sf.<ts>.bak
|
|||
|
|
*/
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
const TARGET = path.join(__dirname, '..', '..', 'hooks', 'review-report-checker.js');
|
|||
|
|
const V1_SENTINEL = '/* patch-review-chaptered-seal:v1 */';
|
|||
|
|
const V2_SENTINEL = '/* patch-review-sealed-frame:v2 */';
|
|||
|
|
|
|||
|
|
// v2 目标状态(最终态)
|
|||
|
|
const V2_STRINGS = {
|
|||
|
|
verdict: "const required = ['审查裁决 │ PASS / BLOCKED (宪章 §2.1 · 封印框)'];",
|
|||
|
|
standard: "required.push('封印框 (模板 M): 规范/安全/质量/架构 4 维度 + BOOKWORM · CODE REVIEW ╔╗ 框');",
|
|||
|
|
redTeam: "required.push('封印框 (模板 L): CODE REVIEW + 红队 5 问分节 (§11.3)');",
|
|||
|
|
semanticDiff: "required.push('封印框 (模板 XL): SEMANTIC DIFF 附节 (§12.1)');",
|
|||
|
|
header: "const header = '[review-required] ' + path.basename(filePath)\n + ' 修改 ' + lineCount + ' 行'\n + (sensitive ? ' (安全敏感)' : '')\n + ' — 回复末尾必须以【封印框】格式附:';",
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 锚点: 可能来自 v0 (原始) 或 v1 (章节封印), 都迁移到 v2
|
|||
|
|
const MIGRATIONS = [
|
|||
|
|
{
|
|||
|
|
label: 'verdict-line',
|
|||
|
|
target: V2_STRINGS.verdict,
|
|||
|
|
anchors: [
|
|||
|
|
"const required = ['审查: PASS / BLOCKED (宪法 2.1)'];", // v0
|
|||
|
|
"const required = ['裁决 │ PASS / BLOCKED (宪章 §2.1 · 章节封印)'];", // v1
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: 'standard-tier',
|
|||
|
|
target: V2_STRINGS.standard,
|
|||
|
|
anchors: [
|
|||
|
|
"required.push('=== AI CODE REVIEW REPORT === (规范/安全/质量/架构 4 维度)');", // v0
|
|||
|
|
"required.push('章节封印 (模板 M): 规范/安全/质量/架构 4 维度 + BOOKWORM REVIEW SEAL 框');", // v1
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: 'red-team-tier',
|
|||
|
|
target: V2_STRINGS.redTeam,
|
|||
|
|
anchors: [
|
|||
|
|
"required.push('=== RED TEAM SELF-REVIEW === (5 问对抗自审, 宪法 11.3)');", // v0
|
|||
|
|
"required.push('章节封印 (模板 L): SAFETY SEAL + 红队 5 问分节 (§11.3)');", // v1
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: 'semantic-diff-tier',
|
|||
|
|
target: V2_STRINGS.semanticDiff,
|
|||
|
|
anchors: [
|
|||
|
|
"required.push('=== SEMANTIC DIFF === (逐行原始→修改→原因→副作用, 宪法 12.1)');", // v0
|
|||
|
|
"required.push('章节封印 (模板 XL): SEMANTIC DIFF 追加节 + 善读者锚定句 (§12.1)');", // v1
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
label: 'header-nudge',
|
|||
|
|
target: V2_STRINGS.header,
|
|||
|
|
anchors: [
|
|||
|
|
"const header = '[review-required] ' + path.basename(filePath)\n + ' 修改 ' + lineCount + ' 行'\n + (sensitive ? ' (安全敏感)' : '')\n + ' — 回复末尾必须附:';", // v0
|
|||
|
|
"const header = '[review-required] ' + path.basename(filePath)\n + ' 修改 ' + lineCount + ' 行'\n + (sensitive ? ' (安全敏感)' : '')\n + ' — 回复末尾必须以【章节封印】格式附:';", // v1
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
function atomicWrite(file, content) {
|
|||
|
|
const tmp = file + '.tmp.' + process.pid + '.' + Date.now();
|
|||
|
|
fs.writeFileSync(tmp, content);
|
|||
|
|
fs.renameSync(tmp, file);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function main() {
|
|||
|
|
if (!fs.existsSync(TARGET)) {
|
|||
|
|
console.error('[patch-sf] 目标不存在:', TARGET);
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
const before = fs.readFileSync(TARGET, 'utf8');
|
|||
|
|
|
|||
|
|
if (before.includes(V2_SENTINEL)) {
|
|||
|
|
console.log('[patch-sf] 已是 v2 (封印框), 跳过');
|
|||
|
|
process.exit(0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let after = before;
|
|||
|
|
const applied = [];
|
|||
|
|
const missing = [];
|
|||
|
|
|
|||
|
|
for (const mig of MIGRATIONS) {
|
|||
|
|
if (after.includes(mig.target)) {
|
|||
|
|
applied.push(mig.label + ' [already-v2]');
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
let hit = false;
|
|||
|
|
for (const anchor of mig.anchors) {
|
|||
|
|
if (after.includes(anchor)) {
|
|||
|
|
after = after.replace(anchor, mig.target);
|
|||
|
|
applied.push(mig.label);
|
|||
|
|
hit = true;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (!hit) missing.push(mig.label);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (applied.length === 0) {
|
|||
|
|
console.error('[patch-sf] 所有锚点均未命中 (v0 和 v1 都不匹配), missing=' + missing.join(','));
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 替换 sentinel: v1 → v2, 或全新注入
|
|||
|
|
if (after.includes(V1_SENTINEL)) {
|
|||
|
|
after = after.replace(V1_SENTINEL, V2_SENTINEL);
|
|||
|
|
} else if (after.startsWith("'use strict';")) {
|
|||
|
|
after = after.replace("'use strict';", "'use strict';\n" + V2_SENTINEL);
|
|||
|
|
} else {
|
|||
|
|
after = V2_SENTINEL + '\n' + after;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 备份
|
|||
|
|
const bakDir = path.join(__dirname, 'bak');
|
|||
|
|
if (!fs.existsSync(bakDir)) fs.mkdirSync(bakDir, { recursive: true });
|
|||
|
|
const bakFile = path.join(bakDir, 'review-report-checker.js.sf.' + Date.now() + '.bak');
|
|||
|
|
fs.writeFileSync(bakFile, before);
|
|||
|
|
|
|||
|
|
atomicWrite(TARGET, after);
|
|||
|
|
|
|||
|
|
console.log('[patch-sf] ✓ applied=[' + applied.join(', ') + ']');
|
|||
|
|
console.log('[patch-sf] bak=' + path.basename(bakFile));
|
|||
|
|
if (missing.length > 0) {
|
|||
|
|
console.warn('[patch-sf] ⚠ missing=' + missing.join(','));
|
|||
|
|
}
|
|||
|
|
console.log('[patch-sf] 下次 Edit/Write 即生效【封印框】提示');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
main();
|