#!/usr/bin/env node 'use strict'; /** * 补丁 — 将 memory-audit 接入 daily-health-snapshot * * 目的: * daily-health-snapshot.js 每日运行时,顺带生成 memory-audit 快照 JSON, * 与 health-.json / predict-.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-.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();