import * as fs from "node:fs"; import * as path from "node:path"; import { BaseAgent, AgentContext, AgentResult } from "./base.js"; interface SolutionSection { title: string; content: string; } export class SolutionWriterAgent extends BaseAgent { readonly name = "solution-writer"; readonly description = "Produces structured solution documents."; readonly workflow = "execute"; async execute(context: AgentContext): Promise { const start = Date.now(); this.log("Writing solution document..."); if (context.backend) { const result = await this.executeViaBackend( context, `Write a structured solution document for: ${context.specification}` ); return { ...result, duration_ms: Date.now() - start }; } const document = this.mechanicalSolutionWrite(context.project_path); return { success: true, output: document, artifacts_created: [], decisions: 0, escalations: 0, duration_ms: Date.now() - start, }; } mechanicalSolutionWrite(projectPath: string): string { const plan = this.readPlan(projectPath); const requirements = this.readRequirements(projectPath); const architecture = this.readArchitecture(projectPath); const sections: SolutionSection[] = [ { title: "Problem Statement", content: this.extractProblemStatement(requirements, plan) }, { title: "Approach", content: this.extractApproach(requirements, architecture) }, { title: "Implementation Plan", content: this.extractImplementationPlan(plan) }, { title: "Verification Criteria", content: this.extractVerificationCriteria(requirements) }, { title: "Risk Assessment", content: this.extractRiskAssessment(architecture) }, ]; return this.fillTemplate(sections); } readPlan(projectPath: string): string { const planPath = path.join(projectPath, ".ciagent", "PLAN.md"); if (!fs.existsSync(planPath)) return ""; try { return fs.readFileSync(planPath, "utf-8"); } catch { return ""; } } readRequirements(projectPath: string): string { const reqPath = path.join(projectPath, ".ciagent", "REQUIREMENTS.md"); if (!fs.existsSync(reqPath)) return ""; try { return fs.readFileSync(reqPath, "utf-8"); } catch { return ""; } } readArchitecture(projectPath: string): string { const archPath = path.join(projectPath, ".ciagent", "ARCHITECTURE.md"); if (!fs.existsSync(archPath)) return ""; try { return fs.readFileSync(archPath, "utf-8"); } catch { return ""; } } fillTemplate(sections: SolutionSection[]): string { const lines: string[] = ["# Solution Document", ""]; for (const section of sections) { lines.push(`## ${section.title}`); lines.push(""); if (section.content.trim()) { lines.push(section.content.trim()); } else { lines.push(`_No ${section.title.toLowerCase()} information available._`); } lines.push(""); } return lines.join("\n"); } extractSectionContent(content: string, headingPatterns: string[]): string { if (!content.trim()) return ""; const sections = content.split(/(?=^#{1,3}\s)/m).filter((s) => s.trim()); for (const section of sections) { for (const pattern of headingPatterns) { if (section.toLowerCase().startsWith(pattern.toLowerCase())) { const lines = section.split("\n"); return lines.slice(1).join("\n").trim(); } } } return ""; } extractProblemStatement(requirements: string, plan: string): string { const content = this.extractSectionContent(requirements, ["# objective", "## objective", "# problem", "## problem", "# goal", "## goal"]); if (content) return content; const planContent = this.extractSectionContent(plan, ["# objective", "## objective", "# problem", "## problem", "# goal", "## goal"]); if (planContent) return planContent; const firstReq = requirements.match(/-?\s*(REQ-\d+[:\s]+)/g); if (firstReq) { return "Requirements to address: " + firstReq.map((m) => m.trim()).join(", "); } if (requirements || plan) { const src = requirements || plan; const firstParagraph = src.split("\n\n")[0]?.trim(); if (firstParagraph && !firstParagraph.startsWith("#")) return firstParagraph; } return "No problem statement could be extracted from project files."; } extractApproach(requirements: string, architecture: string): string { const archContent = this.extractSectionContent(architecture, ["## approach", "### approach", "## design", "### design", "## architecture overview"]); if (archContent) return archContent; const reqContent = this.extractSectionContent(requirements, ["## approach", "### approach", "## design", "### design"]); if (reqContent) return reqContent; const compContent = this.extractSectionContent(architecture, ["## components", "### components"]); if (compContent) return "Architecture-based approach: " + compContent.substring(0, 200); if (architecture) { const firstParagraph = architecture.split("\n\n")[0]?.trim(); if (firstParagraph && !firstParagraph.startsWith("#")) return firstParagraph; } return "No approach information could be extracted from project files."; } extractImplementationPlan(plan: string): string { if (!plan.trim()) return "No implementation plan available — PLAN.md not found."; const taskPattern = plan.match(/\|\s*T-\d+.*\|/g); if (taskPattern) { const lines: string[] = ["Tasks from plan:"]; for (const task of taskPattern) { lines.push(` ${task.trim()}`); } return lines.join("\n"); } const waveSections = plan.split(/(?=^#{1,3}\s+Wave\s)/mi).filter((s) => s.trim()); if (waveSections.length > 1) { const lines: string[] = []; for (const wave of waveSections.slice(1)) { lines.push(wave.trim()); } return lines.join("\n\n"); } const sections = plan.split(/(?=^#{1,3}\s)/m).filter((s) => s.trim()); const lines: string[] = []; for (const section of sections.slice(0, 5)) { lines.push(section.trim()); } return lines.join("\n\n"); } extractVerificationCriteria(requirements: string): string { const lines: string[] = []; const reqIds = requirements.match(/REQ-\d+/g); if (reqIds) { const uniqueIds = [...new Set(reqIds)]; lines.push("Requirements coverage:"); for (const id of uniqueIds) { lines.push(` - ${id}: verified`); } } const verContent = this.extractSectionContent(requirements, ["## verification", "### verification", "## acceptance", "### acceptance", "## testing", "### testing"]); if (verContent) { lines.push(verContent); } if (lines.length === 0) lines.push("No verification criteria extracted — add requirements with REQ-IDs or a Verification section."); return lines.join("\n\n"); } extractRiskAssessment(architecture: string): string { const lines: string[] = []; const riskContent = this.extractSectionContent(architecture, ["## risk", "### risk", "## risks", "### risks", "## concern", "## mitigation"]); if (riskContent) { lines.push(riskContent); } const depContent = this.extractSectionContent(architecture, ["## dependencies", "### dependencies", "## external", "### external"]); if (depContent) { lines.push("Dependency risks:"); lines.push(depContent); } if (lines.length === 0) lines.push("No risks identified — review architecture for potential concerns."); return lines.join("\n\n"); } }