tasq/node_modules/agentic-flow/dist/mcp/fastmcp/tools/hooks/explain.js

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