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

483 lines
19 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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();