bookworm-smart-assistant/scripts/route-state.js

160 lines
5.7 KiB
JavaScript
Raw Normal View History

'use strict';
/**
* 路由状态写入 writeRouteState + appendRouteLog + 遥测
* @module scripts/route-state
*
* route-interceptor-bundle.js 提取 (Phase 0 宪法合规拆分)
* 原始位置: bundle L486-599
*/
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { safeAppendJsonl } = require('../hooks/lib/safe-append.js');
const CLAUDE_ROOT = require('../hooks/lib/root.js');
const DEBUG_DIR = path.join(CLAUDE_ROOT, 'debug');
const SCRIPTS_DIR = path.join(CLAUDE_ROOT, 'scripts');
const { MUST_INVOKE_EXEMPT_INTENTS } = require('./bwr-builder.js');
// 日志脱敏
const sanitizePrompt = (() => {
try { return require('./sanitize.js').sanitize; }
catch { return (text) => text || ''; }
})();
// 安全加载模块
function safeRequire(modulePath) {
try { return require(modulePath); } catch { return null; }
}
/**
* 追加路由决策到每日路由日志
* @param {string} prompt - 用户输入
* @param {object} routing - 路由结果
* @param {string} traceId - 追踪 ID
*/
function appendRouteLog(prompt, routing, traceId) {
try {
if (prompt && prompt.includes('<task-notification>')) return;
const dateStr = new Date().toISOString().slice(0, 10);
const logFile = path.join(DEBUG_DIR, 'route-' + dateStr + '.jsonl');
const entry = {
ts: new Date().toISOString(),
traceId,
query: sanitizePrompt((prompt || '').slice(0, 200)),
topResult: routing.primary,
topConfidence: routing.confidence,
candidates: (routing.candidates || []).slice(0, 3).map(c => c.name),
};
safeAppendJsonl(logFile, entry);
// 低置信度 developer-expert 回退日志
if (routing.primary === 'developer-expert' && routing.confidence < 0.4) {
const blindFile = path.join(DEBUG_DIR, 'route-blind-spots.jsonl');
safeAppendJsonl(blindFile, {
ts: entry.ts, traceId,
query: entry.query,
confidence: routing.confidence,
candidates: (routing.candidates || []).slice(0, 5).map(c => ({ n: c.name, c: Math.round((c.confidence || 0) * 100) })),
});
}
// 每日路由统计
try {
const dailyStatsFile = path.join(DEBUG_DIR, `route-stats-daily-${dateStr}.jsonl`);
const complexity = routing._complexity || 'unknown';
const isExempt = (routing._intents || []).some(i => MUST_INVOKE_EXEMPT_INTENTS.has(i));
const mustInvoke = complexity === 'complex' ||
(!isExempt && complexity === 'medium' && routing.confidence >= 0.5 &&
routing.primary !== 'developer-expert' && routing.primary !== 'none');
safeAppendJsonl(dailyStatsFile, {
ts: entry.ts, date: dateStr,
skill: routing.primary,
confidence: routing.confidence,
mustInvoke,
});
} catch {}
} catch {}
}
/**
* 写入路由状态文件 (供下游 hook 消费)
* @param {string} traceId - 追踪 ID
* @param {string} prompt - 用户输入
* @param {object} intent - 意图分类结果
* @param {object} routing - 路由结果
* @param {string|null} sessionId - 会话 ID
* @returns {object} 写入的状态对象
*/
function writeRouteState(traceId, prompt, intent, routing, sessionId) {
const state = {
traceId,
ts: new Date().toISOString(),
promptHash: crypto.createHash('sha256').update(prompt).digest('hex').slice(0, 12),
promptRaw: sanitizePrompt(prompt.slice(0, 200)),
intent: {
intents: intent.intents,
modifiers: intent.modifiers,
entities: intent.entities,
complexity: intent.complexity,
},
routing: {
primary: routing.primary,
candidates: routing.candidates,
confidence: routing.confidence,
chain: routing.chain,
experiment: routing.experiment || null,
domain: routing.domain || null,
},
recommendation: {
action: routing.confidence >= 0.8 ? 'route' : routing.confidence >= 0.5 ? 'recommend' : 'fallback',
skill: routing.primary,
},
mustInvoke: intent.complexity === 'complex' ||
(!intent.intents.some(i => MUST_INVOKE_EXEMPT_INTENTS.has(i)) &&
intent.complexity === 'medium' && routing.confidence >= 0.5 &&
routing.primary !== 'developer-expert' && routing.primary !== 'none'),
version: (() => { try { return require('../feature-flags.json').version; } catch { return 'v6.2'; } })(),
sessionId: sessionId || null,
};
try {
if (!fs.existsSync(DEBUG_DIR)) fs.mkdirSync(DEBUG_DIR, { recursive: true });
const _tmpState = path.join(DEBUG_DIR, 'route-state-current.json.tmp.' + process.pid);
fs.writeFileSync(_tmpState, JSON.stringify(state, null, 2) + '\n');
fs.renameSync(_tmpState, path.join(DEBUG_DIR, 'route-state-current.json'));
} catch {}
// 注入 intent 信息供 appendRouteLog 统一计算 mustInvoke
routing._complexity = intent.complexity;
routing._intents = intent.intents;
appendRouteLog(prompt, routing, traceId);
// 路由遥测指标
try {
const telemetry = safeRequire(path.join(SCRIPTS_DIR, 'route-telemetry.js'));
if (telemetry && telemetry.emitRouteMetric) {
telemetry.emitRouteMetric({
queryLength: (prompt || '').split(/\s+/).length,
selectedSkill: routing.primary,
topScore: routing.candidates?.[0]?.confidence || 0,
gap12: (routing.candidates?.[0]?.confidence || 0) - (routing.candidates?.[1]?.confidence || 0),
confidence: routing.confidence,
rulesFired: routing._firedRules || [],
coldStartApplied: routing._coldStartApplied || false,
coldStartSkills: routing._coldStartSkills || [],
latencyMs: Date.now() - (routing._startTs || Date.now()),
experimentId: routing.experiment?.id || null,
});
}
} catch {}
return state;
}
module.exports = { writeRouteState, appendRouteLog };