sf-agent-framework
Version:
AI Agent Orchestration Framework for Salesforce Development - Two-phase architecture with 70% context reduction
769 lines (673 loc) ⢠24.7 kB
JavaScript
/**
* Reflection Engine
*
* Purpose: Enable self-correcting agents through systematic reflection
* and iterative improvement
*
* Key Features:
* - Multi-criteria output analysis
* - Self-correction with iterative improvement
* - Quality scoring and threshold enforcement
* - Issue identification and resolution
* - Reflection history tracking
*
* @module ReflectionEngine
* @version 1.0.0
* @date 2025-11-25
*/
const fs = require('fs-extra');
const path = require('path');
const yaml = require('js-yaml');
class ReflectionEngine {
constructor(rootDir = process.cwd()) {
this.rootDir = rootDir;
this.configPath = path.join(rootDir, 'sf-core', 'config', 'reflection-engine.yaml');
this.reflectionsDir = path.join(rootDir, '.sf-agent', 'reflections');
// Default configuration
this.config = {
enabled: true,
max_iterations: 3,
improvement_threshold: 0.8, // 80% quality score required
reflection_triggers: ['on-completion', 'on-error', 'on-user-request', 'on-low-quality'],
// Reflection checklist categories
reflection_checklist: {
code_quality: {
weight: 0.25,
checks: [
'follows-coding-standards',
'has-appropriate-comments',
'handles-edge-cases',
'includes-error-handling',
'no-code-smells',
],
},
salesforce_best_practices: {
weight: 0.3,
checks: [
'bulkified',
'within-governor-limits',
'secure-coding',
'test-coverage-adequate',
'uses-platform-features',
],
},
architecture: {
weight: 0.25,
checks: [
'scalable-design',
'maintainable',
'follows-patterns',
'properly-documented',
'loose-coupling',
],
},
completeness: {
weight: 0.2,
checks: [
'requirements-met',
'all-use-cases-covered',
'no-todos-remaining',
'deployment-ready',
'user-stories-complete',
],
},
},
self_correction: {
enabled: true,
auto_apply: false, // Require user approval
max_auto_iterations: 2,
},
};
// Reflection state
this.state = {
currentReflection: null,
history: [],
};
}
/**
* Initialize reflection engine
*/
async initialize() {
await fs.ensureDir(this.reflectionsDir);
// Load custom configuration if exists
if (await fs.pathExists(this.configPath)) {
const customConfig = yaml.load(await fs.readFile(this.configPath, 'utf8'));
this.config = this.deepMerge(this.config, customConfig);
}
console.log('ā Reflection engine initialized');
return true;
}
/**
* Main reflection entry point
*/
async reflect(agentOutput, options = {}) {
console.log('\nš Starting reflection process...\n');
const reflection = {
id: this.generateReflectionId(),
timestamp: new Date().toISOString(),
agent: options.agent || 'unknown',
originalOutput: agentOutput,
iterations: [],
finalOutput: null,
finalScore: 0,
status: 'in-progress',
};
this.state.currentReflection = reflection;
try {
// Run reflection iterations
let currentOutput = agentOutput;
let iteration = 0;
while (iteration < this.config.max_iterations) {
iteration++;
console.log(`š Reflection iteration ${iteration}/${this.config.max_iterations}\n`);
// Analyze current output
const analysis = await this.analyzeOutput(currentOutput, options);
reflection.iterations.push({
iteration,
analysis,
timestamp: new Date().toISOString(),
});
console.log(` Overall score: ${(analysis.score * 100).toFixed(1)}%`);
console.log(` Issues found: ${analysis.issues.length}\n`);
// Check if quality threshold met
if (analysis.score >= this.config.improvement_threshold) {
console.log(
`ā
Quality threshold met (${(analysis.score * 100).toFixed(1)}% >= ${this.config.improvement_threshold * 100}%)\n`
);
reflection.finalOutput = currentOutput;
reflection.finalScore = analysis.score;
reflection.status = 'success';
break;
}
// Check if max iterations reached
if (iteration >= this.config.max_iterations) {
console.log(`ā ļø Max iterations reached without meeting threshold\n`);
reflection.finalOutput = currentOutput;
reflection.finalScore = analysis.score;
reflection.status = 'max-iterations';
break;
}
// Generate improvements
const improvements = await this.suggestImprovements(analysis);
console.log(` Generated ${improvements.length} improvement suggestions\n`);
// Apply improvements (if auto-correction enabled and within limits)
if (
this.config.self_correction.enabled &&
iteration <= this.config.self_correction.max_auto_iterations
) {
currentOutput = await this.applyImprovements(currentOutput, improvements, analysis);
console.log(` ā Improvements applied automatically\n`);
} else {
// Return improvements for manual review
reflection.finalOutput = currentOutput;
reflection.finalScore = analysis.score;
reflection.pendingImprovements = improvements;
reflection.status = 'pending-approval';
break;
}
}
// Save reflection history
await this.saveReflection(reflection);
this.state.history.push(reflection);
return {
success: reflection.status === 'success',
reflection,
output: reflection.finalOutput,
score: reflection.finalScore,
iterations: reflection.iterations.length,
improvements: reflection.pendingImprovements,
};
} catch (error) {
console.error('ā Reflection failed:', error.message);
reflection.status = 'error';
reflection.error = error.message;
return {
success: false,
error: error.message,
reflection,
};
}
}
/**
* Analyze output against reflection checklist
*/
async analyzeOutput(output, options = {}) {
const results = {};
const issues = [];
let totalScore = 0;
let totalWeight = 0;
// Run checks for each category
for (const [category, config] of Object.entries(this.config.reflection_checklist)) {
const categoryResult = await this.runCategoryChecks(output, category, config);
results[category] = categoryResult;
// Calculate weighted score
totalScore += categoryResult.score * config.weight;
totalWeight += config.weight;
// Collect issues
issues.push(...categoryResult.issues);
}
const overallScore = totalWeight > 0 ? totalScore / totalWeight : 0;
return {
score: overallScore,
categoryResults: results,
issues,
timestamp: new Date().toISOString(),
passesThreshold: overallScore >= this.config.improvement_threshold,
};
}
/**
* Run checks for a category
*/
async runCategoryChecks(output, category, config) {
const checkResults = [];
let passedChecks = 0;
for (const check of config.checks) {
const result = await this.runSingleCheck(output, category, check);
checkResults.push(result);
if (result.passed) passedChecks++;
}
const categoryScore = config.checks.length > 0 ? passedChecks / config.checks.length : 0;
const issues = checkResults
.filter((r) => !r.passed)
.map((r) => ({
category,
check: r.check,
severity: r.severity || 'medium',
description: r.description,
suggestion: r.suggestion,
}));
return {
score: categoryScore,
checks: checkResults,
issues,
passedChecks,
totalChecks: config.checks.length,
};
}
/**
* Run a single check
*/
async runSingleCheck(output, category, check) {
// This would integrate with actual code analysis tools
// For now, we'll implement pattern-based checks
const result = {
check,
category,
passed: false,
confidence: 0,
description: '',
suggestion: '',
};
// Code quality checks
if (category === 'code_quality') {
switch (check) {
case 'follows-coding-standards':
result.passed = this.checkCodingStandards(output);
result.description = result.passed
? 'Code follows naming conventions and formatting standards'
: 'Code does not follow standard conventions';
result.suggestion = 'Review naming conventions, indentation, and code organization';
break;
case 'has-appropriate-comments':
result.passed = this.checkComments(output);
result.description = result.passed
? 'Code has adequate comments and documentation'
: 'Code lacks sufficient comments';
result.suggestion = 'Add comments for complex logic and public methods';
break;
case 'handles-edge-cases':
result.passed = this.checkEdgeCases(output);
result.description = result.passed
? 'Code handles edge cases and null checks'
: 'Missing edge case handling';
result.suggestion = 'Add null checks and boundary condition handling';
break;
case 'includes-error-handling':
result.passed = this.checkErrorHandling(output);
result.description = result.passed
? 'Proper error handling implemented'
: 'Missing or inadequate error handling';
result.suggestion = 'Add try-catch blocks and meaningful error messages';
break;
case 'no-code-smells':
result.passed = this.checkCodeSmells(output);
result.description = result.passed
? 'No major code smells detected'
: 'Code smells detected (duplicated code, long methods, etc.)';
result.suggestion = 'Refactor to eliminate code smells';
break;
}
}
// Salesforce best practices checks
else if (category === 'salesforce_best_practices') {
switch (check) {
case 'bulkified':
result.passed = this.checkBulkification(output);
result.description = result.passed
? 'Code is properly bulkified for governor limits'
: 'Code contains non-bulkified patterns';
result.suggestion = 'Process records in collections, not one at a time';
result.severity = 'high';
break;
case 'within-governor-limits':
result.passed = this.checkGovernorLimits(output);
result.description = result.passed
? 'Code respects Salesforce governor limits'
: 'Potential governor limit violations detected';
result.suggestion = 'Review SOQL queries in loops and DML operations';
result.severity = 'critical';
break;
case 'secure-coding':
result.passed = this.checkSecureCoding(output);
result.description = result.passed
? 'Code follows security best practices'
: 'Security concerns detected';
result.suggestion = 'Add WITH SECURITY_ENFORCED, validate user input';
result.severity = 'high';
break;
case 'test-coverage-adequate':
result.passed = this.checkTestCoverage(output);
result.description = result.passed
? 'Test coverage appears adequate'
: 'Insufficient test coverage';
result.suggestion = 'Add test classes with >75% coverage';
result.severity = 'high';
break;
case 'uses-platform-features':
result.passed = this.checkPlatformFeatures(output);
result.description = result.passed
? 'Leverages Salesforce platform features appropriately'
: 'Could better utilize platform features';
result.suggestion = 'Consider using platform features instead of custom code';
break;
}
}
// Architecture checks
else if (category === 'architecture') {
switch (check) {
case 'scalable-design':
result.passed = this.checkScalability(output);
result.description = result.passed
? 'Design appears scalable'
: 'Design may not scale well';
result.suggestion = 'Review data model and query patterns for scale';
break;
case 'maintainable':
result.passed = this.checkMaintainability(output);
result.description = result.passed
? 'Code is maintainable'
: 'Code may be difficult to maintain';
result.suggestion = 'Simplify complex methods and improve readability';
break;
case 'follows-patterns':
result.passed = this.checkPatterns(output);
result.description = result.passed
? 'Follows established patterns'
: 'Does not follow standard patterns';
result.suggestion = 'Use design patterns like Trigger Handler, Service Layer';
break;
case 'properly-documented':
result.passed = this.checkDocumentation(output);
result.description = result.passed
? 'Code is well documented'
: 'Documentation is lacking';
result.suggestion = 'Add class/method documentation and inline comments';
break;
case 'loose-coupling':
result.passed = this.checkCoupling(output);
result.description = result.passed
? 'Components are loosely coupled'
: 'Tight coupling detected';
result.suggestion = 'Use interfaces and dependency injection';
break;
}
}
// Completeness checks
else if (category === 'completeness') {
switch (check) {
case 'requirements-met':
result.passed = this.checkRequirements(output);
result.description = result.passed
? 'All requirements appear to be met'
: 'Some requirements may not be met';
result.suggestion = 'Review requirements checklist';
result.severity = 'high';
break;
case 'all-use-cases-covered':
result.passed = this.checkUseCases(output);
result.description = result.passed
? 'Use cases are covered'
: 'Some use cases may not be covered';
result.suggestion = 'Review use case scenarios';
break;
case 'no-todos-remaining':
result.passed = !this.checkTodos(output);
result.description = result.passed
? 'No TODO items remaining'
: 'TODO items found in code';
result.suggestion = 'Complete or remove TODO items';
break;
case 'deployment-ready':
result.passed = this.checkDeploymentReady(output);
result.description = result.passed
? 'Code appears deployment ready'
: 'Code may not be ready for deployment';
result.suggestion = 'Complete testing and documentation';
result.severity = 'high';
break;
case 'user-stories-complete':
result.passed = true; // Would check against user stories
result.description = 'User stories status not verified';
result.suggestion = 'Verify all user stories are complete';
break;
}
}
result.confidence = result.passed ? 0.8 : 0.7;
return result;
}
// ========================================================================
// CHECK IMPLEMENTATIONS (Pattern-based heuristics)
// ========================================================================
checkCodingStandards(output) {
const content = this.extractContent(output);
// Check for consistent naming, indentation, etc.
const hasConsistentNaming = /^[A-Z][a-zA-Z0-9]*/.test(content); // Class names
const hasIndentation = /\n {2,}/.test(content);
return hasConsistentNaming && hasIndentation;
}
checkComments(output) {
const content = this.extractContent(output);
const commentRatio =
(content.match(/\/\*|\*\/|\/\//g) || []).length / content.split('\n').length;
return commentRatio > 0.1; // At least 10% lines with comments
}
checkEdgeCases(output) {
const content = this.extractContent(output);
const hasNullChecks = /if\s*\([^)]*!=\s*null|isEmpty\(/i.test(content);
const hasBoundaryChecks = /if\s*\([^)]*[<>]=?/i.test(content);
return hasNullChecks || hasBoundaryChecks;
}
checkErrorHandling(output) {
const content = this.extractContent(output);
const hasTryCatch = /try\s*{[\s\S]*catch\s*\(/i.test(content);
return hasTryCatch || content.includes('throw ');
}
checkCodeSmells(output) {
const content = this.extractContent(output);
const lines = content.split('\n');
const longMethods = lines.filter((_, i) => {
const methodStart = lines[i].match(/\b(?:public|private|protected)\s+\w+\s+\w+\s*\(/);
if (!methodStart) return false;
// Check if method is too long (>50 lines)
let braceDepth = 0;
for (let j = i; j < Math.min(i + 60, lines.length); j++) {
braceDepth += (lines[j].match(/{/g) || []).length;
braceDepth -= (lines[j].match(/}/g) || []).length;
if (braceDepth === 0 && j > i + 50) return true;
}
return false;
});
return longMethods.length === 0;
}
checkBulkification(output) {
const content = this.extractContent(output);
// Check for SOQL/DML in loops
const hasSoqlInLoop = /for\s*\([^)]*\)\s*{[^}]*\[SELECT/i.test(content);
const hasDmlInLoop = /for\s*\([^)]*\)\s*{[^}]*(insert|update|delete|upsert)\s+/i.test(content);
return !hasSoqlInLoop && !hasDmlInLoop;
}
checkGovernorLimits(output) {
const content = this.extractContent(output);
// Check for common governor limit issues
const hasMapForQueries = /Map<.*>\s+\w+\s*=\s*new\s+Map<.*>\(\[SELECT/i.test(content);
const hasBulkDml = /\b(insert|update|delete|upsert)\s+\w+List/i.test(content);
return hasMapForQueries || hasBulkDml || !content.includes('[SELECT');
}
checkSecureCoding(output) {
const content = this.extractContent(output);
const hasSecurityEnforced = /WITH SECURITY_ENFORCED/i.test(content);
const hasSharing = /with sharing|without sharing/i.test(content);
return hasSecurityEnforced || hasSharing || !content.includes('[SELECT');
}
checkTestCoverage(output) {
const content = this.extractContent(output);
const hasTestClass = /|testMethod/i.test(content);
const hasAssertions = /System\.assert/i.test(content);
return hasTestClass && hasAssertions;
}
checkPlatformFeatures(output) {
const content = this.extractContent(output);
// Check if using platform features vs custom solutions
const usesPlatformFeatures = /Schema\.|Database\.|System\./i.test(content);
return usesPlatformFeatures;
}
checkScalability(output) {
const content = this.extractContent(output);
// Check for scalable patterns
const usesLimits = /Limits\./i.test(content);
const usesBatching = /Database\.Batchable|Queueable/i.test(content);
return usesLimits || usesBatching || content.length < 500;
}
checkMaintainability(output) {
const content = this.extractContent(output);
const avgLineLength = content.length / content.split('\n').length;
const hasModularDesign = /class.*{[\s\S]*?public.*{/i.test(content);
return avgLineLength < 80 && hasModularDesign;
}
checkPatterns(output) {
const content = this.extractContent(output);
// Check for common Salesforce patterns
const hasTriggerHandler = /TriggerHandler|Handler/i.test(content);
const hasServiceLayer = /Service\.cls|Service\s+class/i.test(content);
return hasTriggerHandler || hasServiceLayer || content.length < 200;
}
checkDocumentation(output) {
const content = this.extractContent(output);
const hasClassDoc = /\/\*\*[\s\S]*?\*\/[\s\S]*?class/i.test(content);
const hasMethodDoc = /\/\*\*[\s\S]*?\*\/[\s\S]*?(?:public|private|protected)/i.test(content);
return hasClassDoc || hasMethodDoc;
}
checkCoupling(output) {
const content = this.extractContent(output);
// Check for tight coupling indicators
const hasDirectInstantiation = /new\s+[A-Z]\w+\(/g.test(content);
const instanceCount = (content.match(/new\s+[A-Z]\w+\(/g) || []).length;
return instanceCount < 5; // Loose threshold
}
checkRequirements(output) {
// Would check against actual requirements
return true;
}
checkUseCases(output) {
// Would check against use case documentation
return true;
}
checkTodos(output) {
const content = this.extractContent(output);
return /TODO|FIXME|HACK/i.test(content);
}
checkDeploymentReady(output) {
const content = this.extractContent(output);
const hasTests = //i.test(content);
const hasNoTodos = !this.checkTodos(output);
return (hasTests || content.length < 100) && hasNoTodos;
}
/**
* Extract content from output (handle various formats)
*/
extractContent(output) {
if (typeof output === 'string') return output;
if (output.content) return output.content;
if (output.code) return output.code;
return JSON.stringify(output);
}
/**
* Suggest improvements based on analysis
*/
async suggestImprovements(analysis) {
const improvements = [];
for (const issue of analysis.issues) {
improvements.push({
category: issue.category,
check: issue.check,
severity: issue.severity,
description: issue.description,
suggestion: issue.suggestion,
priority: this.calculatePriority(issue.severity),
action: this.generateAction(issue),
});
}
// Sort by priority
improvements.sort((a, b) => a.priority - b.priority);
return improvements;
}
/**
* Calculate priority (lower number = higher priority)
*/
calculatePriority(severity) {
const priorities = {
critical: 1,
high: 2,
medium: 3,
low: 4,
};
return priorities[severity] || 3;
}
/**
* Generate action for improvement
*/
generateAction(issue) {
return {
type: 'refactor',
target: issue.check,
description: issue.suggestion,
};
}
/**
* Apply improvements to output
*/
async applyImprovements(output, improvements, analysis) {
// This would integrate with actual code modification tools
// For now, return output with improvement notes
let improvedOutput = this.extractContent(output);
// Add improvement comments
const improvementNotes = improvements
.map((imp) => `// IMPROVEMENT NEEDED: ${imp.check} - ${imp.suggestion}`)
.join('\n');
return {
content: improvedOutput,
improvements: improvementNotes,
metadata: {
originalScore: analysis.score,
improvementsApplied: improvements.length,
timestamp: new Date().toISOString(),
},
};
}
/**
* Save reflection to history
*/
async saveReflection(reflection) {
const reflectionPath = path.join(this.reflectionsDir, `${reflection.id}.json`);
await fs.writeJson(reflectionPath, reflection, { spaces: 2 });
}
/**
* Get reflection history
*/
getHistory(limit = 10) {
return this.state.history.slice(-limit);
}
/**
* Generate reflection ID
*/
generateReflectionId() {
return `reflection_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
}
/**
* Deep merge objects
*/
deepMerge(target, source) {
const output = { ...target };
if (this.isObject(target) && this.isObject(source)) {
Object.keys(source).forEach((key) => {
if (this.isObject(source[key])) {
if (!(key in target)) {
output[key] = source[key];
} else {
output[key] = this.deepMerge(target[key], source[key]);
}
} else {
output[key] = source[key];
}
});
}
return output;
}
/**
* Check if value is object
*/
isObject(item) {
return item && typeof item === 'object' && !Array.isArray(item);
}
}
module.exports = ReflectionEngine;