diff --git a/apps/server/src/lib/app-spec-format.ts b/apps/server/src/lib/app-spec-format.ts index 6235ec21..ff5af0e6 100644 --- a/apps/server/src/lib/app-spec-format.ts +++ b/apps/server/src/lib/app-spec-format.ts @@ -63,10 +63,11 @@ export function getAppSpecFormatInstruction(): string { ${APP_SPEC_XML_FORMAT} CRITICAL FORMATTING REQUIREMENTS: +- Do NOT use the Write, Edit, or Bash tools to create files - just OUTPUT the XML in your response - Your ENTIRE response MUST be valid XML following the exact template structure above - Do NOT use markdown formatting (no # headers, no **bold**, no - lists, etc.) - Do NOT include any explanatory text, prefix, or suffix outside the XML tags -- Do NOT include phrases like "Based on my analysis..." or "I'll create..." before the XML +- Do NOT include phrases like "Based on my analysis...", "I'll create...", "Let me analyze..." before the XML - Do NOT include any text before or after - Your response must start IMMEDIATELY with with no preceding text - Your response must end IMMEDIATELY with with no following text diff --git a/apps/server/src/lib/sdk-options.ts b/apps/server/src/lib/sdk-options.ts index 361d332e..beb54c7e 100644 --- a/apps/server/src/lib/sdk-options.ts +++ b/apps/server/src/lib/sdk-options.ts @@ -164,6 +164,10 @@ export function createSpecGenerationOptions( ): Options { return { ...getBaseOptions(), + // Override permissionMode - spec generation only needs read-only tools + // Using "acceptEdits" can cause Claude to write files to unexpected locations + // See: https://github.com/AutoMaker-Org/automaker/issues/149 + permissionMode: "default", model: getModelForUseCase("spec", config.model), maxTurns: MAX_TURNS.maximum, cwd: config.cwd, @@ -186,6 +190,8 @@ export function createFeatureGenerationOptions( ): Options { return { ...getBaseOptions(), + // Override permissionMode - feature generation only needs read-only tools + permissionMode: "default", model: getModelForUseCase("features", config.model), maxTurns: MAX_TURNS.quick, cwd: config.cwd, diff --git a/apps/server/src/routes/app-spec/generate-spec.ts b/apps/server/src/routes/app-spec/generate-spec.ts index 90552d19..fe6a3145 100644 --- a/apps/server/src/routes/app-spec/generate-spec.ts +++ b/apps/server/src/routes/app-spec/generate-spec.ts @@ -38,11 +38,13 @@ export async function generateSpec( if (analyzeProject !== false) { // Default to true - analyze the project - analysisInstructions = `Based on this overview, analyze the project directory (if it exists) and create a comprehensive specification. Use the Read, Glob, and Grep tools to explore the codebase and understand: + analysisInstructions = `Based on this overview, analyze the project directory (if it exists) using the Read, Glob, and Grep tools to understand: - Existing technologies and frameworks - Project structure and architecture - Current features and capabilities -- Code patterns and conventions`; +- Code patterns and conventions + +After your analysis, OUTPUT the complete XML specification in your response. Do NOT attempt to write any files - just return the XML content and it will be saved automatically.`; } else { // Use default tech stack techStackDefaults = `Default Technology Stack: @@ -52,7 +54,9 @@ export async function generateSpec( - Styling: Tailwind CSS - Frontend: React -Use these technologies as the foundation for the specification.`; +Use these technologies as the foundation for the specification. + +OUTPUT the complete XML specification in your response. Do NOT attempt to write any files - just return the XML content and it will be saved automatically.`; } const prompt = `You are helping to define a software project specification. @@ -163,17 +167,14 @@ ${getAppSpecFormatInstruction()}`; } } else if (msg.type === "result" && (msg as any).subtype === "success") { logger.info("Received success result"); - logger.info(`Result value: "${(msg as any).result}"`); + logger.info(`Result value length: ${((msg as any).result || "").length}`); logger.info( - `Current responseText length before result: ${responseText.length}` + `Final accumulated responseText length: ${responseText.length}` ); - // Only use result if it has content, otherwise keep accumulated text - if ((msg as any).result && (msg as any).result.length > 0) { - logger.info("Using result value as responseText"); - responseText = (msg as any).result; - } else { - logger.info("Result is empty, keeping accumulated responseText"); - } + // Don't overwrite responseText - the accumulated text contains the XML spec + // The result.result field contains Claude's conversational summary (e.g., "I've created the spec...") + // which is NOT what we want to save to app_spec.txt + // See: https://github.com/AutoMaker-Org/automaker/issues/149 } else if (msg.type === "result") { // Handle all result types const subtype = (msg as any).subtype; @@ -210,14 +211,30 @@ ${getAppSpecFormatInstruction()}`; logger.error("❌ WARNING: responseText is empty! Nothing to save."); } + // Extract XML content from response - Claude might include conversational text before/after + // See: https://github.com/AutoMaker-Org/automaker/issues/149 + let xmlContent = responseText; + const xmlStart = responseText.indexOf(""); + const xmlEnd = responseText.lastIndexOf(""); + + if (xmlStart !== -1 && xmlEnd !== -1) { + // Extract just the XML content + xmlContent = responseText.substring(xmlStart, xmlEnd + "".length); + logger.info(`Extracted XML content: ${xmlContent.length} chars (from position ${xmlStart})`); + } else if (xmlStart === -1) { + logger.warn("⚠️ Response does not contain tag - saving raw response"); + } else { + logger.warn("⚠️ Response has incomplete XML (missing closing tag) - saving raw response"); + } + // Save spec to .automaker directory const specDir = await ensureAutomakerDir(projectPath); const specPath = getAppSpecPath(projectPath); logger.info("Saving spec to:", specPath); - logger.info(`Content to save (${responseText.length} chars)`); + logger.info(`Content to save (${xmlContent.length} chars)`); - await fs.writeFile(specPath, responseText); + await fs.writeFile(specPath, xmlContent); // Verify the file was written const savedContent = await fs.readFile(specPath, "utf-8");