#!/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: 追加新规则 R90–R93 ───────────────── 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); }