bookworm-smart-assistant/hooks/memory-persistence-trigger.js

158 lines
4.8 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* PostToolUse Hook: Pre-Compaction 记忆持久化触发器
* @version 1.0.0
* @file memory-persistence-trigger.js
* @matcher Bash|Edit|Write|Read|Glob|Grep|Skill|Agent
*
* 功能: 追踪工具调用次数 30 次提醒 Claude 持久化重要记忆
* 触发条件: count 达到 30 的倍数时306090...
* 冷却机制: 触发后 5 分钟内不重复触发
*
* stdin: { "session_id": "...", "tool_name": "...", "tool_input": {...}, "tool_output": "..." }
* stdout: { "hookSpecificOutput": { "additionalContext": "..." } } (仅在触发时)
* 退出码: 0 (始终放行fail-open)
*
* 性能目标: <5ms (仅读写一个小 JSON 文件 + 计数器判断)
*/
const fs = require('fs');
const path = require('path');
const readStdin = require('./lib/read-stdin.js');
// ─── 路径解析 ────────────────────────────────────────
let debugDir;
try {
const { PATHS } = require('../scripts/paths.config.js');
debugDir = PATHS.debugDir;
} catch {
debugDir = path.resolve(__dirname, '..', 'debug');
}
const COUNTER_FILE = path.join(debugDir, 'tool-call-counter.json');
const TRIGGER_INTERVAL = 30; // 每 30 次触发
const COOLDOWN_MS = 5 * 60 * 1000; // 5 分钟冷却
// ─── 计数器文件读写 ──────────────────────────────────
/**
* 读取计数器状态
* @returns {{ count: number, lastTrigger: string, sessionId: string }}
*/
function readCounter() {
try {
if (fs.existsSync(COUNTER_FILE)) {
return JSON.parse(fs.readFileSync(COUNTER_FILE, 'utf8'));
}
} catch {}
return { count: 0, lastTrigger: '', sessionId: '' };
}
/**
* 写入计数器状态 (原子写入: temp+rename 防止竞态截断)
* @param {{ count: number, lastTrigger: string, sessionId: string }} state
*/
function writeCounter(state) {
try {
if (!fs.existsSync(debugDir)) {
fs.mkdirSync(debugDir, { recursive: true });
}
var tmp = COUNTER_FILE + '.tmp.' + process.pid;
fs.writeFileSync(tmp, JSON.stringify(state, null, 2) + '\n');
fs.renameSync(tmp, COUNTER_FILE);
} catch {}
}
/**
* 检查是否在冷却期内
* @param {string} lastTrigger - ISO 时间字符串
* @returns {boolean}
*/
function isInCooldown(lastTrigger) {
if (!lastTrigger) return false;
try {
const elapsed = Date.now() - new Date(lastTrigger).getTime();
return elapsed < COOLDOWN_MS;
} catch {
return false;
}
}
/**
* 构建 additionalContext 提醒文本
* @param {number} count - 当前工具调用次数
* @returns {string}
*/
function buildReminder(count) {
return `[MEMORY_PERSISTENCE_TRIGGER] 你已进行了较多工具调用(${count}次),上下文可能即将被压缩。
如果本次会话有重要决策关键发现或架构变更请考虑写入记忆文件
写入规则:
- 稳定结论写入 MEMORY.md 对应章节
- 项目细节写入 memory/ 下的专题文件
- 章节标题加 #tag 标签便于检索 ( ## 部署记录 #deploy #服务器)
- 常用标签: #bookworm #deploy #agenttin #wan22 #ai #安全 #架构 #debug #进度
- 禁止写入: API Key密码tokenSSH 密钥等敏感信息
- 禁止写入: 会话临时状态未验证的猜测
检索命令: node ~/.claude/scripts/memory-search.js "<>" [--tag <>]`;
}
// ─── 主流程 ──────────────────────────────────────────
function main() {
readStdin({ maxSize: 128 * 1024 }).then(input => {
const sessionId = input.session_id || 'unknown';
// 读取当前计数器状态
const state = readCounter();
// 新会话检测: sessionId 变化则重置计数器
if (state.sessionId && state.sessionId !== sessionId) {
state.count = 0;
state.lastTrigger = '';
}
state.sessionId = sessionId;
// 递增计数器
state.count++;
// 判断是否需要触发提醒
const shouldTrigger = (state.count % TRIGGER_INTERVAL === 0)
&& !isInCooldown(state.lastTrigger);
if (shouldTrigger) {
// 更新触发时间
state.lastTrigger = new Date().toISOString();
writeCounter(state);
// 输出 additionalContext 提醒
const output = {
hookSpecificOutput: {
additionalContext: buildReminder(state.count)
}
};
process.stdout.write(JSON.stringify(output));
} else {
// 仅更新计数器,无输出
writeCounter(state);
}
process.exit(0);
}).catch(() => process.exit(0));
}
// 模块导出 (供测试使用)
if (typeof module !== 'undefined') {
module.exports = {
readCounter,
writeCounter,
isInCooldown,
buildReminder,
COUNTER_FILE,
TRIGGER_INTERVAL,
COOLDOWN_MS,
};
}
if (require.main === module) {
main();
}