UNPKG

@pimzino/agentic-tools-mcp

Version:

A comprehensive MCP server for task management and agent memories with JSON file storage

320 lines (312 loc) • 13.8 kB
import { z } from 'zod'; import { readdir, readFile, stat } from 'fs/promises'; import { join, extname } from 'path'; /** * Infer task completion status by analyzing the codebase for implementation evidence * This tool implements intelligent progress inference from code analysis */ export function createProgressInferenceTool(storage, getWorkingDirectoryDescription, config) { return { name: 'infer_task_progress_Agentic_Tools', description: 'Analyze the codebase to infer which tasks appear to be completed based on code changes, file creation, and implementation evidence. Intelligent progress inference feature for automatic task completion tracking.', inputSchema: z.object({ workingDirectory: z.string().describe(getWorkingDirectoryDescription(config)), projectId: z.string().optional().describe('Filter analysis to a specific project'), scanDepth: z.number().min(1).max(5).optional().default(3).describe('Directory depth to scan for code files'), fileExtensions: z.array(z.string()).optional().default(['.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.cs', '.go', '.rs']).describe('File extensions to analyze'), autoUpdateTasks: z.boolean().optional().default(false).describe('Whether to automatically update task status based on inference'), confidenceThreshold: z.number().min(0).max(1).optional().default(0.7).describe('Confidence threshold for auto-updating tasks (0-1)') }), handler: async (args) => { try { const { workingDirectory, projectId, scanDepth, fileExtensions, autoUpdateTasks, confidenceThreshold } = args; // Get tasks to analyze let tasksToAnalyze = []; if (projectId) { tasksToAnalyze = await storage.getTasks(projectId); } else { const projects = await storage.getProjects(); for (const project of projects) { const projectTasks = await storage.getTasks(project.id); tasksToAnalyze.push(...projectTasks); } } // Filter out already completed tasks const incompleteTasks = tasksToAnalyze.filter(task => !task.completed && task.status !== 'done'); if (incompleteTasks.length === 0) { return { content: [{ type: 'text', text: projectId ? `No incomplete tasks found in the specified project.` : `No incomplete tasks found across all projects.` }] }; } // Scan codebase const codebaseFiles = await scanCodebase(workingDirectory, scanDepth, fileExtensions); // Analyze each task for completion evidence const analysisResults = await analyzeTaskCompletion(incompleteTasks, codebaseFiles, workingDirectory); // Auto-update tasks if requested let updatedTasks = []; if (autoUpdateTasks) { updatedTasks = await autoUpdateTaskStatus(storage, analysisResults, confidenceThreshold); } // Generate progress inference report const report = generateProgressInferenceReport(analysisResults, updatedTasks, autoUpdateTasks, confidenceThreshold); return { content: [{ type: 'text', text: report }] }; } catch (error) { return { content: [{ type: 'text', text: `Error inferring task progress: ${error instanceof Error ? error.message : 'Unknown error'}` }], isError: true }; } } }; } /** * Scan codebase for relevant files */ async function scanCodebase(workingDirectory, maxDepth, extensions) { const files = []; async function scanDirectory(dirPath, currentDepth) { if (currentDepth > maxDepth) return; try { const entries = await readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = join(dirPath, entry.name); // Skip common directories to ignore if (entry.isDirectory()) { if (!['node_modules', '.git', 'dist', 'build', '.next', 'coverage'].includes(entry.name)) { await scanDirectory(fullPath, currentDepth + 1); } } else if (entry.isFile()) { const ext = extname(entry.name); if (extensions.includes(ext)) { try { const stats = await stat(fullPath); const content = await readFile(fullPath, 'utf-8'); files.push({ path: fullPath.replace(workingDirectory, '').replace(/^[\/\\]/, ''), content, lastModified: stats.mtime, size: stats.size }); } catch (error) { // Skip files that can't be read continue; } } } } } catch (error) { // Skip directories that can't be read return; } } await scanDirectory(workingDirectory, 0); return files; } /** * Analyze tasks for completion evidence in codebase */ async function analyzeTaskCompletion(tasks, codebaseFiles, workingDirectory) { const results = []; for (const task of tasks) { const analysis = analyzeTaskEvidence(task, codebaseFiles); results.push(analysis); } return results; } /** * Analyze evidence for a specific task */ function analyzeTaskEvidence(task, codebaseFiles) { const evidence = []; let confidence = 0; // Extract keywords from task name and details const taskKeywords = extractTaskKeywords(task); // Analyze each file for task-related content for (const file of codebaseFiles) { const fileEvidence = analyzeFileForTask(file, taskKeywords, task); evidence.push(...fileEvidence.evidence); confidence += fileEvidence.confidence; } // Normalize confidence (0-1 scale) confidence = Math.min(1, confidence / 10); // Determine suggested status let suggestedStatus = 'pending'; if (confidence >= 0.8) { suggestedStatus = 'done'; } else if (confidence >= 0.4) { suggestedStatus = 'in-progress'; } // Generate reasoning const reasoning = generateTaskReasoning(task, evidence, confidence, suggestedStatus); return { task, confidence, evidence, suggestedStatus, reasoning }; } /** * Extract relevant keywords from task */ function extractTaskKeywords(task) { const text = (task.name + ' ' + task.details).toLowerCase(); const keywords = []; // Extract nouns and important terms const words = text.split(/\s+/); for (const word of words) { if (word.length > 3 && !['the', 'and', 'for', 'with', 'this', 'that', 'from', 'they', 'have', 'will'].includes(word)) { keywords.push(word); } } // Add task tags if (task.tags) { keywords.push(...task.tags); } return [...new Set(keywords)]; // Remove duplicates } /** * Analyze a file for task-related evidence */ function analyzeFileForTask(file, taskKeywords, task) { const evidence = []; let confidence = 0; const content = file.content.toLowerCase(); // Check for keyword matches const keywordMatches = taskKeywords.filter(keyword => content.includes(keyword)); if (keywordMatches.length > 0) { evidence.push(`File ${file.path} contains ${keywordMatches.length} task-related keywords: ${keywordMatches.slice(0, 3).join(', ')}`); confidence += keywordMatches.length * 0.5; } // Check for implementation patterns const implementationPatterns = [ /function\s+\w*${taskKeywords.join('|')}\w*/gi, /class\s+\w*${taskKeywords.join('|')}\w*/gi, /const\s+\w*${taskKeywords.join('|')}\w*/gi, /export\s+.*${taskKeywords.join('|')}/gi ]; for (const pattern of implementationPatterns) { const matches = content.match(pattern); if (matches && matches.length > 0) { evidence.push(`File ${file.path} contains implementation patterns: ${matches.slice(0, 2).join(', ')}`); confidence += matches.length * 1.0; } } // Check for test files if (file.path.includes('test') || file.path.includes('spec')) { const testMatches = taskKeywords.filter(keyword => content.includes(keyword)); if (testMatches.length > 0) { evidence.push(`Test file ${file.path} contains task-related tests`); confidence += 2.0; // Tests are strong evidence of completion } } // Check file modification time (recent changes suggest active work) const daysSinceModified = (Date.now() - file.lastModified.getTime()) / (1000 * 60 * 60 * 24); if (daysSinceModified < 7 && keywordMatches.length > 0) { evidence.push(`File ${file.path} was recently modified (${Math.round(daysSinceModified)} days ago)`); confidence += 1.0; } return { evidence, confidence }; } /** * Generate reasoning for task analysis */ function generateTaskReasoning(task, evidence, confidence, suggestedStatus) { if (evidence.length === 0) { return `No evidence found in codebase for task "${task.name}". Task appears not started.`; } const confidencePercent = Math.round(confidence * 100); return `Analysis of task "${task.name}" shows ${confidencePercent}% confidence of ${suggestedStatus} status. Evidence: ${evidence.slice(0, 3).join('; ')}${evidence.length > 3 ? ` and ${evidence.length - 3} more indicators` : ''}.`; } /** * Auto-update task status based on analysis */ async function autoUpdateTaskStatus(storage, analysisResults, threshold) { const updatedTasks = []; for (const result of analysisResults) { if (result.confidence >= threshold && result.suggestedStatus !== 'pending') { try { const updates = { status: result.suggestedStatus }; if (result.suggestedStatus === 'done') { updates.completed = true; } const updatedTask = await storage.updateTask(result.task.id, updates); if (updatedTask) { updatedTasks.push(updatedTask); } } catch (error) { // Continue with other tasks if one fails continue; } } } return updatedTasks; } /** * Generate progress inference report */ function generateProgressInferenceReport(analysisResults, updatedTasks, autoUpdated, threshold) { let report = `šŸ” **Task Progress Inference Report** šŸ“Š **Analysis Summary:** • Tasks analyzed: ${analysisResults.length} • High confidence completions: ${analysisResults.filter(r => r.confidence >= 0.8).length} • Likely in-progress: ${analysisResults.filter(r => r.confidence >= 0.4 && r.confidence < 0.8).length} • No evidence found: ${analysisResults.filter(r => r.confidence < 0.4).length} `; if (autoUpdated && updatedTasks.length > 0) { report += `āœ… **Auto-updated ${updatedTasks.length} tasks** based on codebase analysis (confidence ≄${Math.round(threshold * 100)}%) `; } // Group results by confidence level const highConfidence = analysisResults.filter(r => r.confidence >= 0.8); const mediumConfidence = analysisResults.filter(r => r.confidence >= 0.4 && r.confidence < 0.8); const lowConfidence = analysisResults.filter(r => r.confidence < 0.4); if (highConfidence.length > 0) { report += `šŸŽÆ **High Confidence Completions (≄80%):** ${highConfidence.map(r => `• **${r.task.name}** (${Math.round(r.confidence * 100)}% confidence) Status: ${r.suggestedStatus} ${autoUpdated && updatedTasks.some(t => t.id === r.task.id) ? 'āœ… Updated' : ''} ${r.reasoning}`).join('\n\n')} `; } if (mediumConfidence.length > 0) { report += `šŸ”„ **Likely In Progress (40-79%):** ${mediumConfidence.map(r => `• **${r.task.name}** (${Math.round(r.confidence * 100)}% confidence) ${r.reasoning}`).join('\n\n')} `; } if (lowConfidence.length > 0) { report += `ā“ **No Clear Evidence (<40%):** ${lowConfidence.slice(0, 5).map(r => `• **${r.task.name}** (${Math.round(r.confidence * 100)}% confidence)`).join('\n')}${lowConfidence.length > 5 ? `\n... and ${lowConfidence.length - 5} more` : ''} `; } report += `šŸ’” **Recommendations:** 1. Review high-confidence completions and mark as done if accurate 2. Check in-progress tasks for recent activity 3. Consider updating task descriptions to include more specific keywords 4. Run this analysis regularly to track progress automatically āš ļø **Note:** This analysis is based on code patterns and keywords. Manual verification is recommended for important decisions.`; return report; }