164 lines
7.1 KiB
JavaScript
164 lines
7.1 KiB
JavaScript
/**
|
|
* Explain Hook - Explain routing decisions with full transparency
|
|
*/
|
|
import { z } from 'zod';
|
|
import * as path from 'path';
|
|
import { loadIntelligence, getAgentForFile, simpleEmbed, cosineSimilarity } from './shared.js';
|
|
export const hookExplainTool = {
|
|
name: 'hook_explain',
|
|
description: 'Explain why an agent would be selected for a task',
|
|
parameters: z.object({
|
|
task: z.string().describe('Task description'),
|
|
file: z.string().optional().describe('Optional file context'),
|
|
agent: z.string().optional().describe('Specific agent to explain selection for')
|
|
}),
|
|
execute: async ({ task, file, agent }, { onProgress }) => {
|
|
const startTime = Date.now();
|
|
const intel = loadIntelligence();
|
|
// Collect all reasoning
|
|
const reasons = [];
|
|
// All agent scores
|
|
const allScores = {};
|
|
// 1. File pattern analysis
|
|
if (file) {
|
|
const ext = path.extname(file);
|
|
const suggestedAgent = getAgentForFile(file);
|
|
reasons.push({
|
|
factor: 'File Extension',
|
|
weight: 2.0,
|
|
evidence: `${ext} files are typically handled by ${suggestedAgent}`,
|
|
contributes: agent === suggestedAgent ? 'positive' : 'neutral'
|
|
});
|
|
allScores[suggestedAgent] = (allScores[suggestedAgent] || 0) + 2.0;
|
|
// Learned patterns
|
|
const state = `edit:${ext}`;
|
|
if (intel.patterns[state]) {
|
|
const patterns = intel.patterns[state];
|
|
const sortedPatterns = Object.entries(patterns)
|
|
.sort((a, b) => b[1] - a[1]);
|
|
for (const [patternAgent, value] of sortedPatterns.slice(0, 3)) {
|
|
if (typeof value === 'number') {
|
|
allScores[patternAgent] = (allScores[patternAgent] || 0) + value;
|
|
reasons.push({
|
|
factor: 'Learned Pattern',
|
|
weight: value,
|
|
evidence: `Historical Q-value of ${value.toFixed(2)} for ${patternAgent} on ${ext} files`,
|
|
contributes: agent === patternAgent
|
|
? (value > 0 ? 'positive' : 'negative')
|
|
: 'neutral'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// 2. Task keyword analysis
|
|
const taskLower = task.toLowerCase();
|
|
const keywordMatches = [];
|
|
const keywordMap = {
|
|
'test-engineer': ['test', 'spec', 'coverage'],
|
|
'documentation-specialist': ['document', 'readme', 'docs'],
|
|
'security-specialist': ['security', 'auth', 'vulnerability'],
|
|
'database-specialist': ['database', 'sql', 'query'],
|
|
'optimizer': ['optimize', 'performance', 'speed'],
|
|
'researcher': ['research', 'find', 'explore']
|
|
};
|
|
for (const [agentType, keywords] of Object.entries(keywordMap)) {
|
|
for (const keyword of keywords) {
|
|
if (taskLower.includes(keyword)) {
|
|
keywordMatches.push(keyword);
|
|
allScores[agentType] = (allScores[agentType] || 0) + 1.5;
|
|
reasons.push({
|
|
factor: 'Keyword Match',
|
|
weight: 1.5,
|
|
evidence: `Task mentions "${keyword}" → suggests ${agentType}`,
|
|
contributes: agent === agentType ? 'positive' : 'neutral'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
// 3. Memory similarity analysis
|
|
if (intel.memories.length > 0) {
|
|
const taskEmbed = simpleEmbed(task);
|
|
const similarMemories = [];
|
|
for (const mem of intel.memories) {
|
|
if (mem.embedding) {
|
|
const similarity = cosineSimilarity(taskEmbed, mem.embedding);
|
|
if (similarity > 0.3) {
|
|
similarMemories.push({
|
|
content: mem.content.slice(0, 100),
|
|
similarity
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if (similarMemories.length > 0) {
|
|
similarMemories.sort((a, b) => b.similarity - a.similarity);
|
|
const topMemory = similarMemories[0];
|
|
reasons.push({
|
|
factor: 'Memory Similarity',
|
|
weight: topMemory.similarity,
|
|
evidence: `Similar past task: "${topMemory.content}..."`,
|
|
contributes: 'positive'
|
|
});
|
|
}
|
|
}
|
|
// 4. Error pattern analysis
|
|
const relevantErrors = intel.errorPatterns.filter(ep => task.toLowerCase().includes(ep.errorType.toLowerCase()) ||
|
|
ep.context.toLowerCase().includes(task.toLowerCase().split(' ')[0]));
|
|
if (relevantErrors.length > 0) {
|
|
for (const ep of relevantErrors.slice(0, 2)) {
|
|
const bestAgent = Object.entries(ep.agentSuccess)
|
|
.sort((a, b) => b[1] - a[1])[0];
|
|
if (bestAgent) {
|
|
reasons.push({
|
|
factor: 'Error History',
|
|
weight: 1.0,
|
|
evidence: `${bestAgent[0]} has fixed ${ep.errorType} ${bestAgent[1]} times`,
|
|
contributes: agent === bestAgent[0] ? 'positive' : 'neutral'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
// 5. Calculate final scores and ranking
|
|
const ranking = Object.entries(allScores)
|
|
.sort((a, b) => b[1] - a[1])
|
|
.map(([agentName, score], index) => ({
|
|
rank: index + 1,
|
|
agent: agentName,
|
|
score,
|
|
isRequested: agent === agentName
|
|
}));
|
|
// 6. Generate explanation summary
|
|
const topAgent = ranking[0];
|
|
const requestedAgentRank = agent
|
|
? ranking.find(r => r.agent === agent)
|
|
: null;
|
|
let summary;
|
|
if (!agent) {
|
|
summary = `${topAgent?.agent || 'coder'} is recommended with score ${topAgent?.score.toFixed(2) || 0}`;
|
|
}
|
|
else if (requestedAgentRank?.rank === 1) {
|
|
summary = `${agent} is the top choice with score ${requestedAgentRank.score.toFixed(2)}`;
|
|
}
|
|
else if (requestedAgentRank) {
|
|
summary = `${agent} ranks #${requestedAgentRank.rank} with score ${requestedAgentRank.score.toFixed(2)}. ${topAgent?.agent} is preferred.`;
|
|
}
|
|
else {
|
|
summary = `${agent} has no specific advantages for this task. Consider ${topAgent?.agent || 'coder'}.`;
|
|
}
|
|
const latency = Date.now() - startTime;
|
|
return {
|
|
success: true,
|
|
summary,
|
|
reasons,
|
|
ranking: ranking.slice(0, 5),
|
|
requestedAgent: agent || null,
|
|
recommendedAgent: topAgent?.agent || 'coder',
|
|
memoryCount: intel.memories.length,
|
|
patternCount: Object.keys(intel.patterns).length,
|
|
latencyMs: latency,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
}
|
|
};
|
|
//# sourceMappingURL=explain.js.map
|