129 lines
5.2 KiB
JavaScript
129 lines
5.2 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
/**
|
|||
|
|
* W2 补丁 — 2026-04-16 audit-2026-04-16 WARNING
|
|||
|
|
*
|
|||
|
|
* 问题: H1 修复后 actual-skills.jsonl 已成主路径,但 actual-skill.json 双写/双读残留
|
|||
|
|
* 占用 I/O 与代码复杂度;直接移除风险高,需 feature flag 安全开关。
|
|||
|
|
*
|
|||
|
|
* 修复方案: 引入环境变量 BOOKWORM_LEGACY_ACTUAL_SKILL 作为 feature flag
|
|||
|
|
* 默认 (未设置): 跳过旧 json 写入 + 跳过旧 json 回退读取
|
|||
|
|
* 设为 '1': 保留双写+双读 (紧急回滚路径)
|
|||
|
|
*
|
|||
|
|
* 两处修改:
|
|||
|
|
* - hooks/route-compliance-gate.js L264-266 写入路径前加环境变量守卫
|
|||
|
|
* - hooks/route-auditor.js L119-128 回退读取前加环境变量守卫
|
|||
|
|
*
|
|||
|
|
* 清理: 补丁运行成功后删除 debug/actual-skill.json (若存在)
|
|||
|
|
*
|
|||
|
|
* 回滚: set BOOKWORM_LEGACY_ACTUAL_SKILL=1 即可恢复旧行为
|
|||
|
|
* 彻底移除计划: 稳定运行 1 周后 (2026-04-23) 出补丁 W2b 删除 flag 与代码
|
|||
|
|
*
|
|||
|
|
* 幂等: sentinel = 'BOOKWORM_LEGACY_ACTUAL_SKILL'
|
|||
|
|
*/
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
const HOOKS_DIR = path.join(__dirname, '..', '..', 'hooks');
|
|||
|
|
const GATE_FILE = path.join(HOOKS_DIR, 'route-compliance-gate.js');
|
|||
|
|
const AUDITOR_FILE = path.join(HOOKS_DIR, 'route-auditor.js');
|
|||
|
|
const LEGACY_DATA = path.join(__dirname, '..', '..', 'debug', 'actual-skill.json');
|
|||
|
|
const SENTINEL = 'BOOKWORM_LEGACY_ACTUAL_SKILL';
|
|||
|
|
|
|||
|
|
function patchGate() {
|
|||
|
|
const before = fs.readFileSync(GATE_FILE, 'utf8');
|
|||
|
|
if (before.includes(SENTINEL)) {
|
|||
|
|
console.log('[patch-w2] route-compliance-gate.js 已打过补丁,跳过');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
const anchor =
|
|||
|
|
' fs.appendFileSync(actualJsonl, line);\n' +
|
|||
|
|
' // 向后兼容: 仍写旧单文件,让未迁移的 auditor 能降级读取\n' +
|
|||
|
|
' const actualFile = path.join(DEBUG_DIR, \'actual-skill.json\');\n' +
|
|||
|
|
' fs.writeFileSync(actualFile, line);';
|
|||
|
|
const replacement =
|
|||
|
|
' fs.appendFileSync(actualJsonl, line);\n' +
|
|||
|
|
' // W2 (2026-04-16): 旧 actual-skill.json 默认不写,由 feature flag 恢复\n' +
|
|||
|
|
' // 回滚: set BOOKWORM_LEGACY_ACTUAL_SKILL=1\n' +
|
|||
|
|
' if (process.env.BOOKWORM_LEGACY_ACTUAL_SKILL === \'1\') {\n' +
|
|||
|
|
' const actualFile = path.join(DEBUG_DIR, \'actual-skill.json\');\n' +
|
|||
|
|
' fs.writeFileSync(actualFile, line);\n' +
|
|||
|
|
' }';
|
|||
|
|
if (!before.includes(anchor)) {
|
|||
|
|
console.error('[patch-w2] route-compliance-gate.js 锚点未匹配');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
const after = before.replace(anchor, replacement);
|
|||
|
|
const backup = GATE_FILE + '.bak.w2.' + Date.now();
|
|||
|
|
fs.writeFileSync(backup, before);
|
|||
|
|
const _tmp = GATE_FILE + '.tmp.' + process.pid;
|
|||
|
|
fs.writeFileSync(_tmp, after);
|
|||
|
|
fs.renameSync(_tmp, GATE_FILE);
|
|||
|
|
console.log('[patch-w2] ✓ route-compliance-gate.js 双写路径已加 flag 守卫');
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function patchAuditor() {
|
|||
|
|
const before = fs.readFileSync(AUDITOR_FILE, 'utf8');
|
|||
|
|
if (before.includes(SENTINEL)) {
|
|||
|
|
console.log('[patch-w2] route-auditor.js 已打过补丁,跳过');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
const anchor =
|
|||
|
|
' // 回退: 旧单文件 actual-skill.json\n' +
|
|||
|
|
' if (!actualSkill) {\n' +
|
|||
|
|
' const actualFile = path.join(DEBUG_DIR, \'actual-skill.json\');\n' +
|
|||
|
|
' if (fs.existsSync(actualFile)) {\n' +
|
|||
|
|
' const actualData = JSON.parse(fs.readFileSync(actualFile, \'utf8\'));\n' +
|
|||
|
|
' if (actualData.traceId === state.traceId && actualData.actualSkill) {\n' +
|
|||
|
|
' actualSkill = actualData.actualSkill;\n' +
|
|||
|
|
' }\n' +
|
|||
|
|
' }\n' +
|
|||
|
|
' }';
|
|||
|
|
const replacement =
|
|||
|
|
' // 回退: 旧单文件 actual-skill.json (W2: 默认跳过,由 flag 恢复)\n' +
|
|||
|
|
' if (!actualSkill && process.env.BOOKWORM_LEGACY_ACTUAL_SKILL === \'1\') {\n' +
|
|||
|
|
' const actualFile = path.join(DEBUG_DIR, \'actual-skill.json\');\n' +
|
|||
|
|
' if (fs.existsSync(actualFile)) {\n' +
|
|||
|
|
' const actualData = JSON.parse(fs.readFileSync(actualFile, \'utf8\'));\n' +
|
|||
|
|
' if (actualData.traceId === state.traceId && actualData.actualSkill) {\n' +
|
|||
|
|
' actualSkill = actualData.actualSkill;\n' +
|
|||
|
|
' }\n' +
|
|||
|
|
' }\n' +
|
|||
|
|
' }';
|
|||
|
|
if (!before.includes(anchor)) {
|
|||
|
|
console.error('[patch-w2] route-auditor.js 锚点未匹配');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
const after = before.replace(anchor, replacement);
|
|||
|
|
const backup = AUDITOR_FILE + '.bak.w2.' + Date.now();
|
|||
|
|
fs.writeFileSync(backup, before);
|
|||
|
|
const _tmp = AUDITOR_FILE + '.tmp.' + process.pid;
|
|||
|
|
fs.writeFileSync(_tmp, after);
|
|||
|
|
fs.renameSync(_tmp, AUDITOR_FILE);
|
|||
|
|
console.log('[patch-w2] ✓ route-auditor.js 回退路径已加 flag 守卫');
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function cleanupLegacyData() {
|
|||
|
|
try {
|
|||
|
|
if (fs.existsSync(LEGACY_DATA)) {
|
|||
|
|
fs.unlinkSync(LEGACY_DATA);
|
|||
|
|
console.log('[patch-w2] ✓ 删除历史数据文件 debug/actual-skill.json');
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.log('[patch-w2] ! 清理历史文件失败: ' + e.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const a = patchGate();
|
|||
|
|
const b = patchAuditor();
|
|||
|
|
if (a || b) cleanupLegacyData();
|
|||
|
|
console.log('[patch-w2] 完成。回滚: set BOOKWORM_LEGACY_ACTUAL_SKILL=1');
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('[patch-w2] 异常:', e.message);
|
|||
|
|
process.exit(99);
|
|||
|
|
}
|