bookworm-smart-assistant/scripts/patches/patch-memory-audit-snapshot.js

126 lines
4.3 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
'use strict';
/**
* 补丁 memory-audit 接入 daily-health-snapshot
*
* 目的:
* daily-health-snapshot.js 每日运行时顺带生成 memory-audit 快照 JSON
* health-<date>.json / predict-<date>.json 并列存放
*
* 工具路径: memory/_tools/memory-audit.js ( scripts/ tamper-protection 约束改位)
* 调用方式: execFileSync (node --json 输出 require保持模块边界清晰)
*
* 幂等:
* - sentinel: MEMORY_AUDIT_SNAPSHOT_2026_04_25
* - 检测 daily-health-snapshot.js 内容是否已含 sentinel已存在则 skip
*
* 原子性:
* - .bak.memory-audit 备份
* - tmp + rename
*
* 回滚:
* mv daily-health-snapshot.js.bak.memory-audit daily-health-snapshot.js
*/
const fs = require('fs');
const path = require('path');
const CLAUDE_ROOT = path.join(__dirname, '..', '..');
const TARGET = path.join(CLAUDE_ROOT, 'scripts', 'daily-health-snapshot.js');
const BAK = TARGET + '.bak.memory-audit';
const SENTINEL = 'MEMORY_AUDIT_SNAPSHOT_2026_04_25';
const INSERTION = `
// [v6.6] 可选: 记忆文件体检 (memory/_tools/memory-audit.js)
// sentinel: ${SENTINEL}
// 运行 memory-audit --json保存快照到 health-snapshots/memory-audit-<date>.json
// 若 orphan/ghost > 0 或 health.score < 80追加告警到 evolution-log
try {
const memAuditScript = path.join(
CLAUDE_ROOT, 'projects', 'C--Users-leesu', 'memory', '_tools', 'memory-audit.js'
);
if (fs.existsSync(memAuditScript)) {
const memResult = execFileSync(process.execPath, [memAuditScript, '--json'], {
timeout: 10000, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
});
const memReport = JSON.parse(memResult);
fs.writeFileSync(
path.join(SNAPSHOT_DIR, \`memory-audit-\${dateStr}.json\`),
JSON.stringify(memReport, null, 2)
);
// 告警: orphan/ghost 或 分数偏低
const h = memReport.health || {};
const needAlert = (h.orphanCount > 0) || (h.ghostCount > 0) || (h.score < 80);
if (needAlert) {
const logFile = path.join(CLAUDE_ROOT, 'evolution-log.jsonl');
const existing = fs.existsSync(logFile)
? fs.readFileSync(logFile, 'utf8').trim().split('\\n').filter(Boolean) : [];
const lastSeq = existing.length
? (JSON.parse(existing[existing.length - 1]).seq || 0) : 0;
const memEntry = {
seq: lastSeq + 1,
ts: dateStr,
version: 'v6.6',
scope: 'memory-audit',
trigger: 'daily-health-snapshot',
summary: \`记忆健康 \${h.score}/100 — orphan=\${h.orphanCount} ghost=\${h.ghostCount} oversize=\${h.oversizeCount}\`,
tags: ['memory-alert', 'auto-snapshot'],
};
fs.appendFileSync(logFile, JSON.stringify(memEntry) + '\\n');
}
}
} catch {}
`;
function main() {
if (!fs.existsSync(TARGET)) {
console.error('[ERROR] 目标文件不存在:', TARGET);
process.exit(1);
}
const original = fs.readFileSync(TARGET, 'utf8');
// 幂等: 已含 sentinel 则 skip
if (original.includes(SENTINEL)) {
console.log('[skip] 补丁已应用 (sentinel detected):', SENTINEL);
return;
}
// 定位插入点: 在 skill-retirement-advisor 块结尾之后、evolution-log 告警之前
const ANCHOR = ' // 若 overall < 70追加告警到 evolution-log';
if (!original.includes(ANCHOR)) {
console.error('[ERROR] 未找到插入锚点,原文件结构可能已变。终止。');
process.exit(1);
}
// 备份
if (!fs.existsSync(BAK)) {
fs.writeFileSync(BAK, original);
console.log('[backup]', BAK);
}
// 插入
const patched = original.replace(ANCHOR, INSERTION + ANCHOR);
// 原子写: tmp + rename
const tmp = TARGET + '.tmp.memory-audit';
fs.writeFileSync(tmp, patched);
fs.renameSync(tmp, TARGET);
// 校验 sentinel 已写入
const verify = fs.readFileSync(TARGET, 'utf8');
if (!verify.includes(SENTINEL)) {
console.error('[FATAL] 写入后未检出 sentinel疑似原子性失败');
process.exit(1);
}
console.log('[ok] 补丁已应用:', SENTINEL);
console.log(' TARGET:', TARGET);
console.log(' BAK: ', BAK);
console.log(' 新增行数:', INSERTION.split('\n').length);
}
main();