bookworm-smart-assistant/scripts/patches/patch-c2-safe-append-lock.js

139 lines
6.1 KiB
JavaScript
Raw Normal View History

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