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
JavaScript
/**
* 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 '';
}
}