#!/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); }