bookworm-smart-assistant/scripts/patches/patch-route-precision-10x-batch-a.js

179 lines
7.3 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
/**
* patch-route-precision-10x-batch-a.js
* 路由精度10项改进 Batch A: disambiguation-rules.json 变更
* Item 3: R27 移除 bookworm|自检 关键词
* Item 4: 新增 R90 sre-expert boost
* Item 5: 新增 R91 impact-analyst boost
* Item 7: 新增 R92 Google Sheets 数据分析再路由
* Item 8: R58 补充 evolution-tracker boost
* Item 10: 新增 R93 MCP browser consolidation
*
* 安全性: .bak 备份 + sentinel 幂等检查 + UTF-8 BOM 写入
*/
'use strict';
const fs = require('fs');
const path = require('path');
// SENTINEL: 防止重复运行
const SENTINEL = 'PATCH_ROUTE_PRECISION_10X_BATCH_A_APPLIED';
const SCRIPTS_DIR = path.join(__dirname, '..');
const RULES_FILE = path.join(SCRIPTS_DIR, 'disambiguation-rules.json');
const BAK_FILE = RULES_FILE + '.bak';
// ── 读取原文件 ─────────────────────────────────────────
if (!fs.existsSync(RULES_FILE)) {
console.error('[ERROR] disambiguation-rules.json not found:', RULES_FILE);
process.exit(1);
}
const raw = fs.readFileSync(RULES_FILE, 'utf8');
const data = JSON.parse(raw);
// ── 幂等检查 ───────────────────────────────────────────
if (data._meta && data._meta[SENTINEL]) {
console.log('[SKIP] Patch already applied (sentinel found). Nothing to do.');
process.exit(0);
}
// 另一种幂等检查: 如果 R90 已存在,也跳过
if (data.rules && data.rules.some(r => r.id === 'R90')) {
console.log('[SKIP] R90 already exists. Patch appears already applied.');
process.exit(0);
}
// ── 备份 ───────────────────────────────────────────────
fs.writeFileSync(BAK_FILE, raw, 'utf8');
console.log('[BAK] Backed up to', BAK_FILE);
// ── Item 3: R27 — 移除 bookworm 和 自检 关键词 ─────────
let r27Modified = false;
for (const rule of data.rules) {
if (rule.id === 'R27') {
const before = rule.trigger;
// 移除整个 pipe-delimited token: bookworm 和 自检
// 策略: 将 trigger 按 | 分割,过滤掉目标词,再重新 join
// 这样避免跨词的正则副作用(如 系统自检|系统健康 → 系统系统健康)
const tokens = rule.trigger.split('|');
const filtered = tokens.filter(tok => tok !== 'bookworm' && tok !== '自检');
let t = filtered.join('|');
// 清理多余的 | 分隔符(防御性)
t = t.replace(/\|{2,}/g, '|').replace(/^\||\|$/g, '');
rule.trigger = t;
r27Modified = (before !== t);
console.log('[ITEM3] R27 trigger before:', before);
console.log('[ITEM3] R27 trigger after :', t);
console.log('[ITEM3]', r27Modified ? 'MODIFIED' : 'NO_CHANGE (keywords not found)');
break;
}
}
// ── Item 8: R58 — 补充 evolution-tracker boost ─────────
let r58Modified = false;
for (const rule of data.rules) {
if (rule.id === 'R58') {
if (!rule.boost) {
rule.boost = 'evolution-tracker';
r58Modified = true;
console.log('[ITEM8] R58 added boost: evolution-tracker');
} else {
console.log('[ITEM8] R58 already has boost:', rule.boost, '— no change');
}
break;
}
}
// ── Items 4/5/7/10: 追加新规则 R90R93 ─────────────────
const newRules = [
{
id: 'R90',
note: 'SRE 专属场景 → sre-expert从 devops-expert 分离',
trigger: 'sli|slo|sla.*(?:监控|告警)|on.?call|postmortem|error.*budget|toil|事故响应|incident.*response|runbook|alert.*rule',
boost: 'sre-expert',
penalty: ['devops-expert'],
weight: 0.35,
note2: 'SRE专属场景从devops-expert中分离',
},
{
id: 'R91',
note: '变更影响分析 → impact-analyst与架构咨询消歧',
trigger: '变更影响|影响分析|影响范围|爆炸半径|依赖分析|改.*(?:会|有).*影响|change.*impact|blast.*radius|downstream.*impact|调用链.*分析|谁在.*(?:用|调用)',
boost: 'impact-analyst',
penalty: ['architect-expert', 'developer-expert'],
weight: 0.35,
note2: '变更影响分析与架构咨询消歧',
},
{
id: 'R92',
note: 'Google Sheets 数据分析 → data-analyst-expert (MCP: google-drive)',
trigger: '(?:google\\s*sheets?|谷歌表格).*(?:分析|统计|可视化|透视|图表|数据清洗|pivot)',
boost: 'data-analyst-expert',
penalty: ['developer-expert'],
weight: 0.30,
preferred_mcp: 'google-drive',
note2: 'Google Sheets数据分析场景从developer-expert分流',
},
{
id: 'R93',
note: '浏览器MCP统一路由 → browser-automation-expert (playwright为主)',
trigger: '(?:browser.?mcp|computer.?control|桌面控制).*(?:测试|自动化|操作)',
boost: 'browser-automation-expert',
penalty: [],
weight: 0.25,
preferred_mcp: 'playwright',
note2: '浏览器MCP统一路由: playwright为主, chrome-devtools为辅, browser-mcp/computer-control-mcp为备选',
},
];
for (const r of newRules) {
data.rules.push(r);
console.log(`[ITEM${r.id === 'R90' ? '4' : r.id === 'R91' ? '5' : r.id === 'R92' ? '7' : '10'}] Added rule ${r.id}`);
}
// ── 更新元数据 ──────────────────────────────────────────
const totalRules = data.rules.length;
data._meta.ruleCount = totalRules;
data._meta[SENTINEL] = true;
data._meta.patchedAt_batchA = new Date().toISOString();
// 更新 changelog
if (!data._meta.changelog) data._meta.changelog = [];
data._meta.changelog.push(
'R27: 移除 bookworm|自检 关键词 (已由 R81-R89 覆盖)',
'R58: 补充 boost: evolution-tracker',
'R90: 新增 — SRE 专属场景 sre-expert (postmortem/on-call/runbook等)',
'R91: 新增 — 变更影响分析 impact-analyst (爆炸半径/依赖分析等)',
'R92: 新增 — Google Sheets 数据分析 data-analyst-expert (preferred_mcp: google-drive)',
'R93: 新增 — 浏览器MCP统一路由 browser-automation-expert (preferred_mcp: playwright)',
);
// 更新 description 中的规则数量引用 (89条 → 实际数)
if (data._meta.description) {
data._meta.description = data._meta.description.replace(/\d+ 条/g, `${totalRules}`);
}
// ── 写入 (UTF-8 无 BOM) ────────────────────────────────
const output = JSON.stringify(data, null, 2) + '\n';
fs.writeFileSync(RULES_FILE, output, 'utf8');
console.log('\n[DONE] disambiguation-rules.json updated.');
console.log(` Total rules: ${totalRules}`);
console.log(` R27 modified: ${r27Modified}`);
console.log(` R58 modified: ${r58Modified}`);
console.log(` New rules added: R90, R91, R92, R93`);
// ── JSON 验证 ──────────────────────────────────────────
try {
const verify = JSON.parse(fs.readFileSync(RULES_FILE, 'utf8'));
console.log(`[VERIFY] JSON valid. rules.length=${verify.rules.length}, ruleCount=${verify._meta.ruleCount}`);
} catch (e) {
console.error('[VERIFY ERROR] JSON parse failed:', e.message);
// 回滚
fs.copyFileSync(BAK_FILE, RULES_FILE);
console.error('[ROLLBACK] Restored from .bak');
process.exit(1);
}