bookworm-smart-assistant/scripts/archive/apply-v61-integration-patches.js

483 lines
19 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* v6.1 模块集成补丁脚本
*
* Phase 2-3 三个热路径孤岛模块接入路由管线:
* 1. route-interceptor.js adaptive-disambiguator + embedding-router
* 2. post-edit-dispatcher.js hook-priority-scheduler
* 3. route-auditor.js adaptive-disambiguator 反馈闭环
*
* 约束:
* - hooks/ 文件受 integrity-check 保护补丁只追加不重写原逻辑
* - 所有集成使用 safeRequire + try-catch (fail-open)
* - 补丁为幂等操作: 检测 PATCH_MARKER 防止重复注入
*
* 用法:
* node scripts/apply-v61-integration-patches.js # 应用所有补丁
* node scripts/apply-v61-integration-patches.js --check # 检查补丁状态
* node scripts/apply-v61-integration-patches.js --dry-run # 预览变更不写入
*/
'use strict';
const fs = require('fs');
const path = require('path');
// ─── 路径检测 ──────────────────────────────────────────────────────────────
function detectClaudeRoot() {
if (process.env.CLAUDE_HOME) return process.env.CLAUDE_HOME;
const selfDir = path.dirname(__filename);
if (selfDir.includes('.claude')) return selfDir.replace(/[/\\]scripts$/, '');
try { return require('./paths.config.js').PATHS.root; } catch {}
return (process.env.USERPROFILE || process.env.HOME || '').replace(/\\/g, '/') + '/.claude';
}
const CLAUDE_ROOT = detectClaudeRoot();
const HOOKS_DIR = path.join(CLAUDE_ROOT, 'hooks');
const SCRIPTS_DIR = path.join(CLAUDE_ROOT, 'scripts');
const DRY_RUN = process.argv.includes('--dry-run');
const CHECK = process.argv.includes('--check');
// ─── 补丁标记(用于幂等检测)──────────────────────────────────────────────
const PATCH_MARKERS = {
'route-interceptor.js': '// [v6.1-PATCH] adaptive-disambiguator + embedding-router',
'post-edit-dispatcher.js': '// [v6.1-PATCH] hook-priority-scheduler',
'route-auditor.js': '// [v6.1-PATCH] adaptive-disambiguator feedback',
};
// ─── 工具函数 ──────────────────────────────────────────────────────────────
/**
* 读取文件内容失败时返回 null
*/
function readFile(filePath) {
try {
return fs.readFileSync(filePath, 'utf8');
} catch (e) {
return null;
}
}
/**
* 写入文件dry-run 模式只打印
*/
function writeFile(filePath, content) {
if (DRY_RUN) {
console.log(`[DRY-RUN] 将写入: ${filePath}`);
return true;
}
try {
// 原子写入: 先写 .tmp 再 rename防止写入中断
const tmpPath = filePath + '.patch-tmp.' + process.pid;
fs.writeFileSync(tmpPath, content, 'utf8');
fs.renameSync(tmpPath, filePath);
return true;
} catch (e) {
console.error(`[ERROR] 写入失败: ${filePath}${e.message}`);
return false;
}
}
/**
* 检查文件中是否已存在补丁标记
*/
function isPatchApplied(content, marker) {
return content.includes(marker);
}
// ─── 补丁 1: route-interceptor.js ────────────────────────────────────────
//
// 集成点 A: 消歧规则应用后,注入 adaptive-disambiguator (Bayesian 融合)
// 集成点 B: BM25 评分后、融合权重计算前,注入 embedding-router (tie-breaker)
//
// 原始结构 (runRouteEngine 函数内):
// ① results = index.skills.map(...).sort() ← BM25 评分完成
// ② disambResult = routeAnalyzer.applyDisambiguation() ← 消歧规则
// ③ coldStartBoost ← 冷启动
// ④ rerankTopK ← reranking
//
// 插入顺序:
// - embedding-router: 在 ① 之后results 存在),②之前
// - adaptive-disambiguator: 在 ② 之后,③ 之前
// ─────────────────────────────────────────────────────────────────────────
/**
* 补丁代码: embedding-router 注入块
* 插入锚点: results 排序完成后进入消歧之前
*/
const EMBEDDING_PATCH = `
// [v6.1-PATCH] adaptive-disambiguator + embedding-router
// Phase 3 集成: 当 BM25 top-2 差距 < 15% 时,激活向量路由作为 tie-breaker
const embeddingRouter = safeRequire(path.join(SCRIPTS_DIR, 'embedding-router.js'));
if (embeddingRouter && results.length >= 2) {
try {
const top2Scores = [results[0].score, results[1].score];
if (embeddingRouter.shouldActivate(top2Scores)) {
// 仅对 top-10 候选的技能名计算向量相似度(降低开销)
const top10Names = results.slice(0, 10).map(r => r.name);
const embeddingScores = embeddingRouter.computeSimilarity(prompt, top10Names);
// 将 embedding 相似度以 10% 权重融合到 BM25 分数
const embMap = new Map(embeddingScores.map(es => [es.skill, es.similarity]));
for (const r of results) {
const embSim = embMap.get(r.name) || 0;
if (embSim > 0) {
// 微调: embedding 信号最多影响原始分数的 ±10%
r.score = Math.round((r.score + embSim * r.score * 0.10) * 100) / 100;
}
}
// 重新排序embedding 融合后分数可能变化)
results.sort((a, b) => b.score - a.score);
}
} catch {}
}
`;
/**
* 补丁代码: adaptive-disambiguator 注入块
* 插入锚点: applyDisambiguation 调用之后applyColdStartBoost 之前
*/
const ADAPTIVE_DISAMB_PATCH = `
// [v6.1-PATCH] adaptive-disambiguator: Bayesian 后验融合硬规则结果
// 在消歧规则之后调用,将学习到的 Dirichlet 后验概率与硬规则结果融合
const adaptiveDisamb = safeRequire(path.join(SCRIPTS_DIR, 'adaptive-disambiguator.js'));
if (adaptiveDisamb && disambiguated.length > 0) {
try {
// 构造 hardRuleResults 结构供 adaptiveDisambiguate 使用
const hardRuleResults = {
boosted: firedRules.filter(r => r.action === 'boost' || r.winner).map(r => r.winner || r.skill).filter(Boolean),
penalized: firedRules.filter(r => r.action === 'penalize').map(r => r.skill).filter(Boolean),
firedRules: firedRules.map(r => r.id || r.rule || '').filter(Boolean),
};
const intentResult = precomputedIntent || { intents: [], entities: [] };
const bayesianResult = adaptiveDisamb.adaptiveDisambiguate(
disambiguated.slice(0, 5), // 仅对 top-5 候选进行 Bayesian 调整
{ prompt, domain: domainInfo, intent: intentResult },
hardRuleResults
);
if (bayesianResult && bayesianResult.length > 0) {
// 用 Bayesian 融合结果替代纯硬规则结果(替换 top-5保留后续候选
const tail = disambiguated.slice(5);
disambiguated = [...bayesianResult, ...tail];
}
} catch {}
}
`;
/**
* 应用 route-interceptor.js 补丁
*/
function patchRouteInterceptor() {
const filePath = path.join(HOOKS_DIR, 'route-interceptor.js');
const content = readFile(filePath);
if (!content) {
console.error('[SKIP] route-interceptor.js 读取失败');
return false;
}
const marker = PATCH_MARKERS['route-interceptor.js'];
if (isPatchApplied(content, marker)) {
console.log('[SKIP] route-interceptor.js 补丁已存在,跳过');
return true;
}
// ── 插入点 A: embedding-router ──
// 锚点: ` // v5.8: 冲突消歧 (加权投票 + specificity)`
// 在此行之前插入 embedding patch
const ANCHOR_EMBEDDING = ' // v5.8: 冲突消歧 (加权投票 + specificity)';
if (!content.includes(ANCHOR_EMBEDDING)) {
console.error('[ERROR] route-interceptor.js 锚点 A 未找到: 消歧注释行');
return false;
}
// ── 插入点 B: adaptive-disambiguator ──
// 锚点: ` // v5.8: 冷启动防护 (epsilon-greedy + 新技能 boost)`
// 在此行之前插入 adaptive disambiguator patch
const ANCHOR_ADAPTIVE = ' // v5.8: 冷启动防护 (epsilon-greedy + 新技能 boost)';
if (!content.includes(ANCHOR_ADAPTIVE)) {
console.error('[ERROR] route-interceptor.js 锚点 B 未找到: 冷启动注释行');
return false;
}
// 先插 Badaptive disambiguator再插 Aembedding
// 这样两次字符串替换都基于不同的锚点,不会互相干扰
let patched = content
// 插入 B: adaptive-disambiguator在冷启动之前
.replace(
ANCHOR_ADAPTIVE,
ADAPTIVE_DISAMB_PATCH + '\n ' + ANCHOR_ADAPTIVE.trimStart()
)
// 插入 A: embedding-router在消歧之前
.replace(
ANCHOR_EMBEDDING,
EMBEDDING_PATCH + '\n ' + ANCHOR_EMBEDDING.trimStart()
);
if (DRY_RUN) {
console.log('[DRY-RUN] route-interceptor.js 补丁预览:');
console.log(' + embedding-router 集成BM25 tie-breaker');
console.log(' + adaptive-disambiguator 集成Bayesian 融合)');
return true;
}
const ok = writeFile(filePath, patched);
if (ok) console.log('[OK] route-interceptor.js 补丁已应用');
return ok;
}
// ─── 补丁 2: post-edit-dispatcher.js ────────────────────────────────────
//
// 集成点: 在 runSubHook 调用之前,使用 hook-priority-scheduler
// 检查子钩子是否需要执行(高工具调用次数时跳过低优先级钩子)
//
// 原始结构 (main 函数的 async 回调内):
// heavyChecks.push(runSubHook('check-typescript.js', rawInput));
// heavyChecks.push(runSubHook('check-lint.js', rawInput));
//
// 补丁策略:
// 在 main 函数开头初始化 scheduler 和 toolCallCount
// 在每个 runSubHook 调用前包裹条件检查
// ─────────────────────────────────────────────────────────────────────────
/**
* 补丁代码: hook-priority-scheduler 初始化注入到 main async 回调开头
*/
const SCHEDULER_INIT_PATCH = ` // [v6.1-PATCH] hook-priority-scheduler
// 初始化优先级调度器,高工具调用次数时自动跳过低优先级钩子
let _toolCallCount = 0;
let _scheduler = null;
try {
_scheduler = require(require('path').join(
__dirname.replace(/[/\\\\]hooks$/, ''), 'scripts', 'hook-priority-scheduler.js'
));
_toolCallCount = _scheduler.readToolCallCount();
} catch {}
`;
/**
* 应用 post-edit-dispatcher.js 补丁
*/
function patchPostEditDispatcher() {
const filePath = path.join(HOOKS_DIR, 'post-edit-dispatcher.js');
const content = readFile(filePath);
if (!content) {
console.error('[SKIP] post-edit-dispatcher.js 读取失败');
return false;
}
const marker = PATCH_MARKERS['post-edit-dispatcher.js'];
if (isPatchApplied(content, marker)) {
console.log('[SKIP] post-edit-dispatcher.js 补丁已存在,跳过');
return true;
}
// ── 插入点: async 回调内、filePath 解析之前 ──
// 锚点: ` const filePath = (`
const ANCHOR_INIT = ' const filePath = (';
if (!content.includes(ANCHOR_INIT)) {
console.error('[ERROR] post-edit-dispatcher.js 锚点未找到: filePath 赋值行');
return false;
}
// ── 替换 TypeScript 检查的 runSubHook 调用,加入调度检查 ──
// 原始: ` heavyChecks.push(runSubHook('check-typescript.js', rawInput));`
const ORIG_TS_CHECK = ` heavyChecks.push(runSubHook('check-typescript.js', rawInput));`;
const PATCHED_TS_CHECK = ` // [v6.1-PATCH] hook-priority-scheduler: check-typescript 为 medium 优先级
if (!_scheduler || _scheduler.shouldExecute('check-typescript', _toolCallCount)) {
heavyChecks.push(runSubHook('check-typescript.js', rawInput));
}`;
// ── 替换 ESLint 检查的 runSubHook 调用,加入调度检查 ──
const ORIG_LINT_CHECK = ` heavyChecks.push(runSubHook('check-lint.js', rawInput));`;
const PATCHED_LINT_CHECK = ` // [v6.1-PATCH] hook-priority-scheduler: check-lint 为 medium 优先级
if (!_scheduler || _scheduler.shouldExecute('check-lint', _toolCallCount)) {
heavyChecks.push(runSubHook('check-lint.js', rawInput));
}`;
if (!content.includes(ORIG_TS_CHECK) || !content.includes(ORIG_LINT_CHECK)) {
console.error('[ERROR] post-edit-dispatcher.js runSubHook 调用行未找到,代码结构可能已变更');
return false;
}
let patched = content
// 1. 在 filePath 解析前注入调度器初始化
.replace(ANCHOR_INIT, SCHEDULER_INIT_PATCH + ' ' + ANCHOR_INIT.trimStart())
// 2. 包裹 TypeScript 检查
.replace(ORIG_TS_CHECK, PATCHED_TS_CHECK)
// 3. 包裹 ESLint 检查
.replace(ORIG_LINT_CHECK, PATCHED_LINT_CHECK);
if (DRY_RUN) {
console.log('[DRY-RUN] post-edit-dispatcher.js 补丁预览:');
console.log(' + hook-priority-scheduler 初始化');
console.log(' + check-typescript: 高负载时跳过medium 优先级)');
console.log(' + check-lint: 高负载时跳过medium 优先级)');
return true;
}
const ok = writeFile(filePath, patched);
if (ok) console.log('[OK] post-edit-dispatcher.js 补丁已应用');
return ok;
}
// ─── 补丁 3: route-auditor.js ─────────────────────────────────────────────
//
// 集成点: 在 writeAuditEntry 调用之后、autoLearn 检查之前,
// 当检测到路由纠正compliant === false
// 调用 adaptive-disambiguator.updateFromFeedback() 更新学习权重
//
// 原始结构 (main 函数的 stdin 'end' 回调内):
// writeAuditEntry(state, judgment);
// (回写 actualSkill)
// A/B 实验回写
// 累计检查 checkAutoLearn
// runImplicitFeedback
// cleanupState
// ─────────────────────────────────────────────────────────────────────────
/**
* 补丁代码: adaptive-disambiguator 反馈闭环
* judgment.compliant === false 说明用户实际使用了不同的技能 路由纠正
*/
const FEEDBACK_PATCH = `
// [v6.1-PATCH] adaptive-disambiguator feedback
// 反馈闭环: 检测到路由纠正时,更新 Bayesian Dirichlet 先验
// compliant===false 表示用户实际使用的技能与路由推荐不符
if (judgment.compliant === false && judgment.actualSkill && state.routing) {
try {
const adaptiveDisamb = require(require('path').join(
__dirname.replace(/[/\\\\]hooks$/, ''), 'scripts', 'adaptive-disambiguator.js'
));
const routedTo = state.routing.primary;
const actualSkill = judgment.actualSkill;
// 竞争技能: 路由时的候选列表(排除 routedTo 和 actualSkill 本身)
const competingSkills = (state.routing.candidates || [])
.map(c => c.name)
.filter(n => n !== routedTo && n !== actualSkill);
adaptiveDisamb.updateFromFeedback(routedTo, actualSkill, competingSkills);
} catch {}
}
`;
/**
* 应用 route-auditor.js 补丁
*/
function patchRouteAuditor() {
const filePath = path.join(HOOKS_DIR, 'route-auditor.js');
const content = readFile(filePath);
if (!content) {
console.error('[SKIP] route-auditor.js 读取失败');
return false;
}
const marker = PATCH_MARKERS['route-auditor.js'];
if (isPatchApplied(content, marker)) {
console.log('[SKIP] route-auditor.js 补丁已存在,跳过');
return true;
}
// 锚点: 写入审计记录后的那一行
// ` // 回写 actualSkill 到当日路由日志 (供 implicit-feedback 消费)`
const ANCHOR_FEEDBACK = ' // 回写 actualSkill 到当日路由日志 (供 implicit-feedback 消费)';
if (!content.includes(ANCHOR_FEEDBACK)) {
console.error('[ERROR] route-auditor.js 锚点未找到: 回写 actualSkill 注释行');
return false;
}
let patched = content.replace(
ANCHOR_FEEDBACK,
FEEDBACK_PATCH + '\n ' + ANCHOR_FEEDBACK.trimStart()
);
if (DRY_RUN) {
console.log('[DRY-RUN] route-auditor.js 补丁预览:');
console.log(' + adaptive-disambiguator 反馈闭环(路由纠正时更新 Bayesian 权重)');
return true;
}
const ok = writeFile(filePath, patched);
if (ok) console.log('[OK] route-auditor.js 补丁已应用');
return ok;
}
// ─── 检查模式: 报告每个文件的补丁状态 ──────────────────────────────────
function checkPatchStatus() {
console.log('=== v6.1 补丁状态检查 ===\n');
const files = [
{ file: path.join(HOOKS_DIR, 'route-interceptor.js'), key: 'route-interceptor.js' },
{ file: path.join(HOOKS_DIR, 'post-edit-dispatcher.js'), key: 'post-edit-dispatcher.js' },
{ file: path.join(HOOKS_DIR, 'route-auditor.js'), key: 'route-auditor.js' },
];
let allApplied = true;
for (const { file, key } of files) {
const content = readFile(file);
if (!content) {
console.log(` [MISSING] ${key} — 文件不存在`);
allApplied = false;
continue;
}
const marker = PATCH_MARKERS[key];
const applied = isPatchApplied(content, marker);
console.log(` [${applied ? 'APPLIED' : 'PENDING'}] ${key}`);
if (!applied) allApplied = false;
}
console.log(`\n总状态: ${allApplied ? '全部已应用' : '有待应用的补丁'}`);
return allApplied;
}
// ─── 主入口 ───────────────────────────────────────────────────────────────
function main() {
if (CHECK) {
const allOk = checkPatchStatus();
process.exit(allOk ? 0 : 1);
}
console.log(`=== v6.1 模块集成补丁${DRY_RUN ? ' (DRY-RUN)' : ''} ===\n`);
let successCount = 0;
const total = 3;
// 应用三个补丁
if (patchRouteInterceptor()) successCount++;
if (patchPostEditDispatcher()) successCount++;
if (patchRouteAuditor()) successCount++;
console.log(`\n完成: ${successCount}/${total} 个文件已处理`);
if (!DRY_RUN && successCount > 0) {
// 语法检查node -c
console.log('\n─── 语法检查 ───');
const { execFileSync } = require('child_process');
const targets = [
path.join(HOOKS_DIR, 'route-interceptor.js'),
path.join(HOOKS_DIR, 'post-edit-dispatcher.js'),
path.join(HOOKS_DIR, 'route-auditor.js'),
];
let syntaxOk = true;
for (const target of targets) {
try {
execFileSync(process.execPath, ['--check', target], {
timeout: 5000,
stdio: ['pipe', 'pipe', 'pipe'],
});
console.log(` [OK] ${path.basename(target)}`);
} catch (e) {
console.error(` [FAIL] ${path.basename(target)} — 语法错误: ${(e.stderr || e.message || '').toString().trim()}`);
syntaxOk = false;
}
}
if (!syntaxOk) {
console.error('\n[WARNING] 存在语法错误,请检查补丁代码');
process.exit(1);
}
}
process.exit(successCount === total ? 0 : 1);
}
main();