#!/usr/bin/env node /** * Patch C2: 强制 evolution-log / route-feedback 走 safeAppendJsonl({useLock:true}) * 并将 stop-dispatcher 的 dedup 从 Batch1 移到 Batch3 尾部,避免 dedup 后被 * 后续 Batch append 抢占覆盖丢失条目。 * 幂等: sentinel C2_SAFE_APPEND_v1 / C2_DEDUP_TAIL_v1 */ const fs = require('fs'); const path = require('path'); const ROOT = path.join(__dirname, '..', '..'); const DISP = path.join(ROOT, 'hooks', 'stop-dispatcher.js'); const ROUTE_AUDITOR = path.join(ROOT, 'hooks', 'route-auditor.js'); const ADAPTIVE = path.join(ROOT, 'scripts', 'adaptive-disambiguator.js'); const SAFE_APPEND_REQUIRE = "require('./lib/safe-append.js')"; const SENTINEL_APPEND = 'C2_SAFE_APPEND_v1'; const SENTINEL_DEDUP = 'C2_DEDUP_TAIL_v1'; function bak(file, src, tag) { const dir = path.join(path.dirname(file), 'archive'); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(path.join(dir, path.basename(file) + '.bak.' + tag + '.' + Date.now()), src); } function write(file, content) { const tmp = file + '.tmp.' + process.pid; fs.writeFileSync(tmp, content); fs.renameSync(tmp, file); } // --- 1) stop-dispatcher: evolution-log 写入加锁 + dedup 移尾部 --- function patchDispatcher() { const src = fs.readFileSync(DISP, 'utf8'); let patched = src; if (patched.includes(SENTINEL_APPEND) && patched.includes(SENTINEL_DEDUP)) { console.error('[patch-c2] dispatcher already applied'); return; } // (a) evolution-log append 改 safeAppendJsonl (CRLF-tolerant, 只匹配 appendFileSync 行) if (!patched.includes(SENTINEL_APPEND)) { const oldEvoLine = " fs.appendFileSync(evoLog, (needsNewline ? '\\n' : '') + JSON.stringify(entry) + '\\n');"; if (!patched.includes(oldEvoLine)) { console.error('[patch-c2] evo anchor miss'); process.exit(3); } const newEvoLine = " // " + SENTINEL_APPEND + ": 文件锁避免并发半写\n" + " if (needsNewline) { try { fs.appendFileSync(evoLog, '\\n'); } catch {} }\n" + " try {\n" + " const { safeAppendJsonl } = require('./lib/safe-append.js');\n" + " safeAppendJsonl(evoLog, entry, { useLock: true });\n" + " } catch {\n" + " try { fs.appendFileSync(evoLog, JSON.stringify(entry) + '\\n'); } catch {}\n" + " }"; patched = patched.replace(oldEvoLine, newEvoLine); } // (b) dedup 从 Batch1 移除 → Batch3 尾部 (CRLF-tolerant: 不含行尾) if (!patched.includes(SENTINEL_DEDUP)) { const oldDedupInBatch1 = " race('dedup-errors', () => deduplicateHookErrors(), 500),"; if (!patched.includes(oldDedupInBatch1)) { console.error('[patch-c2] dedup batch1 anchor miss'); process.exit(4); } patched = patched.replace( oldDedupInBatch1, " // " + SENTINEL_DEDUP + ": 已迁移到 Batch3 尾部 (避免被后续 append 抢占覆盖)" ); // 将 dedup 插入到 auto-git-push 之后 (只匹配单行关键语句,CRLF 安全) const tailSingle = " }, 5000);"; // 找最后一个 ` }, 5000);` (auto-git-push 的结尾) const lastIdx = patched.lastIndexOf(tailSingle); if (lastIdx === -1) { console.error('[patch-c2] tail anchor miss'); process.exit(5); } const before = patched.slice(0, lastIdx + tailSingle.length); const after = patched.slice(lastIdx + tailSingle.length); patched = before + "\n // " + SENTINEL_DEDUP + ": dedup 放最后,确保所有 append 已完成" + "\n await _budgetRace('dedup-errors', () => deduplicateHookErrors(), 500);" + after; } if (patched === src) { console.error('[patch-c2] dispatcher no change'); return; } bak(DISP, src, 'c2'); write(DISP, patched); console.error('[patch-c2] dispatcher patched'); } // --- 2) route-auditor: route-feedback 写入加锁 --- function patchRouteAuditor() { if (!fs.existsSync(ROUTE_AUDITOR)) return; const src = fs.readFileSync(ROUTE_AUDITOR, 'utf8'); if (src.includes(SENTINEL_APPEND)) { console.error('[patch-c2] route-auditor already applied'); return; } // 只处理 route-feedback.jsonl 路径;保留其他 log const old = " fs.appendFileSync(logFile, JSON.stringify(entry) + '\\n');"; if (!src.includes(old)) { console.error('[patch-c2] route-auditor anchor miss, skip'); return; } const replacement = " // " + SENTINEL_APPEND + ": 使用 safeAppendJsonl 文件锁防并发损坏\n" + " try {\n" + " const { safeAppendJsonl } = require('./lib/safe-append.js');\n" + " safeAppendJsonl(logFile, entry, { useLock: true });\n" + " } catch {\n" + " try { fs.appendFileSync(logFile, JSON.stringify(entry) + '\\n'); } catch {}\n" + " }"; const patched = src.replace(old, replacement); if (patched === src) return; bak(ROUTE_AUDITOR, src, 'c2'); write(ROUTE_AUDITOR, patched); console.error('[patch-c2] route-auditor patched'); } // --- 3) adaptive-disambiguator: evolution-log append 加锁 --- function patchAdaptive() { if (!fs.existsSync(ADAPTIVE)) return; const src = fs.readFileSync(ADAPTIVE, 'utf8'); if (src.includes(SENTINEL_APPEND)) { console.error('[patch-c2] adaptive already applied'); return; } const old = " fs.appendFileSync(evolutionLog, JSON.stringify(logEntry) + '\\n');"; if (!src.includes(old)) { console.error('[patch-c2] adaptive anchor miss, skip'); return; } const replacement = " // " + SENTINEL_APPEND + ": 与 stop-dispatcher consistency-sentinel 共享 evolution-log,必须加锁\n" + " try {\n" + " const { safeAppendJsonl } = require(require('path').join(__dirname, '..', 'hooks', 'lib', 'safe-append.js'));\n" + " safeAppendJsonl(evolutionLog, logEntry, { useLock: true });\n" + " } catch {\n" + " try { fs.appendFileSync(evolutionLog, JSON.stringify(logEntry) + '\\n'); } catch {}\n" + " }"; const patched = src.replace(old, replacement); if (patched === src) return; bak(ADAPTIVE, src, 'c2'); write(ADAPTIVE, patched); console.error('[patch-c2] adaptive patched'); } patchDispatcher(); patchRouteAuditor(); patchAdaptive();