UNPKG

task-master-neo-sdlc

Version:

Enhanced task management system with Neo SDLC agents and MCP tools for comprehensive, AI-driven software development lifecycle management.

419 lines (362 loc) 12 kB
/** * QA Agent Helper Methods * * Additional methods for the QA Agent class. */ import { log } from '../../../utils/logging.js'; import fs from 'fs'; import path from 'path'; import { execSync } from 'child_process'; /** * Determine project type based on files and dependencies * @param {string} projectRoot - Project root directory * @returns {string} Project type (react, vue, angular, node, etc.) */ export function determineProjectType(projectRoot) { try { // Check package.json const packageJsonPath = path.join(projectRoot, 'package.json'); if (fs.existsSync(packageJsonPath)) { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies }; // Check for React if (dependencies.react) { return 'react'; } // Check for Vue if (dependencies.vue) { return 'vue'; } // Check for Angular if (dependencies['@angular/core']) { return 'angular'; } // Check for Next.js if (dependencies.next) { return 'nextjs'; } // Check for Express if (dependencies.express) { return 'express'; } // Check for NestJS if (dependencies['@nestjs/core']) { return 'nestjs'; } } // Check for specific files if (fs.existsSync(path.join(projectRoot, 'angular.json'))) { return 'angular'; } if (fs.existsSync(path.join(projectRoot, 'vue.config.js'))) { return 'vue'; } if (fs.existsSync(path.join(projectRoot, 'next.config.js'))) { return 'nextjs'; } // Default to node return 'node'; } catch (error) { log.warn(`Error determining project type: ${error.message}`); return 'node'; } } /** * Ensure test framework is installed * @param {string} framework - Test framework to install * @param {string} projectRoot - Project root directory * @returns {Promise<boolean>} Whether installation was successful */ export async function ensureTestFrameworkInstalled(framework, projectRoot) { try { // Check if framework is already installed const packageJsonPath = path.join(projectRoot, 'package.json'); if (fs.existsSync(packageJsonPath)) { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies }; // Check if framework is already installed if (dependencies[framework]) { log.info(`Test framework ${framework} is already installed`); return true; } // Check for framework-specific packages switch (framework) { case 'jest': if (dependencies.jest) return true; break; case 'vitest': if (dependencies.vitest) return true; break; case 'mocha': if (dependencies.mocha) return true; break; case 'cypress': if (dependencies.cypress) return true; break; case 'playwright': if (dependencies['@playwright/test']) return true; break; } } // Install framework log.info(`Installing test framework ${framework}...`); // Get install command based on framework let installCmd; switch (framework) { case 'jest': installCmd = 'npm install --save-dev jest @testing-library/react @testing-library/jest-dom'; break; case 'vitest': installCmd = 'npm install --save-dev vitest @testing-library/react jsdom'; break; case 'mocha': installCmd = 'npm install --save-dev mocha chai'; break; case 'cypress': installCmd = 'npm install --save-dev cypress'; break; case 'playwright': installCmd = 'npm install --save-dev @playwright/test'; break; default: installCmd = `npm install --save-dev ${framework}`; } // Execute install command execSync(installCmd, { cwd: projectRoot, stdio: 'inherit' }); log.info(`Test framework ${framework} installed successfully`); return true; } catch (error) { log.error(`Error installing test framework ${framework}: ${error.message}`); return false; } } /** * Set up CI/CD integration * @param {string} testFramework - Test framework to use * @param {string} projectRoot - Project root directory * @param {Object} context - Execution context * @returns {Promise<Object>} CI/CD configuration */ export async function setupCICDIntegration(testFramework, projectRoot, context) { try { // Detect existing CI/CD system const existingCICD = detectCICDSystem(projectRoot); // Determine CI/CD system to use const ciSystem = context.ciSystem || existingCICD || 'GitHub Actions'; // Create CI/CD configuration const cicdConfig = await createCICDConfig({ ciSystem, projectRoot, testFrameworks: [testFramework], testConfig: { coverage: context.coverage || 80 } }); log.info(`CI/CD integration set up for ${ciSystem}`); return cicdConfig; } catch (error) { log.error(`Error setting up CI/CD integration: ${error.message}`); return null; } } /** * Collect and analyze test metrics * @param {string} testFramework - Test framework to use * @param {string} projectRoot - Project root directory * @param {Object} testResults - Test results (if available) * @returns {Promise<Object>} Metrics and analysis */ export async function collectAndAnalyzeMetrics(testFramework, projectRoot, testResults) { try { // Collect metrics const metrics = await collectTestMetrics({ projectRoot, testFrameworks: [testFramework], metricTypes: ['coverage', 'performance', 'reliability', 'maintainability'] }); // Analyze metrics const analysis = analyzeTestMetrics(metrics); // Generate report const report = await generateTestReport({ metrics, analysis, projectRoot, format: 'html' }); // Track metrics over time const trends = await trackTestMetricsOverTime({ projectRoot, metrics }); log.info(`Test metrics collected and analyzed`); return { metrics, analysis, report, trends }; } catch (error) { log.error(`Error collecting and analyzing metrics: ${error.message}`); return null; } } /** * Run tests with specific framework * @param {string} testPath - Path to test file or directory * @param {string} testFramework - Test framework to use * @param {string} projectRoot - Project root directory * @param {Object} options - Additional options * @returns {Promise<Object>} Test results */ export async function runTestsWithFramework(testPath, testFramework, projectRoot, options = {}) { try { const { coverage = true } = options; // Run tests const results = await runTests(testFramework, testPath, { coverage, projectRoot }); log.info(`Tests run with ${testFramework}: ${results.passed ? 'PASSED' : 'FAILED'}`); return results; } catch (error) { log.error(`Error running tests with ${testFramework}: ${error.message}`); return { passed: false, failures: 1, totalTests: 1, coverage: null, error: error.message }; } } /** * Generate refactoring suggestions based on test results * @param {Object} params - Refactoring parameters * @param {string} params.implementationPath - Path to implementation * @param {string} params.testPath - Path to tests * @param {Object} params.testResults - Test results * @param {Object} context - Execution context * @returns {Promise<Object>} Refactoring suggestions */ export async function generateRefactoringSuggestions(params, context) { const { implementationPath, testPath, testResults } = params; try { // Read implementation code const implementationCode = fs.readFileSync(implementationPath, 'utf8'); // Read test code const testCode = fs.readFileSync(testPath, 'utf8'); // Prepare prompt for AI const prompt = `I need to refactor this implementation to make it pass the tests. Implementation code: \`\`\` ${implementationCode} \`\`\` Test code: \`\`\` ${testCode} \`\`\` Test results: ${JSON.stringify(testResults, null, 2)} Please suggest specific changes to the implementation code to make it pass the tests. Focus on addressing the failing tests and improving code quality. Return the refactored implementation code and explain the changes made.`; // Get AI model const { type: modelType, client } = getAvailableAIModel(); // Call AI model let response; if (modelType === 'claude') { response = await client.messages.create({ model: context.model || 'claude-3-opus-20240229', max_tokens: context.maxTokens || 4000, temperature: context.temperature || 0.2, system: "You are an expert software engineer specializing in test-driven development. Your task is to refactor code to make it pass tests.", messages: [ { role: 'user', content: prompt } ] }); // Extract refactored code const refactoredCode = extractCodeFromAIResponse(response.content[0].text); // Extract explanation const explanation = extractExplanationFromAIResponse(response.content[0].text); return { refactoredCode, explanation, implementationPath }; } else if (modelType === 'perplexity') { response = await client.chat.completions.create({ model: context.model || 'sonar-medium-online', messages: [ { role: 'system', content: "You are an expert software engineer specializing in test-driven development. Your task is to refactor code to make it pass tests." }, { role: 'user', content: prompt } ], temperature: context.temperature || 0.2 }); // Extract refactored code const refactoredCode = extractCodeFromAIResponse(response.choices[0].message.content); // Extract explanation const explanation = extractExplanationFromAIResponse(response.choices[0].message.content); return { refactoredCode, explanation, implementationPath }; } else { throw new Error('No AI model available'); } } catch (error) { log.error(`Error generating refactoring suggestions: ${error.message}`); return { refactoredCode: null, explanation: `Error generating refactoring suggestions: ${error.message}`, implementationPath }; } } /** * Extract code from AI response * @param {string} response - AI response * @returns {string} Extracted code */ function extractCodeFromAIResponse(response) { try { // Extract code block from response const codeMatch = response.match(/```(?:javascript|jsx|typescript|tsx)?\n([\s\S]*?)```/); if (codeMatch) { return codeMatch[1].trim(); } // If no code block found, return null return null; } catch (error) { log.error(`Error extracting code: ${error.message}`); return null; } } /** * Extract explanation from AI response * @param {string} response - AI response * @returns {string} Extracted explanation */ function extractExplanationFromAIResponse(response) { try { // Remove code blocks const withoutCode = response.replace(/```(?:javascript|jsx|typescript|tsx)?\n[\s\S]*?```/g, ''); // Extract explanation return withoutCode.trim(); } catch (error) { log.error(`Error extracting explanation: ${error.message}`); return ''; } }