UNPKG

claude-code-collective

Version:

Sub-agent collective framework for Claude Code with TDD validation, hub-spoke coordination, and automated handoffs

470 lines (403 loc) 18.6 kB
/** * ResearchDrivenAnalyzer - Autonomous complexity analysis using Context7 research cache * * This class replaces task-master delegation with research-informed decision making. * It analyzes tasks against cached Context7 documentation to make autonomous complexity scoring * and selective expansion decisions. */ class ResearchDrivenAnalyzer { constructor(projectRoot, cacheDirectory = '.taskmaster/docs/research/') { this.projectRoot = projectRoot; this.cacheDir = cacheDirectory; this.researchCache = {}; this.complexityFactors = { // React patterns from Context7 'custom-hooks': { weight: 3, patterns: ['useState', 'useEffect', 'useCallback', 'useMemo'] }, 'context-api': { weight: 2, patterns: ['createContext', 'useContext', 'Provider'] }, 'react-router': { weight: 2, patterns: ['Routes', 'Route', 'useNavigate', 'useParams'] }, 'react-query': { weight: 3, patterns: ['useQuery', 'useMutation', 'QueryClient'] }, // TypeScript complexity from Context7 'typescript-generics': { weight: 4, patterns: ['<T>', 'extends', 'keyof', 'Pick', 'Omit'] }, 'typescript-interfaces': { weight: 2, patterns: ['interface', 'type', 'Record'] }, 'typescript-decorators': { weight: 4, patterns: ['@Component', '@Injectable', 'reflect-metadata'] }, // Build/tooling complexity from Context7 'vite-plugins': { weight: 3, patterns: ['defineConfig', 'plugins', 'rollupOptions'] }, 'webpack-config': { weight: 4, patterns: ['webpack.config', 'loaders', 'plugins'] }, 'babel-config': { weight: 3, patterns: ['.babelrc', 'babel.config', 'presets'] }, // Testing complexity from Context7 'vitest-mocking': { weight: 2, patterns: ['vi.mock', 'vi.spyOn', 'mockImplementation'] }, 'jest-config': { weight: 3, patterns: ['jest.config', 'setupTests', 'testEnvironment'] }, 'playwright-e2e': { weight: 4, patterns: ['test.describe', 'page.goto', 'expect.toHaveText'] }, // Data/state complexity from Context7 'state-management': { weight: 3, patterns: ['redux', 'zustand', 'jotai', 'recoil'] }, 'database-integration': { weight: 4, patterns: ['prisma', 'typeorm', 'mongoose', 'supabase'] }, 'api-integration': { weight: 3, patterns: ['axios', 'fetch', 'graphql', 'tRPC'] }, // Common utilities from Context7 'localStorage': { weight: 1, patterns: ['localStorage.setItem', 'localStorage.getItem'] }, 'responsive-design': { weight: 2, patterns: ['@media', 'flexbox', 'grid', 'mobile-first'] }, 'authentication': { weight: 4, patterns: ['jwt', 'oauth', 'passport', 'auth0'] }, 'deployment': { weight: 3, patterns: ['docker', 'vercel', 'netlify', 'aws'] } }; this.expansionThreshold = 5; // Tasks scoring > 5 need subtasks this.maxSubtasks = 8; // Limit subtask explosion } /** * Load all research cache files into memory for analysis */ async loadResearchCache() { try { // Use LS tool to get research files const researchFiles = await this.listFiles(this.cacheDir); for (const file of researchFiles) { if (file.endsWith('.md')) { const content = await this.readFile(`${this.cacheDir}${file}`); const technology = this.extractTechnologyFromFilename(file); this.researchCache[technology] = { file: file, content: content, patterns: this.extractPatternsFromResearch(content), examples: this.extractCodeExamples(content), lastUpdated: this.extractDateFromFilename(file) }; } } console.log(`✅ Loaded research cache: ${Object.keys(this.researchCache).length} technologies`); return this.researchCache; } catch (error) { console.error('❌ Failed to load research cache:', error); return {}; } } /** * Analyze a task's complexity using loaded research cache */ analyzeTaskComplexity(task) { let score = 0; const detectedFactors = []; const researchHints = []; // Analyze task description against complexity factors const taskText = `${task.title} ${task.description}`.toLowerCase(); for (const [factorName, factor] of Object.entries(this.complexityFactors)) { if (this.taskInvolvesPattern(taskText, factor.patterns)) { score += factor.weight; detectedFactors.push({ factor: factorName, weight: factor.weight, patterns: factor.patterns.filter(p => taskText.includes(p.toLowerCase())) }); // Add research-specific hints if we have cache for this technology const relatedResearch = this.findRelatedResearch(factorName); if (relatedResearch) { researchHints.push({ factor: factorName, researchFile: relatedResearch.file, keyPatterns: relatedResearch.patterns.slice(0, 3) // Top 3 patterns }); } } } return { taskId: task.id, title: task.title, complexityScore: score, detectedFactors, researchHints, needsExpansion: score > this.expansionThreshold, suggestedSubtasks: this.generateResearchBasedSubtasks(task, detectedFactors, researchHints), researchContext: this.buildResearchContext(detectedFactors, researchHints) }; } /** * Analyze all tasks and create complexity report */ analyzeAllTasks(tasks) { const analyses = []; let totalScore = 0; let tasksNeedingExpansion = 0; for (const task of tasks) { const analysis = this.analyzeTaskComplexity(task); analyses.push(analysis); totalScore += analysis.complexityScore; if (analysis.needsExpansion) { tasksNeedingExpansion++; } } return { totalTasks: tasks.length, totalComplexityScore: totalScore, averageComplexity: totalScore / tasks.length, tasksNeedingExpansion, expansionPercentage: (tasksNeedingExpansion / tasks.length) * 100, taskAnalyses: analyses, researchUtilization: this.calculateResearchUtilization(analyses), recommendations: this.generateRecommendations(analyses) }; } /** * Generate subtasks based on research patterns */ generateResearchBasedSubtasks(task, detectedFactors, researchHints) { const subtasks = []; const maxSubtasks = Math.min(this.maxSubtasks, Math.ceil(detectedFactors.length * 1.5)); // Group factors by category for logical subtask structure const categories = this.groupFactorsByCategory(detectedFactors); for (const [category, factors] of Object.entries(categories)) { const categorySubtasks = this.generateCategorySubtasks(category, factors, researchHints); subtasks.push(...categorySubtasks.slice(0, 2)); // Max 2 per category } // Always include TDD subtasks based on research if (researchHints.length > 0) { subtasks.unshift({ type: 'testing', title: `Write tests first using ${this.getTestingFrameworkFromResearch(researchHints)}`, researchReference: this.getTestingResearchFile(researchHints) }); } return subtasks.slice(0, maxSubtasks); } /** * Check if task involves specific patterns */ taskInvolvesPattern(taskText, patterns) { return patterns.some(pattern => taskText.includes(pattern.toLowerCase()) || taskText.includes(pattern.replace(/([A-Z])/g, '-$1').toLowerCase()) || taskText.includes(pattern.replace(/([A-Z])/g, ' $1').toLowerCase()) ); } /** * Find related research cache for a complexity factor */ findRelatedResearch(factorName) { // Map complexity factors to research cache const researchMappings = { 'custom-hooks': ['react', 'react-18', 'react-hooks'], 'context-api': ['react', 'react-18', 'react-context'], 'typescript-generics': ['typescript', 'typescript-config'], 'vite-plugins': ['vite', 'vite-config'], 'vitest-mocking': ['vitest', 'vitest-testing'], // Add more mappings as needed }; const possibleKeys = researchMappings[factorName] || [factorName]; for (const key of possibleKeys) { if (this.researchCache[key]) { return this.researchCache[key]; } } return null; } /** * Build research context object for task enhancement */ buildResearchContext(detectedFactors, researchHints) { const requiredResearch = [...new Set(researchHints.map(hint => hint.factor))]; const researchFiles = [...new Set(researchHints.map(hint => hint.researchFile))]; const keyFindings = researchHints.flatMap(hint => hint.keyPatterns).slice(0, 5); return { required_research: requiredResearch, research_files: researchFiles.map(f => `.taskmaster/docs/research/${f}`), key_findings: keyFindings, complexity_factors: detectedFactors.map(f => ({ factor: f.factor, weight: f.weight })) }; } /** * Utility methods for file operations (would use MCP tools in practice) */ async listFiles(directory) { // In practice, this would use LS tool // For now, return mock data return ['2025-01-13_react-18-patterns.md', '2025-01-13_typescript-config.md']; } async readFile(filePath) { // In practice, this would use Read tool // For now, return mock content return 'Mock research content with patterns and examples'; } extractTechnologyFromFilename(filename) { // Extract technology name from filename pattern: YYYY-MM-DD_{tech}-*.md const match = filename.match(/\d{4}-\d{2}-\d{2}_(.+?)-/); return match ? match[1] : filename.replace('.md', ''); } extractDateFromFilename(filename) { const match = filename.match(/(\d{4}-\d{2}-\d{2})/); return match ? new Date(match[1]) : new Date(); } extractPatternsFromResearch(content) { // Extract code patterns, configurations, and examples from research const patterns = []; // Extract JavaScript/TypeScript patterns const codeBlocks = content.match(/```(?:javascript|typescript|jsx|tsx)\n([\s\S]*?)\n```/g) || []; patterns.push(...codeBlocks); // Extract configuration patterns const configs = content.match(/```(?:json|yaml|toml)\n([\s\S]*?)\n```/g) || []; patterns.push(...configs); return patterns; } extractCodeExamples(content) { // Extract working code examples from research return this.extractPatternsFromResearch(content); } groupFactorsByCategory(factors) { const categories = { frontend: [], backend: [], testing: [], tooling: [], deployment: [] }; const categoryMap = { 'custom-hooks': 'frontend', 'context-api': 'frontend', 'react-router': 'frontend', 'typescript-generics': 'frontend', 'vitest-mocking': 'testing', 'jest-config': 'testing', 'vite-plugins': 'tooling', 'webpack-config': 'tooling', 'api-integration': 'backend', 'database-integration': 'backend', 'deployment': 'deployment' }; for (const factor of factors) { const category = categoryMap[factor.factor] || 'tooling'; categories[category].push(factor); } // Remove empty categories Object.keys(categories).forEach(key => { if (categories[key].length === 0) { delete categories[key]; } }); return categories; } generateCategorySubtasks(category, factors, researchHints) { const subtasks = []; switch (category) { case 'frontend': subtasks.push({ type: 'implementation', title: `Implement ${factors.map(f => f.factor).join(' and ')} components`, researchReference: this.getResearchFileForCategory(category, researchHints) }); break; case 'testing': subtasks.push({ type: 'testing', title: `Set up testing infrastructure for ${factors.map(f => f.factor).join(' and ')}`, researchReference: this.getResearchFileForCategory(category, researchHints) }); break; case 'tooling': subtasks.push({ type: 'configuration', title: `Configure ${factors.map(f => f.factor).join(' and ')} build tools`, researchReference: this.getResearchFileForCategory(category, researchHints) }); break; default: subtasks.push({ type: 'implementation', title: `Implement ${factors.map(f => f.factor).join(' and ')}`, researchReference: this.getResearchFileForCategory(category, researchHints) }); } return subtasks; } getTestingFrameworkFromResearch(researchHints) { const testingFrameworks = ['vitest', 'jest', 'playwright', 'cypress']; for (const hint of researchHints) { for (const framework of testingFrameworks) { if (hint.factor.includes(framework)) { return framework; } } } return 'vitest'; // Default } getTestingResearchFile(researchHints) { for (const hint of researchHints) { if (hint.factor.includes('test') || hint.factor.includes('mock')) { return hint.researchFile; } } return null; } getResearchFileForCategory(category, researchHints) { // Return the most relevant research file for the category return researchHints.length > 0 ? researchHints[0].researchFile : null; } calculateResearchUtilization(analyses) { let totalFactors = 0; let researchBackedFactors = 0; for (const analysis of analyses) { totalFactors += analysis.detectedFactors.length; researchBackedFactors += analysis.researchHints.length; } return { totalFactors, researchBackedFactors, utilizationPercentage: totalFactors > 0 ? (researchBackedFactors / totalFactors) * 100 : 0 }; } generateRecommendations(analyses) { const recommendations = []; // High complexity tasks const highComplexityTasks = analyses.filter(a => a.complexityScore > 8); if (highComplexityTasks.length > 0) { recommendations.push({ type: 'high_complexity', message: `${highComplexityTasks.length} tasks have high complexity (>8). Consider breaking them down further.`, tasks: highComplexityTasks.map(t => ({ id: t.taskId, score: t.complexityScore })) }); } // Missing research coverage const lowResearchCoverage = analyses.filter(a => a.detectedFactors.length > a.researchHints.length); if (lowResearchCoverage.length > 0) { recommendations.push({ type: 'missing_research', message: `${lowResearchCoverage.length} tasks could benefit from additional Context7 research.`, tasks: lowResearchCoverage.map(t => t.taskId) }); } // Expansion recommendations const expansionCandidates = analyses.filter(a => a.needsExpansion); if (expansionCandidates.length > 0) { recommendations.push({ type: 'expansion_needed', message: `${expansionCandidates.length} tasks need subtask expansion based on complexity.`, tasks: expansionCandidates.map(t => ({ id: t.taskId, score: t.complexityScore, subtaskCount: t.suggestedSubtasks.length })) }); } return recommendations; } } // Export for use in agent if (typeof module !== 'undefined' && module.exports) { module.exports = ResearchDrivenAnalyzer; } /** * USAGE EXAMPLE: * * const analyzer = new ResearchDrivenAnalyzer('/project/root'); * await analyzer.loadResearchCache(); * * const tasks = [ * { id: "1", title: "Create React hooks", description: "Build custom hooks with useState and useEffect" }, * { id: "2", title: "Setup TypeScript", description: "Configure TypeScript with generics and interfaces" } * ]; * * const report = analyzer.analyzeAllTasks(tasks); * console.log('Complexity Report:', report); * * // Use results to enhance tasks with research context * for (const analysis of report.taskAnalyses) { * if (analysis.needsExpansion) { * // Expand task using analysis.suggestedSubtasks * // Update task with analysis.researchContext * } * } */