// QUALITY AGENT
// Reviews code quality, creates summaries, suggests improvements
// Provides inventory summaries for the orchestrator
import { promises as fs } from 'fs';
import path from 'path';
import { actionLogger } from '../../logging/agent-action-logger.js';
export class QualityAgent {
constructor() {
this.name = 'QualityAgent';
this.role = 'Code review, quality assessment, and summarization';
}
// Review file quality
async review(filePath) {
console.log(`🔍 ${this.name}: Reviewing ${path.basename(filePath)}...`);
const sessionId = await actionLogger.startSession(
this.name,
`Review file: ${filePath}`
);
try {
const content = await fs.readFile(filePath, 'utf-8');
const review = {
filePath: filePath,
fileName: path.basename(filePath),
timestamp: new Date().toISOString(),
quality: {},
improvements: [],
summary: ''
};
// Check 1: Code structure
review.quality.structure = this.assessStructure(content);
// Check 2: Documentation
review.quality.documentation = this.assessDocumentation(content);
// Check 3: Error handling
review.quality.errorHandling = this.assessErrorHandling(content);
// Check 4: Dependencies
review.quality.dependencies = this.assessDependencies(content);
// Generate improvements
review.improvements = this.generateImprovements(review.quality);
// Generate summary
review.summary = this.generateSummary(content, filePath);
// Calculate overall score
review.overallScore = this.calculateScore(review.quality);
await actionLogger.endSession('completed');
console.log(` Score: ${review.overallScore}/100`);
console.log(` Improvements: ${review.improvements.length}`);
return review;
} catch (error) {
await actionLogger.endSession('failed');
console.error(` ❌ Review failed:`, error);
throw error;
}
}
// Assess code structure
assessStructure(content) {
const score = {
score: 0,
maxScore: 25,
notes: []
};
// Has exports
if (/export (class|const|function)/.test(content)) {
score.score += 10;
} else {
score.notes.push('No exports found');
}
// Has proper class structure
if (/class \w+/.test(content)) {
score.score += 10;
}
// Has constructor
if (/constructor\s*\(/.test(content)) {
score.score += 5;
}
return score;
}
// Assess documentation
assessDocumentation(content) {
const score = {
score: 0,
maxScore: 25,
notes: []
};
// Has file-level comments
if (/^\/\//.test(content)) {
score.score += 10;
} else {
score.notes.push('Missing file-level comments');
}
// Has function comments
const functionCommentRatio = this.countFunctionComments(content);
score.score += Math.min(15, Math.floor(functionCommentRatio * 15));
if (functionCommentRatio < 0.5) {
score.notes.push(`Only ${Math.floor(functionCommentRatio * 100)}% of functions documented`);
}
return score;
}
// Assess error handling
assessErrorHandling(content) {
const score = {
score: 0,
maxScore: 25,
notes: []
};
// Has try-catch blocks
const tryCatchCount = (content.match(/try\s*{/g) || []).length;
if (tryCatchCount > 0) {
score.score += Math.min(15, tryCatchCount * 5);
} else {
score.notes.push('No error handling found');
}
// Has error logging
if (/console\.(error|warn)/.test(content)) {
score.score += 10;
} else {
score.notes.push('No error logging');
}
return score;
}
// Assess dependencies
assessDependencies(content) {
const score = {
score: 0,
maxScore: 25,
notes: []
};
// Count imports
const imports = (content.match(/import .* from/g) || []).length;
if (imports === 0) {
score.score += 25; // No dependencies is good for standalone files
score.notes.push('No external dependencies');
} else if (imports <= 5) {
score.score += 20;
score.notes.push(`${imports} dependencies - good`);
} else {
score.score += 10;
score.notes.push(`${imports} dependencies - consider reducing`);
}
return score;
}
// Count function documentation ratio
countFunctionComments(content) {
const functions = (content.match(/(?:async\s+)?(?:function\s+)?\w+\s*\([^)]*\)\s*{/g) || []).length;
const comments = (content.match(/\/\/.*(?:function|method|param|return)/gi) || []).length;
return functions > 0 ? comments / functions : 0;
}
// Generate improvement suggestions
generateImprovements(quality) {
const improvements = [];
for (const [category, assessment] of Object.entries(quality)) {
if (assessment.score < assessment.maxScore * 0.7) {
improvements.push({
category: category,
priority: 'medium',
suggestions: assessment.notes
});
}
}
return improvements;
}
// Generate file summary
generateSummary(content, filePath) {
const fileName = path.basename(filePath);
const lines = content.split('\n').length;
// Extract class name
const classMatch = content.match(/export class (\w+)/);
const className = classMatch ? classMatch[1] : null;
// Extract main functions
const functions = [];
const funcMatches = content.matchAll(/(?:async\s+)?(\w+)\s*\([^)]*\)\s*{/g);
for (const match of funcMatches) {
if (match[1] !== 'constructor') {
functions.push(match[1]);
}
}
// Build summary
let summary = `File: ${fileName} (${lines} lines)\n`;
if (className) {
summary += `Class: ${className}\n`;
}
if (functions.length > 0) {
summary += `Main functions: ${functions.slice(0, 5).join(', ')}`;
if (functions.length > 5) {
summary += ` (+${functions.length - 5} more)`;
}
}
return summary;
}
// Calculate overall score
calculateScore(quality) {
let total = 0;
let max = 0;
for (const assessment of Object.values(quality)) {
total += assessment.score;
max += assessment.maxScore;
}
return Math.round((total / max) * 100);
}
// Create inventory summary for orchestrator
async createInventorySummary(inventoryPath) {
console.log(`📊 ${this.name}: Creating inventory summary...`);
try {
const data = await fs.readFile(inventoryPath, 'utf-8');
const inventory = JSON.parse(data);
const summary = {
totalAgents: inventory.totalAgents || 0,
totalTools: inventory.totalTools || 0,
agentsByCapability: {},
toolsByCategory: {},
reusableComponents: inventory.reusableComponents || [],
recommendations: []
};
// Group agents by capability
for (const agent of Object.values(inventory.agents || {})) {
for (const capability of agent.capabilities || []) {
if (!summary.agentsByCapability[capability]) {
summary.agentsByCapability[capability] = [];
}
summary.agentsByCapability[capability].push(agent.name);
}
}
// Generate recommendations
if (summary.totalAgents < 5) {
summary.recommendations.push('Consider creating more specialized agents');
}
if (summary.reusableComponents.length === 0) {
summary.recommendations.push('No reusable components identified yet');
}
console.log(` ✅ Summary created`);
console.log(` Agents: ${summary.totalAgents}`);
console.log(` Tools: ${summary.totalTools}`);
return summary;
} catch (error) {
console.error(` ❌ Summary failed:`, error);
throw error;
}
}
}
export const qualityAgent = new QualityAgent();