export default class CodeAgent extends AgenticSystem {
@field codebase: CodebaseManager;
@field thinking: stream<string>('');
@field code: stream<string>('');
@field testOutput: stream<string>('');
@field promptGuidance: { do: string[]; avoid: string[] } = { do: [], avoid: [] };
async implement(spec: Specification): Promise<ImplementationResult> {
this.thinking.append(`Starting implementation: ${spec.title}\n`);
// Plan
const plan = await this.plan(spec);
this.thinking.append(`Plan created with ${plan.steps.length} steps\n`);
// Implementation loop
let attempt = 0;
const maxAttempts = 5;
let lastError: VerificationError | null = null;
while (attempt < maxAttempts) {
attempt++;
this.thinking.append(`\n--- Attempt ${attempt}/${maxAttempts} ---\n`);
// Generate code
const code = await this.generateCode(spec, plan, lastError);
// Apply to codebase
await this.applyCode(code);
this.code.set(code.content);
// Verify
const verification = await this.verify(spec);
this.testOutput.set(verification.output);
if (verification.passed) {
this.thinking.append(`Verification passed!\n`);
return {
success: true,
code,
attempts: attempt,
spec
};
}
// Analyze error for next attempt
this.thinking.append(`Verification failed: ${verification.summary}\n`);
lastError = await this.analyzeError(verification, code);
}
this.thinking.append(`Max attempts reached. Implementation failed.\n`);
return {
success: false,
code: null,
attempts: attempt,
lastError,
spec
};
}
private async plan(spec: Specification): Promise<ImplementationPlan> {
// Gather context
const relevantFiles = await this.findRelevantFiles(spec);
const existingPatterns = await this.analyzePatterns(relevantFiles);
const response = await llm.complete([
{ role: 'system', content: `You are an expert software developer.
Create an implementation plan for the given specification.
Consider existing code patterns and conventions.
When possible, follow these best practices:
${this.promptGuidance.do.map(p => `- ${p}`).join('\n')}
Avoid these failure patterns:
${this.promptGuidance.avoid.map(p => `- ${p}`).join('\n')}` },
{ role: 'user', content: `Specification: ${spec.description}
Relevant existing code:
${relevantFiles.map(f => `--- ${f.path} ---\n${f.content}`).join('\n\n')}
Codebase patterns:
${JSON.stringify(existingPatterns, null, 2)}
Create a plan with:
1. Files to create or modify
2. Implementation approach
3. Edge cases to handle
4. Tests to add or modify
Format your response as JSON:
{
"steps": ["..."],
"files": ["..."],
"approach": "High-level explanation of the strategy"
}` }
]);
const content = response.choices[0].message.content;
return this.parsePlan(content);
}
private async generateCode(
spec: Specification,
plan: ImplementationPlan,
previousError: VerificationError | null
): Promise<GeneratedCode> {
const errorContext = previousError
? `\n\nPrevious attempt failed with:\n${previousError.message}\n\nSpecifically:\n${previousError.details}\n\nFix this issue in your implementation.`
: '';
const response = await llm.complete([
{ role: 'system', content: `You are an expert software developer.
Generate code that implements the specification.
Follow the plan and codebase conventions exactly.
When possible, follow these best practices:
${this.promptGuidance.do.map(p => `- ${p}`).join('\n')}
Avoid these failure patterns:
${this.promptGuidance.avoid.map(p => `- ${p}`).join('\n')}
${
this.codebase.codebase.conventions
? `Codebase conventions (JSON): ${JSON.stringify(this.codebase.codebase.conventions)}`
: ''
}
Format your response as strict JSON:
{
"files": [
{ "path": "string", "content": "string" }
]
}` },
{ role: 'user', content: `Specification: ${spec.description}
Plan:
${JSON.stringify(plan, null, 2)}
${errorContext}
Generate the complete code. Include all necessary imports.
Do not include backticks or any extra commentary—only valid JSON matching the specified schema.` }
]);
const content = response.choices[0].message.content;
return JSON.parse(content);
}
private async applyCode(code: GeneratedCode) {
for (const file of code.files) {
await this.codebase.writeFile(file.path, file.content);
}
}
private async verify(spec: Specification): Promise<VerificationResult> {
const results: VerificationResult = {
passed: true,
output: '',
failures: [],
summary: ''
};
// Run type checking
const typeCheck = await this.runTypeCheck();
results.output += `Type Check:\n${typeCheck.output}\n\n`;
if (!typeCheck.passed) {
results.passed = false;
results.failures.push({ type: 'typecheck', details: typeCheck.output });
}
// Run tests
const testRun = await this.runTests(spec.testPatterns);
results.output += `Tests:\n${testRun.output}\n\n`;
if (!testRun.passed) {
results.passed = false;
results.failures.push({ type: 'test', details: testRun.output });
}
// Run linting (if configured)
const lint = await this.runLint();
results.output += `Lint:\n${lint.output}\n\n`;
if (!lint.passed) {
results.passed = false;
results.failures.push({ type: 'lint', details: lint.output });
}
results.summary = results.passed
? 'All checks passed'
: `Failed: ${results.failures.map(f => f.type).join(', ')}`;
return results;
}
private async analyzeError(
verification: VerificationResult,
code: GeneratedCode
): Promise<VerificationError> {
const response = await llm.complete([
{ role: 'system', content: `Analyze this verification failure.
Identify the root cause and suggest a specific fix.
Be precise about what line or logic needs to change.` },
{ role: 'user', content: `Generated code:
${code.files.map(f => `--- ${f.path} ---\n${f.content}`).join('\n\n')}
Verification output:
${verification.output}
What specifically went wrong and how should it be fixed?` }
]);
const content = response.choices[0].message.content;
return {
message: verification.summary,
details: content,
failures: verification.failures
};
}
@tool({ description: 'Run TypeScript type checking' })
private async runTypeCheck(): Promise<{ passed: boolean; output: string }> {
// In a real implementation, this would run the TypeScript compiler
// against the current codebase and interpret the result.
//
// For example, in a Node environment you could use:
//
// const { execa } = await import('execa');
// const subprocess = await execa('npx', ['tsc', '--noEmit'], {
// cwd: this.codebase.codebase.rootPath
// });
// const passed = subprocess.exitCode === 0;
// const output = subprocess.stdout + '\n' + subprocess.stderr;
//
// return { passed, output };
//
// The agent then uses `passed` as a boolean signal and `output` as
// structured text for error analysis.
return { passed: true, output: 'No type errors' };
}
@tool({ description: 'Run tests matching pattern' })
private async runTests(patterns: string[]): Promise<{ passed: boolean; output: string }> {
// In a real implementation, this would invoke your test runner CLI
// with the provided patterns and surface its results.
//
// For example, you might run Vitest in band with JSON reporting:
//
// const { execa } = await import('execa');
// const args = ['vitest', '--runInBand', '--reporter', 'json'];
// for (const pattern of patterns) {
// args.push('--include', pattern);
// }
// const subprocess = await execa('npx', args, {
// cwd: this.codebase.codebase.rootPath
// });
// const passed = subprocess.exitCode === 0;
// const output = subprocess.stdout + '\n' + subprocess.stderr;
//
// return { passed, output };
//
// The `output` string becomes the raw material for parsing which tests
// failed and why.
return { passed: true, output: 'All tests passed' };
}
@tool({ description: 'Run linter' })
private async runLint(): Promise<{ passed: boolean; output: string }> {
// In real implementation, would run eslint/prettier
return { passed: true, output: 'No lint errors' };
}
private async findRelevantFiles(spec: Specification): Promise<CodeFile[]> {
const relevant: CodeFile[] = [];
// Search for keywords from spec
const keywords = this.extractKeywords(spec.description);
for (const keyword of keywords) {
const results = await this.codebase.searchCode(keyword);
for (const result of results) {
const file = this.codebase.codebase.files.get(result.path);
if (file && !relevant.includes(file)) {
relevant.push(file);
}
}
}
return relevant.slice(0, 10); // Limit context size
}
private async analyzePatterns(files: CodeFile[]): Promise<CodePatterns> {
// Extract common patterns from existing code
return {
errorHandling: 'try-catch with specific error types',
asyncStyle: 'async/await',
testStyle: 'describe/it blocks with jest',
importStyle: 'named imports from relative paths'
};
}
private extractKeywords(text: string): string[] {
// Simple keyword extraction
return text
.toLowerCase()
.split(/\W+/)
.filter(w => w.length > 3)
.slice(0, 10);
}
private parsePlan(response: string): ImplementationPlan {
// Parse LLM response into structured plan. The system prompt requires
// JSON of the form:
//
// {
// "steps": ["..."],
// "files": ["..."],
// "approach": "..."
// }
//
// To keep the example realistic, we at least attempt to interpret these
// fields while falling back to sensible defaults when missing.
try {
const plan = JSON.parse(response);
return {
steps: Array.isArray(plan.steps) ? plan.steps : [],
files: Array.isArray(plan.files) ? plan.files : [],
approach: typeof plan.approach === 'string' ? plan.approach : response
};
} catch {
return {
steps: [],
files: [],
approach: response
};
}
}
}
interface Specification {
title: string;
description: string;
testPatterns: string[];
requirements: string[];
}
interface ImplementationPlan {
steps: string[];
files: string[];
approach: string;
}
interface GeneratedCode {
files: { path: string; content: string }[];
}
interface VerificationResult {
passed: boolean;
output: string;
failures: { type: string; details: string }[];
summary: string;
}
interface VerificationError {
message: string;
details: string;
failures: { type: string; details: string }[];
}
interface ImplementationResult {
success: boolean;
code: GeneratedCode | null;
attempts: number;
lastError?: VerificationError;
spec: Specification;
}
interface CodePatterns {
errorHandling: string;
asyncStyle: string;
testStyle: string;
importStyle: string;
}