cortexweaver
Version:
CortexWeaver is a command-line interface (CLI) tool that orchestrates a swarm of specialized AI agents, powered by Claude Code and Gemini CLI, to assist in software development. It transforms a high-level project plan (plan.md) into a series of coordinate
359 lines (353 loc) • 14 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.QualityGatekeeperAgent = void 0;
const agent_1 = require("../agent");
/**
* QualityGatekeeperAgent validates code quality after Coder completion
* Runs linting, testing, and coverage validation
*/
class QualityGatekeeperAgent extends agent_1.Agent {
constructor() {
super(...arguments);
this.notification = null;
this.startTime = 0;
}
/**
* Get the prompt template for quality gatekeeper tasks
*/
getPromptTemplate() {
return `You are a Quality Gatekeeper responsible for validating code quality after development completion.
Task: {{taskDescription}}
Your responsibilities:
1. Validate code meets quality standards through linting
2. Ensure all tests pass (unit and integration)
3. Verify code coverage meets thresholds
4. Generate comprehensive quality reports
Quality Standards:
- ESLint must pass with no errors
- Prettier formatting must be consistent
- All unit tests must pass
- Integration tests must pass
- Code coverage must meet configured thresholds
Project Context:
- Language: {{language}}
- Framework: {{framework}}
- Test Framework: {{testFramework}}
Coverage Thresholds:
- Statements: {{statementsThreshold}}%
- Branches: {{branchesThreshold}}%
- Functions: {{functionsThreshold}}%
- Lines: {{linesThreshold}}%
Please provide detailed analysis and recommendations for any quality issues found.`;
}
/**
* Execute quality gatekeeper task
*/
async executeTask() {
if (!this.currentTask || !this.taskContext) {
throw new Error('No task or context available');
}
this.startTime = Date.now();
await this.reportProgress('started', 'Beginning quality validation');
try {
// Wait for coder notification if not already received
if (!this.notification) {
await this.waitForCoderNotification();
}
// Run linting checks
await this.reportProgress('in_progress', 'Running linting checks');
const lintingResult = await this.runLinter();
// Run test suites
await this.reportProgress('in_progress', 'Running test suites');
const testingResult = await this.runTests();
// Validate coverage
await this.reportProgress('in_progress', 'Validating code coverage');
const coverageResult = await this.validateCoverage();
// Generate comprehensive report
const report = await this.generateReport({
linting: lintingResult,
testing: testingResult,
coverage: coverageResult
});
const overallStatus = this.determineOverallStatus(lintingResult, testingResult, coverageResult);
const executionTime = Date.now() - this.startTime;
await this.reportProgress('completed', `Quality validation completed: ${overallStatus}`);
return {
notificationReceived: true,
qualityChecksPassed: overallStatus === 'PASS',
overallStatus,
report,
executionTime
};
}
catch (error) {
const executionTime = Date.now() - this.startTime;
await this.reportProgress('error', `Quality validation failed: ${error.message}`);
throw error;
}
}
/**
* Receive notification from Coder completion
*/
async receiveNotification(notification) {
if (!notification.type || notification.type !== 'coder_completion') {
throw new Error('Invalid notification format');
}
if (!notification.taskId || !notification.timestamp) {
throw new Error('Invalid notification format');
}
this.notification = notification;
await this.reportProgress('notification_received', `Received coder completion for task ${notification.taskId}`);
return {
success: true,
notificationReceived: true
};
}
/**
* Run project linters (ESLint, Prettier)
*/
async runLinter() {
const errors = [];
const warnings = [];
try {
// Run ESLint
const eslintResult = await this.executeCommand('npx eslint src tests --ext .ts,.js --format json');
if (eslintResult.exitCode !== 0) {
try {
const eslintData = JSON.parse(eslintResult.stdout);
for (const file of eslintData) {
for (const message of file.messages) {
const errorMsg = `${file.filePath}: ${message.ruleId} - ${message.message} (line ${message.line})`;
if (message.severity === 2) {
errors.push(errorMsg);
}
else {
warnings.push(errorMsg);
}
}
}
}
catch (parseError) {
errors.push(`ESLint parsing error: ${eslintResult.stderr || eslintResult.stdout}`);
}
}
// Run Prettier check
const prettierResult = await this.executeCommand('npx prettier --check src tests --parser typescript');
if (prettierResult.exitCode !== 0) {
errors.push(`Prettier formatting issues: ${prettierResult.stdout}`);
}
const success = errors.length === 0;
return {
success,
lintingPassed: success,
errors,
warnings
};
}
catch (error) {
return {
success: false,
lintingPassed: false,
errors: [`Linting execution failed: ${error.message}`],
warnings: []
};
}
}
/**
* Run unit and integration tests
*/
async runTests() {
const errors = [];
let unitTestsPassed = false;
let integrationTestsPassed = false;
let testStats = { total: 0, passed: 0, failed: 0 };
try {
// Run unit tests
const unitTestResult = await this.executeCommand('npm test');
if (unitTestResult.exitCode === 0) {
unitTestsPassed = true;
// Parse test statistics if available
const testOutput = unitTestResult.stdout;
const testMatch = testOutput.match(/Tests:\s+(\d+)\s+passed,\s+(\d+)\s+total/);
if (testMatch) {
testStats.passed = parseInt(testMatch[1]);
testStats.total = parseInt(testMatch[2]);
testStats.failed = testStats.total - testStats.passed;
}
}
else {
errors.push(`Unit tests failed: ${unitTestResult.stderr || unitTestResult.stdout}`);
}
// Run integration tests if they exist
const integrationTestResult = await this.executeCommand('npm test -- --testPathPattern=integration');
if (integrationTestResult.exitCode === 0) {
integrationTestsPassed = true;
}
else {
// Integration tests might not exist, check if that's the case
if (!integrationTestResult.stderr?.includes('No tests found')) {
errors.push(`Integration tests failed: ${integrationTestResult.stderr || integrationTestResult.stdout}`);
}
else {
integrationTestsPassed = true; // No integration tests is OK
}
}
const success = unitTestsPassed && integrationTestsPassed;
return {
success,
unitTestsPassed,
integrationTestsPassed,
errors,
testStats
};
}
catch (error) {
return {
success: false,
unitTestsPassed: false,
integrationTestsPassed: false,
errors: [`Test execution failed: ${error.message}`],
testStats
};
}
}
/**
* Validate code coverage thresholds
*/
async validateCoverage() {
const errors = [];
let coverageData = { statements: 0, branches: 0, functions: 0, lines: 0 };
try {
// Run coverage report
const coverageResult = await this.executeCommand('npm run test:coverage -- --coverageReporters=json');
if (coverageResult.exitCode === 0) {
try {
const coverageJson = JSON.parse(coverageResult.stdout);
const total = coverageJson.total;
coverageData = {
statements: total.statements.pct,
branches: total.branches.pct,
functions: total.functions.pct,
lines: total.lines.pct
};
// Check thresholds
const thresholds = this.getCoverageThresholds();
if (coverageData.statements < thresholds.statements) {
errors.push(`Statements coverage ${coverageData.statements}% below threshold ${thresholds.statements}%`);
}
if (coverageData.branches < thresholds.branches) {
errors.push(`Branches coverage ${coverageData.branches}% below threshold ${thresholds.branches}%`);
}
if (coverageData.functions < thresholds.functions) {
errors.push(`Functions coverage ${coverageData.functions}% below threshold ${thresholds.functions}%`);
}
if (coverageData.lines < thresholds.lines) {
errors.push(`Lines coverage ${coverageData.lines}% below threshold ${thresholds.lines}%`);
}
}
catch (parseError) {
errors.push(`Coverage parsing error: ${parseError}`);
}
}
else {
errors.push(`Coverage execution failed: ${coverageResult.stderr || coverageResult.stdout}`);
}
const success = errors.length === 0;
return {
success,
coverageThresholdsMet: success,
coverageData,
errors
};
}
catch (error) {
return {
success: false,
coverageThresholdsMet: false,
coverageData,
errors: [`Coverage validation failed: ${error.message}`]
};
}
}
/**
* Generate detailed quality report
*/
async generateReport(results) {
const { linting, testing, coverage } = results;
const overallStatus = this.determineOverallStatus(linting, testing, coverage);
const timestamp = new Date().toISOString();
let summary = '';
if (overallStatus === 'PASS') {
summary = 'All quality checks passed successfully. Code is ready for deployment.';
}
else {
const failedChecks = [];
if (!linting.success)
failedChecks.push('linting');
if (!testing.success)
failedChecks.push('testing');
if (!coverage.success)
failedChecks.push('coverage');
summary = `Quality checks failed: ${failedChecks.join(', ')}. Review errors and fix issues.`;
}
return {
overallStatus,
summary,
timestamp,
linting: {
status: linting.success ? 'PASS' : 'FAIL',
errors: linting.errors,
warnings: linting.warnings
},
testing: {
status: testing.success ? 'PASS' : 'FAIL',
errors: testing.errors,
unitTests: testing.unitTestsPassed,
integrationTests: testing.integrationTestsPassed
},
coverage: {
status: coverage.success ? 'PASS' : 'FAIL',
errors: coverage.errors,
data: coverage.coverageData
}
};
}
/**
* Wait for coder notification (with timeout)
*/
async waitForCoderNotification(timeoutMs = 60000) {
return new Promise((resolve, reject) => {
const checkInterval = setInterval(() => {
if (this.notification) {
clearInterval(checkInterval);
clearTimeout(timeout);
resolve();
}
}, 1000);
const timeout = setTimeout(() => {
clearInterval(checkInterval);
reject(new Error('Timeout waiting for coder notification'));
}, timeoutMs);
});
}
/**
* Get coverage thresholds from context or defaults
*/
getCoverageThresholds() {
const contextThresholds = this.taskContext?.coverageThresholds;
return {
statements: contextThresholds?.statements || 80,
branches: contextThresholds?.branches || 75,
functions: contextThresholds?.functions || 85,
lines: contextThresholds?.lines || 80
};
}
/**
* Determine overall status from individual check results
*/
determineOverallStatus(linting, testing, coverage) {
return linting.success && testing.success && coverage.success ? 'PASS' : 'FAIL';
}
}
exports.QualityGatekeeperAgent = QualityGatekeeperAgent;
//# sourceMappingURL=quality-gatekeeper.js.map