bookworm-smart-assistant/hooks/session-start-memory-audit.js
Bookworm Admin b7a8e29d21 release: v6.7.0 - OTA E2E test release
- 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)
2026-04-27 17:59:44 +08:00

113 lines
3.6 KiB
JavaScript

#!/usr/bin/env node
'use strict';
/**
* SessionStart Memory Audit Hook
* sentinel: SESSION_START_MEMORY_AUDIT_2026_04_25
*
* 事件: UserPromptSubmit (Bookworm 无 SessionStart, 用 UserPromptSubmit + 日守卫)
* 目的: 每日首次会话自动体检记忆文件健康度, 异常时作为 additionalContext 提醒
*
* 预算:
* - 今日已跑: <5ms (stamp 快速返回)
* - 首次: <1000ms (memory-audit.js --json, execFileSync 3s timeout)
*
* 告警门槛 (静默默认, 命中才输出):
* - orphan >= 3 目录有文件未索引
* - ghost >= 1 索引指向已删文件
* - health.score < 80
*
* Feature flag: .bookworm-features.json.memory_audit=false 可关闭
* Fail-open: 任何异常 exit 0, 永不阻断用户输入
*/
const fs = require('fs');
const path = require('path');
const os = require('os');
const HOME = process.env.USERPROFILE || process.env.HOME || os.homedir();
const CLAUDE_ROOT = process.env.CLAUDE_HOME ||
(fs.existsSync(path.join(HOME, '.claude')) ? path.join(HOME, '.claude') : HOME);
const DEBUG_DIR = path.join(CLAUDE_ROOT, 'debug');
const STAMP_FILE = path.join(DEBUG_DIR, '.last-memory-audit');
const FEATURE_FLAGS_FILE = path.join(CLAUDE_ROOT, '.bookworm-features.json');
const AUDIT_TOOL = path.join(
CLAUDE_ROOT, 'projects', 'C--Users-leesu', 'memory', '_tools', 'memory-audit.js'
);
const TODAY = new Date().toISOString().slice(0, 10);
function safeExit() { process.exit(0); }
function main() {
try {
// [P0-2] SESSION_ONCE_v1 — 会话级去重 (<1ms)
try { if (require('./lib/session-once.js').hasRun('memory-audit')) return safeExit(); } catch {}
// Feature flag
try {
if (fs.existsSync(FEATURE_FLAGS_FILE)) {
const flags = JSON.parse(fs.readFileSync(FEATURE_FLAGS_FILE, 'utf8'));
if (flags && flags.memory_audit === false) return safeExit();
}
} catch {}
// 日级守卫
if (fs.existsSync(STAMP_FILE)) {
try {
const last = fs.readFileSync(STAMP_FILE, 'utf8').trim();
if (last === TODAY) return safeExit();
} catch {}
}
// 工具存在性
if (!fs.existsSync(AUDIT_TOOL)) return safeExit();
// 运行审计
const { execFileSync } = require('child_process');
let report;
try {
const result = execFileSync(process.execPath, [AUDIT_TOOL, '--json'], {
timeout: 3000,
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe'],
});
report = JSON.parse(result);
} catch {
return safeExit();
}
// 更新 stamp (不管结果, 避免失败时每次会话都重试)
try {
fs.mkdirSync(DEBUG_DIR, { recursive: true });
fs.writeFileSync(STAMP_FILE, TODAY);
// [P0-2] SESSION_ONCE_v1
try { require('./lib/session-once.js').markRun('memory-audit'); } catch {}
} catch {}
// 判断是否需要告警
const h = (report && report.health) || {};
const orphan = h.orphanCount || 0;
const ghost = h.ghostCount || 0;
const oversize = h.oversizeCount || 0;
const score = typeof h.score === 'number' ? h.score : 100;
const needs = (orphan >= 3) || (ghost >= 1) || (score < 80);
if (!needs) return safeExit();
// 输出为 UserPromptSubmit 的 additionalContext
const lines = [
'[memory-audit] 记忆系统需要关注:',
' score=' + score + '/100 | orphan=' + orphan + ' ghost=' + ghost + ' oversize=' + oversize,
' 运行: node projects/C--Users-leesu/memory/_tools/memory-audit.js',
' 一键归档 orphan: ... --fix',
];
console.log(lines.join('\n'));
} catch {
// fail-open
}
safeExit();
}
if (require.main === module) main();
module.exports = { main };