bookworm-smart-assistant/scripts/patches/patch-p2-handoff-mechanism.js

133 lines
4.4 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
/**
* patch-p2-handoff-mechanism.js
*
* P2 /handoff 机制改造 patch:
* 1. context-pressure-monitor.js: CRITICAL 消息改为建议 /handoff
* 2. pre-compact-handoff.js: 添加过期 handoff 文件清理 (保留最新 5 )
* 3. CLAUDE.md: 上下文管理节添加 /handoff 说明
* 4. SKILL-REGISTRY.md: 添加 handoff 技能条目 + 更新统计
*
* 幂等: 每处修改前检查 sentinel patch 则跳过
*/
'use strict';
const fs = require('fs');
const path = require('path');
const CLAUDE_ROOT = path.join(require('os').homedir(), '.claude');
const HOOKS_DIR = path.join(CLAUDE_ROOT, 'hooks');
const results = [];
function patchFile(filePath, label, patches) {
if (!fs.existsSync(filePath)) {
results.push(`[SKIP] ${label}: 文件不存在 ${filePath}`);
return false;
}
const bak = filePath + '.bak';
let content = fs.readFileSync(filePath, 'utf8');
let changed = false;
for (const p of patches) {
if (p.sentinel && content.includes(p.sentinel)) {
results.push(`[SKIP] ${label}/${p.name}: sentinel 已存在`);
continue;
}
if (p.find && !content.includes(p.find)) {
results.push(`[SKIP] ${label}/${p.name}: 未找到目标文本`);
continue;
}
if (p.find) {
content = content.replace(p.find, p.replace);
} else if (p.insertAfter) {
const idx = content.indexOf(p.insertAfter);
if (idx === -1) {
results.push(`[SKIP] ${label}/${p.name}: 未找到插入锚点`);
continue;
}
const insertAt = idx + p.insertAfter.length;
content = content.slice(0, insertAt) + p.insert + content.slice(insertAt);
}
changed = true;
results.push(`[OK] ${label}/${p.name}`);
}
if (changed) {
if (!fs.existsSync(bak)) fs.copyFileSync(filePath, bak);
fs.writeFileSync(filePath, content, 'utf8');
}
return changed;
}
// === 1. context-pressure-monitor.js: CRITICAL 消息 ===
patchFile(
path.join(HOOKS_DIR, 'context-pressure-monitor.js'),
'context-pressure-monitor',
[{
name: 'critical-msg-handoff',
sentinel: '/handoff',
find: 'dump 当前关键决策到 .bookworm-progress.md, 然后请用户 /clear',
replace: '调用 /handoff 保存当前进度到 .bookworm-progress.md, 然后请用户 /clear'
}]
);
// === 2. pre-compact-handoff.js: 添加过期 handoff 清理 ===
patchFile(
path.join(HOOKS_DIR, 'pre-compact-handoff.js'),
'pre-compact-handoff',
[{
name: 'cleanup-old-handoffs',
sentinel: 'PATCH-P2-HANDOFF-CLEANUP',
insertAfter: 'fs.renameSync(_tmpHandoff, HANDOFF_PATH);',
insert: `
// [PATCH-P2-HANDOFF-CLEANUP] 清理过期 handoff 时间戳文件, 保留最新 5 个
try {
const files = fs.readdirSync(SESSION_STATE_DIR)
.filter(f => /^handoff-\\d+\\.json$/.test(f))
.map(f => ({ name: f, time: parseInt(f.match(/\\d+/)[0], 10) }))
.sort((a, b) => b.time - a.time);
const toDelete = files.slice(5);
for (const f of toDelete) {
try { fs.unlinkSync(path.join(SESSION_STATE_DIR, f.name)); } catch {}
}
} catch {}
`
}]
);
// === 3. CLAUDE.md: 上下文管理节添加 /handoff ===
patchFile(
path.join(CLAUDE_ROOT, 'CLAUDE.md'),
'CLAUDE.md',
[{
name: 'handoff-doc',
sentinel: '/handoff',
insertAfter: '- 长会话(>20 轮工具调用)时主动建议 `/clear` 重置上下文',
insert: '\n- **主动交接** (P2): 上下文压力 CRITICAL 时自动建议 `/handoff`; 手动 `/handoff` 可随时调用, 将进度写入 `.bookworm-progress.md` + 生成继续提示词 + 清理过期 handoff JSON'
}]
);
// === 4. SKILL-REGISTRY.md: 添加 handoff 条目 ===
const registryPath = path.join(CLAUDE_ROOT, 'SKILL-REGISTRY.md');
patchFile(registryPath, 'SKILL-REGISTRY', [
{
name: 'add-handoff-entry',
sentinel: '| 102 | handoff',
insertAfter: '| 101 | mcp-prune | stable | MCP 剪枝分析工具 (Phase 1 · T1.4)。基于使用率识别低频 MCP 候选,生成剪枝 plan。永不自动修改 .claude.json。 |',
insert: '\n| 102 | handoff | stable | 上下文交接/进度保存/.bookworm-progress.md 生成/继续提示词 |'
},
{
name: 'update-skill-count',
sentinel: '95 (',
find: '- **总计**: 94 (',
replace: '- **总计**: 95 ('
}
]);
// === 输出摘要 ===
console.log('=== patch-p2-handoff-mechanism ===');
for (const r of results) console.log(r);
console.log('=== done ===');