bookworm-smart-assistant/scripts/archive/apply-remaining-patches.js

199 lines
8.1 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
/**
* 应用剩余手动补丁 (F1-2, F1-6, constitution-precheck 注册)
*
* 用法:
* node scripts/apply-remaining-patches.js # 预览
* node scripts/apply-remaining-patches.js --apply # 应用
*/
'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 dryRun = !process.argv.includes('--apply');
console.log('=== 剩余补丁应用工具 ===');
console.log(`模式: ${dryRun ? 'DRY-RUN (预览)' : 'APPLY'}`);
console.log('');
let patchCount = 0;
let errorCount = 0;
function backup(filePath) {
const bakPath = filePath + '.bak-remaining';
if (!fs.existsSync(bakPath)) {
fs.copyFileSync(filePath, bakPath);
console.log(` 已备份: ${bakPath}`);
}
}
// ═══════════════════════════════════════════════════
// F1-2: route-interceptor.js — 消除 intent-classifier 双重调用
// ═══════════════════════════════════════════════════
console.log('[F1-2] route-interceptor.js — 消除 intent-classifier 双重调用');
try {
const riPath = path.join(HOOKS_DIR, 'route-interceptor.js');
let content = fs.readFileSync(riPath, 'utf8');
// 改动 1: 函数签名增加 precomputedIntent 参数
const sig1 = 'function runRouteEngine(prompt, cwd)';
const sig1New = 'function runRouteEngine(prompt, cwd, precomputedIntent)';
// 改动 2: 替换内部重复调用为使用传入参数
const dup1 = "const intentClassifier = safeRequire(path.join(SCRIPTS_DIR, 'intent-classifier.js'));\n";
const dup2 = " const intentResult = intentClassifier ? intentClassifier.classifyIntent(prompt) : { intents: [], entities: [] };";
const dupReplace = "// F1-2: 使用调用方传入的 intent避免重复分类\n const intentResult = precomputedIntent || { intents: [], entities: [] };";
// 改动 3: main() 中传入 intent
const call1 = 'routing = runRouteEngine(prompt, cwd);';
const call1New = 'routing = runRouteEngine(prompt, cwd, intent);';
if (content.includes(sig1) && !content.includes(sig1New)) {
if (!dryRun) {
backup(riPath);
content = content.replace(sig1, sig1New);
// 替换内部双重调用(处理可能的缩进差异)
const dupPattern = /const intentClassifier = safeRequire\(path\.join\(SCRIPTS_DIR, 'intent-classifier\.js'\)\);\s*\n\s*const intentResult = intentClassifier \? intentClassifier\.classifyIntent\(prompt\) : \{ intents: \[\], entities: \[\] \};/;
content = content.replace(dupPattern, dupReplace);
content = content.replace(call1, call1New);
fs.writeFileSync(riPath, content);
console.log(` 已写入: ${riPath}`);
} else {
console.log(' [预览] 将修改函数签名、移除重复调用、传入已有 intent');
}
patchCount++;
} else if (content.includes(sig1New)) {
console.log(' [跳过] 已应用');
} else {
console.log(' [警告] 未找到目标代码,可能已修改');
errorCount++;
}
} catch (e) {
console.log(` [错误] ${e.message}`);
errorCount++;
}
console.log('');
// ═══════════════════════════════════════════════════
// F1-6: block-sensitive-files.js — self-healer 白名单
// ═══════════════════════════════════════════════════
console.log('[F1-6] block-sensitive-files.js — self-healer 安全白名单');
try {
const bsfPath = path.join(HOOKS_DIR, 'block-sensitive-files.js');
let content = fs.readFileSync(bsfPath, 'utf8');
const marker = 'SETTINGS_SAFE_KEYS';
if (content.includes(marker)) {
console.log(' [跳过] 白名单已存在');
} else {
// 在 "检查文件路径" 注释前插入白名单代码
const insertTarget = " // 检查文件路径\n for (const { pattern, reason } of SENSITIVE_PATH_PATTERNS) {\n if (pattern.test(filePath)) {";
const whitelistCode = ` // ─── F1-6: self-healer 白名单 ──────────────────────
// 允许 self-healer 修改 settings.json 中已知安全的配置键
const SETTINGS_SAFE_KEYS = new Set([
'skipDangerousModePermissionPrompt',
]);
function isSettingsSafeWrite(fp, c) {
if (!/[\\\\/]\\.claude[\\\\/]settings\\.json$/.test(fp)) return false;
if (!c) return false;
try {
const parsed = JSON.parse(c);
const keys = Object.keys(parsed);
return keys.length > 0 && keys.every(k => SETTINGS_SAFE_KEYS.has(k));
} catch { return false; }
}
// ──────────────────────────────────────────────────────
// 检查文件路径
for (const { pattern, reason } of SENSITIVE_PATH_PATTERNS) {
if (pattern.test(filePath)) {
// F1-6: self-healer 白名单豁免
if (isSettingsSafeWrite(filePath, content)) break;`;
if (content.includes(insertTarget)) {
if (!dryRun) {
backup(bsfPath);
content = content.replace(insertTarget, whitelistCode);
fs.writeFileSync(bsfPath, content);
console.log(` 已写入: ${bsfPath}`);
} else {
console.log(' [预览] 将在路径检查前插入 SETTINGS_SAFE_KEYS 白名单');
}
patchCount++;
} else {
console.log(' [警告] 未找到插入点,文件结构可能已变化');
errorCount++;
}
}
} catch (e) {
console.log(` [错误] ${e.message}`);
errorCount++;
}
console.log('');
// ═══════════════════════════════════════════════════
// 注册 constitution-precheck.js 到 settings.json
// ═══════════════════════════════════════════════════
console.log('[注册] constitution-precheck.js → settings.json PreToolUse Write|Edit');
try {
const settingsPath = path.join(CLAUDE_ROOT, 'settings.json');
let content = fs.readFileSync(settingsPath, 'utf8');
const settings = JSON.parse(content);
const hookCmd = 'node C:/Users/janson9527us/.claude/hooks/constitution-precheck.js';
const preToolUse = settings.hooks?.PreToolUse || [];
// 检查是否已注册
const alreadyRegistered = preToolUse.some(entry =>
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('constitution-precheck'))
);
if (alreadyRegistered) {
console.log(' [跳过] 已注册');
} else {
// 找到 Write matcher在其 hooks 数组末尾追加
let registered = false;
for (const entry of preToolUse) {
if (entry.matcher === 'Write' || entry.matcher === 'Edit') {
entry.hooks = entry.hooks || [];
entry.hooks.push({
type: 'command',
command: hookCmd,
timeout: 3000
});
registered = true;
}
}
if (registered) {
if (!dryRun) {
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
console.log(` 已写入: ${settingsPath}`);
} else {
console.log(' [预览] 将在 Write 和 Edit matcher 中注册 constitution-precheck');
}
patchCount++;
} else {
console.log(' [警告] 未找到 Write/Edit PreToolUse 配置');
errorCount++;
}
}
} catch (e) {
console.log(` [错误] ${e.message}`);
errorCount++;
}
console.log('');
console.log('=== 执行摘要 ===');
console.log(` 补丁数: ${patchCount}`);
console.log(` 错误数: ${errorCount}`);
if (dryRun) {
console.log('');
console.log('运行 --apply 以应用: node scripts/apply-remaining-patches.js --apply');
}
console.log('');
console.log('完成。回滚: 使用 .bak-remaining 文件。');