#!/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; } // 先插 B(adaptive disambiguator),再插 A(embedding) // 这样两次字符串替换都基于不同的锚点,不会互相干扰 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();