- VERSION file as authoritative version source - export.mjs reads VERSION with package.json fallback - bw-ota.ps1 DryRun mode for safe testing - auto-setup.ps1 bumped to v3.2.0 (Phase 8 OTA)
139 lines
6.1 KiB
JavaScript
139 lines
6.1 KiB
JavaScript
#!/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();
|