UNPKG

@oliverpople/agency-x

Version:

🚀 **Transform feature requests into production-ready code in seconds**

1,339 lines (1,264 loc) • 45.1 kB
// src/utils/voice.ts import * as say from "say"; var voiceEnabled = false; var setVoiceEnabled = (enabled) => { voiceEnabled = enabled; }; var speak2 = (text) => { if (voiceEnabled) { say.speak(text); } }; // src/utils/errorRecovery.ts var AgentError = class extends Error { constructor(agentName, message, isRetryable = true, originalError) { super(`[${agentName}] ${message}`); this.agentName = agentName; this.isRetryable = isRetryable; this.originalError = originalError; this.name = "AgentError"; } }; var calculateDelay = (attempt, config) => { const delay = config.baseDelay * Math.pow(config.backoffMultiplier, attempt - 1); return Math.min(delay, config.maxDelay); }; var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); var withRetry = async (operation, agentName, config) => { let lastError; for (let attempt = 1; attempt <= config.maxRetries; attempt++) { try { console.log(`\u{1F504} [${agentName}] Attempt ${attempt}/${config.maxRetries}`); return await operation(); } catch (error) { lastError = error; if (error instanceof AgentError && !error.isRetryable) { throw error; } if (attempt < config.maxRetries) { const delay = calculateDelay(attempt, config); console.warn(`\u26A0\uFE0F [${agentName}] Attempt ${attempt} failed: ${error}. Retrying in ${delay}ms...`); await sleep(delay); } } } throw new AgentError( agentName, `Failed after ${config.maxRetries} attempts. Last error: ${lastError.message}`, false, lastError ); }; var withTimeout = (promise, timeoutMs, agentName) => { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new AgentError(agentName, `Operation timed out after ${timeoutMs}ms`, true)); }, timeoutMs); promise.then(resolve).catch(reject).finally(() => clearTimeout(timeoutId)); }); }; var createAgentRunner = (config) => { return async (operation) => { const timedOperation = () => withTimeout(operation(), config.timeout, config.name); return withRetry(timedOperation, config.name, config.retryConfig); }; }; var DEFAULT_RETRY_CONFIG = { maxRetries: 3, baseDelay: 1e3, // 1 second maxDelay: 3e4, // 30 seconds backoffMultiplier: 2 }; var DEFAULT_AGENT_CONFIG = { critical: false, retryConfig: DEFAULT_RETRY_CONFIG, timeout: 9e4 // 90 seconds }; var CRITICAL_RETRY_CONFIG = { maxRetries: 5, baseDelay: 2e3, // 2 seconds maxDelay: 6e4, // 60 seconds backoffMultiplier: 2 }; // src/utils/logger.ts import chalk from "chalk"; var agentStyles = { productManager: chalk.bold.blue, frontendDeveloper: chalk.green, backendDeveloper: chalk.yellow.italic, qaEngineer: chalk.red.underline, documentationAgent: chalk.cyan, fullstackIntegrator: chalk.magenta, infraEngineer: chalk.gray, securityEngineer: chalk.yellow, uxDesigner: chalk.blue, copywriter: chalk.green, releaseManager: chalk.magenta.bold, aiPromptEngineer: chalk.blue.italic, dataEngineer: chalk.yellow, orchestratorAgent: chalk.bold.magenta, voiceNarratorAgent: chalk.italic.gray, reviewerAgent: chalk.bold.yellow, assumptionChallenger: chalk.bold.red, userEmpathyAgent: chalk.bold.green }; var log = (agentName, message, icon = "\u{1F4DD}") => { const styledMessage = `${agentStyles[agentName](`[${agentName}]`)} ${icon} ${message}`; console.log(styledMessage); }; var spinner = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"]; var agentIntervals = /* @__PURE__ */ new Map(); var startThinking = (agentName) => { stopThinking(agentName); let i = 0; const interval = setInterval(() => { i = (i + 1) % spinner.length; process.stdout.write(`\r${agentStyles[agentName](`[${agentName}]`)} ${spinner[i]} is thinking...`); }, 100); agentIntervals.set(agentName, interval); }; var stopThinking = (agentName) => { const interval = agentIntervals.get(agentName); if (interval) { clearInterval(interval); agentIntervals.delete(agentName); process.stdout.write("\r" + " ".repeat(50) + "\r"); } }; var createLogger = (agentName) => ({ log: (message) => log(agentName, message), start: () => startThinking(agentName), stop: () => stopThinking(agentName), info: (message) => log(agentName, chalk.white(message), "\u2139\uFE0F"), success: (message) => log(agentName, chalk.greenBright(message), "\u2705"), error: (message) => log(agentName, chalk.redBright(message), "\u274C"), warn: (message) => log(agentName, chalk.yellowBright(message), "\u26A0\uFE0F"), debug: (message) => { if (process.env.DEBUG === "true") { log(agentName, chalk.gray(message), "\u{1F41B}"); } } }); var orchestratorLogger = createLogger("orchestratorAgent"); var cleanupAllIntervals = () => { agentIntervals.forEach((_, agentName) => { stopThinking(agentName); }); }; process.on("exit", cleanupAllIntervals); process.on("SIGINT", () => { cleanupAllIntervals(); process.exit(0); }); process.on("SIGTERM", () => { cleanupAllIntervals(); process.exit(0); }); // src/utils/contextStore.ts import fs from "fs/promises"; import path from "path"; var currentContext = null; var sessionsDir = path.join(process.cwd(), "sessions"); var backupDir = path.join(sessionsDir, "backups"); var ensureDirectories = async () => { await fs.mkdir(sessionsDir, { recursive: true }); await fs.mkdir(backupDir, { recursive: true }); }; var generateSpecId = () => { const date = /* @__PURE__ */ new Date(); const year = date.getFullYear(); const month = (date.getMonth() + 1).toString().padStart(2, "0"); const day = date.getDate().toString().padStart(2, "0"); const randomId = Math.random().toString(36).substring(2, 6).toUpperCase(); return `SPEC-${year}${month}${day}-${randomId}`; }; var validateContext = (context) => { if (!context.specId || typeof context.specId !== "string") { throw new Error("Invalid specId in context"); } if (!context.featurePrompt || typeof context.featurePrompt !== "string") { throw new Error("Invalid featurePrompt in context"); } if (!Array.isArray(context.log)) { throw new Error("Invalid log in context"); } return context; }; var validateAgentOutput = (output) => { if (typeof output.completed !== "boolean") { throw new Error("Agent output must have completed boolean"); } return output; }; var createContext = (featurePrompt) => { const specId = generateSpecId(); const now = (/* @__PURE__ */ new Date()).toISOString(); currentContext = { specId, featurePrompt, spec: {}, agents: {}, finalBundle: {}, log: [], metadata: { createdAt: now, lastUpdated: now, version: "1.0.0" } }; return currentContext; }; var getContext = () => { if (!currentContext) { throw new Error("Context not initialized. Call createContext first."); } return currentContext; }; var updateContext = (updates) => { if (!currentContext) { throw new Error("Context not initialized. Call createContext first."); } const updatedContext = { ...currentContext, ...updates, metadata: { ...currentContext.metadata, ...updates.metadata, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() } }; try { currentContext = validateContext(updatedContext); return currentContext; } catch (error) { console.warn("Context validation failed, keeping previous context:", error); throw new Error(`Context update failed validation: ${error}`); } }; var updateAgentOutput = (agentName, output) => { if (!currentContext) { throw new Error("Context not initialized"); } const existingAgent = currentContext.agents[agentName] || { completed: false }; const updatedAgent = { ...existingAgent, ...output }; try { validateAgentOutput(updatedAgent); updateContext({ agents: { ...currentContext.agents, [agentName]: updatedAgent } }); } catch (error) { console.error(`Agent output validation failed for ${agentName}:`, error); throw new Error(`Agent output validation failed: ${error}`); } }; var logToContext = (message) => { if (currentContext) { currentContext.log.push(message); currentContext.metadata.lastUpdated = (/* @__PURE__ */ new Date()).toISOString(); } }; var createBackup = async (filePath) => { try { const stats = await fs.stat(filePath); if (stats.isFile()) { const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"); const fileName = path.basename(filePath, ".json"); const backupPath = path.join(backupDir, `${fileName}-${timestamp}.json`); await fs.copyFile(filePath, backupPath); console.log(`\u{1F4BE} Backup created: ${backupPath}`); return backupPath; } } catch (error) { console.warn("Failed to create backup:", error); } return ""; }; var saveContext = async (createBackupFlag = true) => { if (!currentContext) { throw new Error("Context not initialized."); } await ensureDirectories(); try { validateContext(currentContext); } catch (error) { throw new Error(`Cannot save invalid context: ${error}`); } const filePath = path.join(sessionsDir, `${currentContext.specId}.json`); if (createBackupFlag) { await createBackup(filePath); } const tempPath = `${filePath}.tmp`; try { await fs.writeFile(tempPath, JSON.stringify(currentContext, null, 2)); await fs.rename(tempPath, filePath); console.log(`\u2705 Context saved successfully: ${filePath}`); return filePath; } catch (error) { try { await fs.unlink(tempPath); } catch { } throw new Error(`Failed to save context: ${error}`); } }; var loadContext = async (specId) => { const filePath = path.join(sessionsDir, `${specId}.json`); try { const data = await fs.readFile(filePath, "utf-8"); const parsedContext = JSON.parse(data); currentContext = validateContext(parsedContext); console.log(`\u2705 Context loaded successfully: ${filePath}`); return currentContext; } catch (error) { throw new Error(`Failed to load or validate context from ${filePath}: ${error}`); } }; // src/llm/claudeClient.ts import Anthropic from "@anthropic-ai/sdk"; var anthropic = null; var getAnthropic = () => { if (!anthropic) { const apiKey = process.env.CLAUDE_API_KEY; if (!apiKey) { throw new Error("CLAUDE_API_KEY environment variable is not set"); } anthropic = new Anthropic({ apiKey }); } return anthropic; }; var getClaudeClient = () => { return { generate: async (prompt) => { const client = getAnthropic(); const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error("Claude API request timed out after 90 seconds")), 9e4); }); try { const response = await Promise.race([ client.messages.create({ model: "claude-3-opus-20240229", max_tokens: 2048, messages: [{ role: "user", content: prompt }] }), timeoutPromise ]); return response.content[0].text; } catch (error) { console.error("Claude API error:", error); throw error; } } }; }; // src/llm/openaiClient.ts import OpenAI from "openai"; var openai = null; var getOpenAI = () => { if (!openai) { const apiKey = process.env.OPENAI_API_KEY; if (!apiKey) { throw new Error("OPENAI_API_KEY environment variable is not set"); } openai = new OpenAI({ apiKey }); } return openai; }; var getOpenAIClient = () => { return { generate: async (prompt) => { const client = getOpenAI(); const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { reject(new Error("OpenAI API request timed out after 90 seconds")); }, 9e4); }); try { const apiPromise = client.chat.completions.create({ model: "gpt-3.5-turbo", messages: [{ role: "user", content: prompt }] }); const response = await Promise.race([ apiPromise, timeoutPromise ]); return response.choices[0].message.content || ""; } catch (error) { console.error("[ERROR] OpenAI API error:", error); throw error; } } }; }; // src/llm/llmRouter.ts var defaultProvider = "openai"; var getLlmClient = () => { switch (defaultProvider) { case "claude": return getClaudeClient(); case "openai": return getOpenAIClient(); default: throw new Error(`Unknown LLM provider: ${defaultProvider}`); } }; // src/utils/promptTemplates.ts var productManagerPrompt = ` You are a world-class Product Manager. Your task is to take a high-level feature prompt and convert it into a structured product specification. The output must be a single, valid JSON object with the following keys: "title", "userStories", "acceptanceCriteria", and "edgeCases". - "title": A concise, descriptive title for the feature. - "userStories": An array of strings, each representing a user story in the format "As a [user type], I want to [action] so that [benefit]". - "acceptanceCriteria": An array of strings, each describing a specific, testable condition that must be met for the feature to be considered complete. - "edgeCases": An array of strings, each identifying a potential edge case, error state, or unexpected user behavior that needs to be considered. Do not include any other text, explanations, or markdown formatting in your response. The output must be only the JSON object. Feature Prompt: "{{featurePrompt}}" `; var backendDeveloperPrompt = ` You are a world-class Backend Developer. Your task is to take a product specification and generate the necessary server-side code. The output must be a single, valid code block that implements the described feature using Express.js. Do not include any other text, explanations, or markdown formatting in your response. The output must be only the code. Product Specification: {{spec}} `; var frontendDeveloperPrompt = ` You are a world-class Frontend Developer. Your task is to take a product specification and generate the necessary client-side code. The output must be a single, valid React component that implements the described feature. Do not include any other text, explanations, or markdown formatting in your response. The output must be only the code. Product Specification: {{spec}} `; var qaEngineerPrompt = ` You are a world-class QA Engineer. Your task is to take a product specification and the generated code and write tests for it. The output must be a single, valid code block that contains the tests, using Jest and React Testing Library. Do not include any other text, explanations, or markdown formatting in your response. The output must be only the code. Product Specification: {{spec}} Backend Code: {{backendCode}} Frontend Code: {{frontendCode}} `; var documentationAgentPrompt = ` You are a world-class Technical Writer. Your task is to take a product specification and generate documentation for it. The output must be a single, valid Markdown document. Do not include any other text, explanations, or markdown formatting in your response. The output must be only the Markdown. Product Specification: {{spec}} `; var infraEngineerPrompt = ` You are a world-class Infrastructure Engineer. Your task is to analyze the project's code and generate a production-ready, multi-stage Dockerfile to build and run the application. The output must be a single, valid Dockerfile. Do not include any other text, explanations, or markdown formatting in your response. The output must be only the Dockerfile code. Backend Code: {{backendCode}} Frontend Code: {{frontendCode}} `; var securityEngineerPrompt = ` You are a world-class Security Engineer. Your task is to audit the provided code and product specification for potential vulnerabilities. The output must be a Markdown report outlining potential risks, such as XSS, SQLi, or authentication gaps, with recommendations for mitigation. Product Specification: {{spec}} Backend Code: {{backendCode}} Frontend Code: {{frontendCode}} `; var uxDesignerPrompt = ` You are a world-class UX Designer. Your task is to analyze a product specification and provide a text-based wireframe and interaction flow summary. The output must be a Markdown document outlining the user flow, key UI elements, and accessibility considerations. Product Specification: {{spec}} `; var copywriterPrompt = ` You are a world-class UX Copywriter. Your task is to review a product specification and the UI code to provide clear, concise, and helpful microcopy. The output must be a JSON object mapping component IDs or class names to suggested copy for labels, tooltips, and error messages. Product Specification: {{spec}} Frontend Code: {{frontendCode}} `; var releaseManagerPrompt = ` You are a world-class Release Manager. Your task is to review the entire project context and prepare for a production release. The output must be a Markdown document containing a release checklist, including a suggested SemVer bump, a summary for the changelog, and a list of pre-flight checks. Project Context: {{context}} `; var aiPromptEngineerPrompt = ` You are a world-class AI Prompt Engineer. Your task is to analyze a product specification and generate optimized Claude/GPT prompt templates for any AI features in the product. The output must be a JSON object containing structured prompts with few-shot examples. Product Specification: {{spec}} `; var dataEngineerPrompt = ` You are a world-class Data Engineer. Your task is to analyze a product specification and generate analytics event schemas and tracking plans. The output must be a JSON object defining event maps, schema extensions, and tracking requirements. Product Specification: {{spec}} `; var reviewerAgentPrompt = ` You are a world-class Code Reviewer. Your task is to review all generated code and documentation for quality, consistency, and best practices. The output must be a Markdown report with specific feedback and improvement suggestions. Code to Review: {{code}} Documentation: {{docs}} `; var assumptionChallengerPrompt = ` You are a world-class Devil's Advocate. Your task is to identify potential gaps, assumptions, or edge cases in the product specification that may have been overlooked. The output must be a list of challenging questions and potential issues that should be addressed. Product Specification: {{spec}} `; var userEmpathyAgentPrompt = ` You are a world-class User Experience Advocate. Your task is to simulate a real user interacting with the proposed feature and identify usability concerns. The output must be a Markdown report describing the user journey, potential friction points, and suggestions for improvement. Product Specification: {{spec}} Frontend Code: {{frontendCode}} `; var jsonRepairPrompt = ` You are a JSON repair bot. Your task is to take a piece of text that is supposed to be a JSON object and fix it so that it is a single, valid JSON object. Do not include any other text, explanations, or markdown formatting in your response. The output must be only the repaired JSON object. Invalid JSON: {{invalidJson}} `; // src/agents/productManager.ts var logger = createLogger("productManager"); var runProductManager = async () => { logger.start(); const context = getContext(); const { featurePrompt } = context; const llmClient = getLlmClient(); const prompt = productManagerPrompt.replace("{{featurePrompt}}", featurePrompt); let response = await llmClient.generate(prompt); let spec; try { spec = JSON.parse(response); } catch (error) { logger.error("Invalid JSON response from LLM. Attempting to repair..."); const repairPrompt = jsonRepairPrompt.replace("{{invalidJson}}", response); response = await llmClient.generate(repairPrompt); try { spec = JSON.parse(response); } catch (error2) { logger.error("Failed to repair JSON response. Skipping spec generation."); return; } } updateContext({ spec }); logger.stop(); logger.success("Generated product spec."); return spec; }; // src/agents/frontendDeveloper.ts var logger2 = createLogger("frontendDeveloper"); var runFrontendDeveloper = async () => { logger2.start(); const context = getContext(); const { spec } = context; const llmClient = getLlmClient(); const prompt = frontendDeveloperPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)); const code = await llmClient.generate(prompt); updateContext({ agents: { ...context.agents || {}, frontendDeveloper: { output: code, completed: true } } }); logger2.stop(); logger2.success("Generated frontend code."); return code; }; // src/agents/backendDeveloper.ts var logger3 = createLogger("backendDeveloper"); var runBackendDeveloper = async () => { const existingContext = getContext(); if (existingContext.agents?.backendDeveloper?.completed) { return existingContext.agents.backendDeveloper.output; } logger3.start(); try { const context = getContext(); const { spec } = context; const llmClient = getLlmClient(); const prompt = backendDeveloperPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)); const code = await llmClient.generate(prompt); updateContext({ agents: { ...context.agents || {}, backendDeveloper: { output: code, completed: true } } }); logger3.success("Generated backend code."); return code; } catch (error) { console.error("[ERROR] Backend developer failed:", error); throw error; } finally { logger3.stop(); } }; // src/agents/fullstackIntegrator.ts var logger4 = createLogger("fullstackIntegrator"); var runFullstackIntegrator = async () => { logger4.start(); const context = getContext(); logger4.info("Ensuring FE and BE contract alignment."); updateContext({ agents: { ...context.agents || {}, fullstackIntegrator: { completed: true } } }); logger4.stop(); logger4.success("Fullstack integration complete."); return true; }; // src/agents/qaEngineer.ts var logger5 = createLogger("qaEngineer"); var runQaEngineer = async () => { logger5.start(); const context = getContext(); const { spec, agents = {} } = context; const backendOutput = agents.backendDeveloper?.output || "No backend code generated yet"; const frontendOutput = agents.frontendDeveloper?.output || "No frontend code generated yet"; const llmClient = getLlmClient(); const prompt = qaEngineerPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)).replace("{{backendCode}}", backendOutput).replace("{{frontendCode}}", frontendOutput); const tests = await llmClient.generate(prompt); updateContext({ agents: { ...agents, qaEngineer: { tests, passed: true, completed: true } } }); logger5.stop(); logger5.success("Generated QA tests."); return tests; }; // src/agents/documentationAgent.ts var logger6 = createLogger("documentationAgent"); var runDocumentationAgent = async () => { logger6.start(); const context = getContext(); const { spec } = context; const llmClient = getLlmClient(); const prompt = documentationAgentPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)); const doc = await llmClient.generate(prompt); updateContext({ agents: { ...context.agents || {}, documentationAgent: { doc, completed: true } } }); logger6.stop(); logger6.success("Generated documentation."); return doc; }; // src/agents/infraEngineer.ts var logger7 = createLogger("infraEngineer"); var runInfraEngineer = async () => { logger7.start(); const context = getContext(); const { agents = {} } = context; const backendOutput = agents.backendDeveloper?.output || "No backend code generated yet"; const frontendOutput = agents.frontendDeveloper?.output || "No frontend code generated yet"; const llmClient = getLlmClient(); const prompt = infraEngineerPrompt.replace("{{backendCode}}", backendOutput).replace("{{frontendCode}}", frontendOutput); const dockerfile = await llmClient.generate(prompt); updateContext({ agents: { ...agents, infraEngineer: { output: dockerfile, completed: true } } }); logger7.stop(); logger7.success("Generated Dockerfile."); return dockerfile; }; // src/agents/securityEngineer.ts var logger8 = createLogger("securityEngineer"); var runSecurityEngineer = async () => { logger8.start(); const context = getContext(); const { spec, agents = {} } = context; const backendOutput = agents.backendDeveloper?.output || "No backend code generated yet"; const frontendOutput = agents.frontendDeveloper?.output || "No frontend code generated yet"; const llmClient = getLlmClient(); const prompt = securityEngineerPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)).replace("{{backendCode}}", backendOutput).replace("{{frontendCode}}", frontendOutput); const report = await llmClient.generate(prompt); updateContext({ agents: { ...agents, securityEngineer: { output: report, completed: true } } }); logger8.stop(); logger8.success("Generated security report."); return report; }; // src/agents/uxDesigner.ts var logger9 = createLogger("uxDesigner"); var runUxDesigner = async () => { logger9.start(); const context = getContext(); const { spec } = context; const llmClient = getLlmClient(); const prompt = uxDesignerPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)); const report = await llmClient.generate(prompt); updateContext({ agents: { ...context.agents || {}, uxDesigner: { output: report, completed: true } } }); logger9.stop(); logger9.success("Generated UX design report."); return report; }; // src/agents/copywriter.ts var logger10 = createLogger("copywriter"); var runCopywriter = async () => { logger10.start(); const context = getContext(); const { spec, agents = {} } = context; const frontendOutput = agents.frontendDeveloper?.output || "No frontend code generated yet"; const llmClient = getLlmClient(); const prompt = copywriterPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)).replace("{{frontendCode}}", frontendOutput); let response = await llmClient.generate(prompt); let copy; try { copy = JSON.parse(response); } catch (error) { logger10.error("Invalid JSON response from LLM. Attempting to repair..."); const repairPrompt = jsonRepairPrompt.replace("{{invalidJson}}", response); response = await llmClient.generate(repairPrompt); try { copy = JSON.parse(response); } catch (error2) { logger10.error("Failed to repair JSON response. Skipping copy generation."); return; } } updateContext({ agents: { ...agents, copywriter: { output: copy, completed: true } } }); logger10.stop(); logger10.success("Generated microcopy."); return copy; }; // src/agents/releaseManager.ts var logger11 = createLogger("releaseManager"); var runReleaseManager = async () => { logger11.start(); const context = getContext(); const llmClient = getLlmClient(); const prompt = releaseManagerPrompt.replace("{{context}}", JSON.stringify(context, null, 2)); const report = await llmClient.generate(prompt); updateContext({ agents: { ...context.agents || {}, releaseManager: { output: report, completed: true } } }); logger11.stop(); logger11.success("Generated release plan."); return report; }; // src/agents/aiPromptEngineer.ts var logger12 = createLogger("aiPromptEngineer"); var runAiPromptEngineer = async () => { logger12.start(); const context = getContext(); const { spec } = context; const llmClient = getLlmClient(); const prompt = aiPromptEngineerPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)); let response = await llmClient.generate(prompt); let prompts; try { prompts = JSON.parse(response); } catch (error) { logger12.error("Invalid JSON response from LLM. Attempting to repair..."); const repairPrompt = jsonRepairPrompt.replace("{{invalidJson}}", response); response = await llmClient.generate(repairPrompt); try { prompts = JSON.parse(response); } catch (error2) { logger12.error("Failed to repair JSON response. Skipping AI prompt generation."); return; } } updateContext({ agents: { ...context.agents || {}, aiPromptEngineer: { output: prompts, completed: true } } }); logger12.stop(); logger12.success("Generated AI prompts."); return prompts; }; // src/agents/dataEngineer.ts var logger13 = createLogger("dataEngineer"); var runDataEngineer = async () => { logger13.start(); const context = getContext(); const { spec } = context; const llmClient = getLlmClient(); const prompt = dataEngineerPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)); let response = await llmClient.generate(prompt); let events; try { events = JSON.parse(response); } catch (error) { logger13.error("Invalid JSON response from LLM. Attempting to repair..."); const repairPrompt = jsonRepairPrompt.replace("{{invalidJson}}", response); response = await llmClient.generate(repairPrompt); try { events = JSON.parse(response); } catch (error2) { logger13.error("Failed to repair JSON response. Skipping analytics event generation."); return; } } updateContext({ agents: { ...context.agents || {}, dataEngineer: { output: events, completed: true } } }); logger13.stop(); logger13.success("Generated analytics events."); return events; }; // src/agents/reviewerAgent.ts var logger14 = createLogger("reviewerAgent"); var runReviewerAgent = async () => { logger14.start(); const context = getContext(); const { agents = {} } = context; const backendOutput = agents.backendDeveloper?.output || "No backend code generated yet"; const frontendOutput = agents.frontendDeveloper?.output || "No frontend code generated yet"; const docsOutput = agents.documentationAgent?.doc || "No documentation generated yet"; const llmClient = getLlmClient(); const prompt = reviewerAgentPrompt.replace("{{code}}", JSON.stringify({ backend: backendOutput, frontend: frontendOutput }, null, 2)).replace("{{docs}}", docsOutput); const report = await llmClient.generate(prompt); updateContext({ agents: { ...agents, reviewerAgent: { output: report, completed: true } } }); logger14.stop(); logger14.success("Generated review."); return report; }; // src/agents/assumptionChallenger.ts var logger15 = createLogger("assumptionChallenger"); var runAssumptionChallenger = async () => { logger15.start(); const context = getContext(); const { spec } = context; const llmClient = getLlmClient(); const prompt = assumptionChallengerPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)); const report = await llmClient.generate(prompt); updateContext({ agents: { ...context.agents || {}, assumptionChallenger: { output: report, completed: true } } }); logger15.stop(); logger15.success("Generated assumption challenge report."); return report; }; // src/agents/userEmpathyAgent.ts var logger16 = createLogger("userEmpathyAgent"); var runUserEmpathyAgent = async () => { logger16.start(); const context = getContext(); const { spec, agents = {} } = context; const frontendOutput = agents.frontendDeveloper?.output || "No frontend code generated yet"; const llmClient = getLlmClient(); const prompt = userEmpathyAgentPrompt.replace("{{spec}}", JSON.stringify(spec, null, 2)).replace("{{frontendCode}}", frontendOutput); const report = await llmClient.generate(prompt); updateContext({ agents: { ...agents, userEmpathyAgent: { output: report, completed: true } } }); logger16.stop(); logger16.success("Generated user empathy report."); return report; }; // src/configs/agentConfigs.ts var AGENT_DEFINITIONS = [ // Phase 1: Product Specification (Critical) { name: "productManager", dependencies: [], critical: true, runner: runProductManager, config: { retryConfig: CRITICAL_RETRY_CONFIG, timeout: 12e4 // 2 minutes for critical agent } }, // Phase 2: Core Development (Parallel, both critical) { name: "frontendDeveloper", dependencies: ["productManager"], critical: true, runner: runFrontendDeveloper, config: { retryConfig: CRITICAL_RETRY_CONFIG, timeout: 12e4 } }, { name: "backendDeveloper", dependencies: ["productManager"], critical: true, runner: runBackendDeveloper, config: { retryConfig: CRITICAL_RETRY_CONFIG, timeout: 12e4 } }, // Phase 3: Integration (Critical, depends on both frontend and backend) { name: "fullstackIntegrator", dependencies: ["frontendDeveloper", "backendDeveloper"], critical: true, runner: runFullstackIntegrator, config: { retryConfig: CRITICAL_RETRY_CONFIG, timeout: 9e4 } }, // Phase 4: Testing (Critical, depends on integration) { name: "qaEngineer", dependencies: ["fullstackIntegrator"], critical: true, runner: runQaEngineer, config: { retryConfig: CRITICAL_RETRY_CONFIG, timeout: 12e4 } }, // Phase 5: Parallel Support Tasks (Non-critical) { name: "documentationAgent", dependencies: ["fullstackIntegrator"], critical: false, runner: runDocumentationAgent, config: { retryConfig: DEFAULT_RETRY_CONFIG, timeout: 9e4 } }, { name: "infraEngineer", dependencies: ["fullstackIntegrator"], critical: false, runner: runInfraEngineer, config: { retryConfig: DEFAULT_RETRY_CONFIG, timeout: 9e4 } }, { name: "securityEngineer", dependencies: ["fullstackIntegrator"], critical: false, runner: runSecurityEngineer, config: { retryConfig: DEFAULT_RETRY_CONFIG, timeout: 9e4 } }, { name: "uxDesigner", dependencies: ["frontendDeveloper"], critical: false, runner: runUxDesigner, config: { retryConfig: DEFAULT_RETRY_CONFIG, timeout: 9e4 } }, // Phase 6: Content and Analysis (Parallel, non-critical) { name: "copywriter", dependencies: ["frontendDeveloper"], critical: false, runner: runCopywriter, config: { retryConfig: DEFAULT_RETRY_CONFIG, timeout: 9e4 } }, { name: "aiPromptEngineer", dependencies: ["qaEngineer"], critical: false, runner: runAiPromptEngineer, config: { retryConfig: DEFAULT_RETRY_CONFIG, timeout: 9e4 } }, { name: "dataEngineer", dependencies: ["fullstackIntegrator"], critical: false, runner: runDataEngineer, config: { retryConfig: DEFAULT_RETRY_CONFIG, timeout: 9e4 } }, // Phase 7: Review and Analysis (Parallel, non-critical) { name: "reviewerAgent", dependencies: ["qaEngineer", "documentationAgent"], critical: false, runner: runReviewerAgent, config: { retryConfig: DEFAULT_RETRY_CONFIG, timeout: 9e4 } }, { name: "assumptionChallenger", dependencies: ["qaEngineer"], critical: false, runner: runAssumptionChallenger, config: { retryConfig: DEFAULT_RETRY_CONFIG, timeout: 9e4 } }, { name: "userEmpathyAgent", dependencies: ["uxDesigner"], critical: false, runner: runUserEmpathyAgent, config: { retryConfig: DEFAULT_RETRY_CONFIG, timeout: 9e4 } }, // Phase 8: Release Management (Final step, critical) { name: "releaseManager", dependencies: ["qaEngineer", "documentationAgent", "infraEngineer", "securityEngineer"], critical: true, runner: runReleaseManager, config: { retryConfig: CRITICAL_RETRY_CONFIG, timeout: 9e4 } } ]; var getAgentsByPhase = () => { const phases = /* @__PURE__ */ new Map(); phases.set(1, ["productManager"]); phases.set(2, ["frontendDeveloper", "backendDeveloper"]); phases.set(3, ["fullstackIntegrator"]); phases.set(4, ["qaEngineer"]); phases.set(5, ["documentationAgent", "infraEngineer", "securityEngineer", "uxDesigner", "copywriter"]); phases.set(6, ["aiPromptEngineer", "dataEngineer"]); phases.set(7, ["reviewerAgent", "assumptionChallenger", "userEmpathyAgent"]); phases.set(8, ["releaseManager"]); return phases; }; var getCriticalAgents = () => { return AGENT_DEFINITIONS.filter((agent) => agent.critical).map((agent) => agent.name); }; // src/utils/agentOrchestrator.ts var DependencyGraph = class { agents = /* @__PURE__ */ new Map(); completed = /* @__PURE__ */ new Set(); failed = /* @__PURE__ */ new Set(); running = /* @__PURE__ */ new Set(); addAgent(definition) { this.agents.set(definition.name, definition); } canRunAgent(agentName) { const agent = this.agents.get(agentName); if (!agent) return false; if (this.completed.has(agentName) || this.failed.has(agentName) || this.running.has(agentName)) { return false; } return agent.dependencies.every((dep) => this.completed.has(dep)); } getReadyAgents() { return Array.from(this.agents.keys()).filter((name) => this.canRunAgent(name)); } markCompleted(agentName) { this.completed.add(agentName); this.running.delete(agentName); } markFailed(agentName) { this.failed.add(agentName); this.running.delete(agentName); } markRunning(agentName) { this.running.add(agentName); } getStatus() { return { total: this.agents.size, completed: this.completed.size, failed: this.failed.size, running: this.running.size, pending: this.agents.size - this.completed.size - this.failed.size - this.running.size }; } getCriticalFailures() { return Array.from(this.failed).filter((name) => { const agent = this.agents.get(name); return agent?.critical === true; }); } isComplete() { return this.completed.size + this.failed.size === this.agents.size; } canContinue() { const criticalFailures = this.getCriticalFailures(); const hasReadyAgents = this.getReadyAgents().length > 0; const hasRunningAgents = this.running.size > 0; return criticalFailures.length === 0 && (hasReadyAgents || hasRunningAgents); } }; var AgentOrchestrator = class { graph = new DependencyGraph(); maxConcurrent; saveInterval = 3e4; // Save every 30 seconds saveTimer; onProgress; constructor(maxConcurrent = 5, onProgress) { this.maxConcurrent = maxConcurrent; this.onProgress = onProgress; this.startPeriodicSave(); } startPeriodicSave() { this.saveTimer = setInterval(async () => { try { await saveContext(false); } catch (error) { console.warn("Periodic save failed:", error); } }, this.saveInterval); } stopPeriodicSave() { if (this.saveTimer) { clearInterval(this.saveTimer); this.saveTimer = void 0; } } addAgent(definition) { this.graph.addAgent(definition); } async runAgent(agentName) { const agent = this.graph.agents.get(agentName); if (!agent) throw new Error(`Agent ${agentName} not found`); const config = { name: agentName, dependencies: agent.dependencies, critical: agent.critical, ...DEFAULT_AGENT_CONFIG, ...agent.critical ? { retryConfig: CRITICAL_RETRY_CONFIG } : {}, ...agent.config }; const runner = createAgentRunner(config); const startTime = Date.now(); try { this.graph.markRunning(agentName); updateAgentOutput(agentName, { completed: false, startTime, retryCount: 0 }); const isVerbose = process.env.DEBUG === "true"; if (isVerbose) console.log(`\u{1F680} Starting agent: ${agentName}`); const output = await runner(agent.runner); const executionTime = Date.now() - startTime; updateAgentOutput(agentName, { output, completed: true, executionTime }); this.graph.markCompleted(agentName); if (isVerbose) console.log(`\u2705 Agent completed: ${agentName} (${executionTime}ms)`); if (this.onProgress) { const status = this.graph.getStatus(); this.onProgress({ completed: status.completed, failed: status.failed, running: status.running }); } } catch (error) { const executionTime = Date.now() - startTime; const agentError = error instanceof AgentError ? error : new AgentError(agentName, error.message); updateAgentOutput(agentName, { completed: false, error: agentError.message, executionTime }); this.graph.markFailed(agentName); if (agent.critical) { console.error(`\u{1F4A5} Critical agent failed: ${agentName} - ${agentError.message}`); throw agentError; } else { console.warn(`\u26A0\uFE0F Non-critical agent failed: ${agentName} - ${agentError.message}`); } } } async runAll() { const isVerbose = process.env.DEBUG === "true"; if (isVerbose) console.log("\u{1F3AC} Starting orchestration..."); try { while (!this.graph.isComplete() && this.graph.canContinue()) { const readyAgents = this.graph.getReadyAgents(); const currentlyRunning = this.graph.running.size; if (readyAgents.length === 0 && currentlyRunning === 0) { if (isVerbose) console.warn("\u26A0\uFE0F No agents ready to run and none currently running. Possible dependency deadlock."); break; } const availableSlots = this.maxConcurrent - currentlyRunning; const agentsToStart = readyAgents.slice(0, availableSlots); if (agentsToStart.length > 0) { if (isVerbose) { console.log(`\u{1F4CA} Status: ${JSON.stringify(this.graph.getStatus())}`); console.log(`\u{1F504} Starting ${agentsToStart.length} agents: ${agentsToStart.join(", ")}`); } const promises = agentsToStart.map( (agentName) => this.runAgent(agentName).catch((error) => { if (error instanceof AgentError && this.graph.agents.get(agentName)?.critical) { throw error; } }) ); await Promise.allSettled(promises); } else { await new Promise((resolve) => setTimeout(resolve, 1e3)); } } const status = this.graph.getStatus(); const criticalFailures = this.graph.getCriticalFailures(); if (isVerbose) console.log(`\u{1F4CA} Final Status: ${JSON.stringify(status)}`); if (criticalFailures.length > 0) { throw new Error(`Orchestration failed due to critical agent failures: ${criticalFailures.join(", ")}`); } if (status.failed > 0 && isVerbose) { console.warn(`\u26A0\uFE0F Orchestration completed with ${status.failed} non-critical failures`); } if (isVerbose) console.log("\u{1F389} Orchestration completed successfully!"); } finally { this.stopPeriodicSave(); try { await saveContext(); } catch (error) { if (isVerbose) console.error("Failed to save final context:", error); } } } getStatus() { return this.graph.getStatus(); } // Cleanup method cleanup() { this.stopPeriodicSave(); } }; // src/orchestrator/orchestratorAgent.ts var runOrchestrator = async (options) => { const { feature, voice = false, resume, maxConcurrent = 5, onStop, onProgress } = options; setVoiceEnabled(voice); let orchestrator = null; const isVerbose = process.env.DEBUG === "true"; try { if (resume) { await loadContext(resume); if (isVerbose) orchestratorLogger.info(`Resuming orchestration for specId: ${resume}`); } else { createContext(feature); if (isVerbose) orchestratorLogger.info(`Starting orchestration for feature: "${feature}"`); } if (isVerbose) { const phases = getAgentsByPhase(); const criticalAgents = getCriticalAgents(); orchestratorLogger.info(`Orchestration Plan:`); phases.forEach((agents, phase) => { const criticalInPhase = agents.filter((agent) => criticalAgents.includes(agent)); orchestratorLogger.info(` Phase ${phase}: ${agents.join(", ")} ${criticalInPhase.length > 0 ? `(Critical: ${criticalInPhase.join(", ")})` : ""}`); }); } orchestrator = new AgentOrchestrator(maxConcurrent, onProgress); AGENT_DEFINITIONS.forEach((definition) => { orchestrator.addAgent(definition); }); await orchestrator.runAll(); const context = getContext(); if (isVerbose) orchestratorLogger.success(`Orchestration completed successfully!`); if (onStop) { onStop(context.log); } return context; } catch (error) { if (isVerbose) orchestratorLogger.error(`Orchestration failed: ${error}`); logToContext(`ERROR: ${error}`); throw error; } finally { if (orchestrator) { orchestrator.cleanup(); } } }; export { speak2 as speak, AGENT_DEFINITIONS, runOrchestrator };