mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
feat: enhance app specification structure and XML conversion
- Introduced a TypeScript interface for structured specification output to standardize project details. - Added a JSON schema for reliable parsing of structured output. - Implemented XML conversion for structured specifications, ensuring comprehensive project representation. - Updated spec generation options to include output format configuration. - Enhanced prompt instructions for generating specifications to improve clarity and completeness.
This commit is contained in:
@@ -4,6 +4,235 @@
|
|||||||
* This format must be included in all prompts that generate, modify, or regenerate
|
* This format must be included in all prompts that generate, modify, or regenerate
|
||||||
* app specifications to ensure consistency across the application.
|
* app specifications to ensure consistency across the application.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TypeScript interface for structured spec output
|
||||||
|
*/
|
||||||
|
export interface SpecOutput {
|
||||||
|
project_name: string;
|
||||||
|
overview: string;
|
||||||
|
technology_stack: string[];
|
||||||
|
core_capabilities: string[];
|
||||||
|
implemented_features: Array<{
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
file_locations?: string[];
|
||||||
|
}>;
|
||||||
|
additional_requirements?: string[];
|
||||||
|
development_guidelines?: string[];
|
||||||
|
implementation_roadmap?: Array<{
|
||||||
|
phase: string;
|
||||||
|
status: "completed" | "in_progress" | "pending";
|
||||||
|
description: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON Schema for structured spec output
|
||||||
|
* Used with Claude's structured output feature for reliable parsing
|
||||||
|
*/
|
||||||
|
export const specOutputSchema = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
project_name: {
|
||||||
|
type: "string",
|
||||||
|
description: "The name of the project",
|
||||||
|
},
|
||||||
|
overview: {
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
"A comprehensive description of what the project does, its purpose, and key goals",
|
||||||
|
},
|
||||||
|
technology_stack: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
description:
|
||||||
|
"List of all technologies, frameworks, libraries, and tools used",
|
||||||
|
},
|
||||||
|
core_capabilities: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
description: "List of main features and capabilities the project provides",
|
||||||
|
},
|
||||||
|
implemented_features: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: "string",
|
||||||
|
description: "Name of the implemented feature",
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: "string",
|
||||||
|
description: "Description of what the feature does",
|
||||||
|
},
|
||||||
|
file_locations: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
description: "File paths where this feature is implemented",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["name", "description"],
|
||||||
|
},
|
||||||
|
description: "Features that have been implemented based on code analysis",
|
||||||
|
},
|
||||||
|
additional_requirements: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
description: "Any additional requirements or constraints",
|
||||||
|
},
|
||||||
|
development_guidelines: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
description: "Development standards and practices",
|
||||||
|
},
|
||||||
|
implementation_roadmap: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
phase: {
|
||||||
|
type: "string",
|
||||||
|
description: "Name of the implementation phase",
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: "string",
|
||||||
|
enum: ["completed", "in_progress", "pending"],
|
||||||
|
description: "Current status of this phase",
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: "string",
|
||||||
|
description: "Description of what this phase involves",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["phase", "status", "description"],
|
||||||
|
},
|
||||||
|
description: "Phases or roadmap items for implementation",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: [
|
||||||
|
"project_name",
|
||||||
|
"overview",
|
||||||
|
"technology_stack",
|
||||||
|
"core_capabilities",
|
||||||
|
"implemented_features",
|
||||||
|
],
|
||||||
|
additionalProperties: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape special XML characters
|
||||||
|
*/
|
||||||
|
function escapeXml(str: string): string {
|
||||||
|
return str
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert structured spec output to XML format
|
||||||
|
*/
|
||||||
|
export function specToXml(spec: SpecOutput): string {
|
||||||
|
const indent = " ";
|
||||||
|
|
||||||
|
let xml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project_specification>
|
||||||
|
${indent}<project_name>${escapeXml(spec.project_name)}</project_name>
|
||||||
|
|
||||||
|
${indent}<overview>
|
||||||
|
${indent}${indent}${escapeXml(spec.overview)}
|
||||||
|
${indent}</overview>
|
||||||
|
|
||||||
|
${indent}<technology_stack>
|
||||||
|
${spec.technology_stack.map((t) => `${indent}${indent}<technology>${escapeXml(t)}</technology>`).join("\n")}
|
||||||
|
${indent}</technology_stack>
|
||||||
|
|
||||||
|
${indent}<core_capabilities>
|
||||||
|
${spec.core_capabilities.map((c) => `${indent}${indent}<capability>${escapeXml(c)}</capability>`).join("\n")}
|
||||||
|
${indent}</core_capabilities>
|
||||||
|
|
||||||
|
${indent}<implemented_features>
|
||||||
|
${spec.implemented_features
|
||||||
|
.map(
|
||||||
|
(f) => `${indent}${indent}<feature>
|
||||||
|
${indent}${indent}${indent}<name>${escapeXml(f.name)}</name>
|
||||||
|
${indent}${indent}${indent}<description>${escapeXml(f.description)}</description>${
|
||||||
|
f.file_locations && f.file_locations.length > 0
|
||||||
|
? `\n${indent}${indent}${indent}<file_locations>
|
||||||
|
${f.file_locations.map((loc) => `${indent}${indent}${indent}${indent}<location>${escapeXml(loc)}</location>`).join("\n")}
|
||||||
|
${indent}${indent}${indent}</file_locations>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
${indent}${indent}</feature>`
|
||||||
|
)
|
||||||
|
.join("\n")}
|
||||||
|
${indent}</implemented_features>`;
|
||||||
|
|
||||||
|
// Optional sections
|
||||||
|
if (spec.additional_requirements && spec.additional_requirements.length > 0) {
|
||||||
|
xml += `
|
||||||
|
|
||||||
|
${indent}<additional_requirements>
|
||||||
|
${spec.additional_requirements.map((r) => `${indent}${indent}<requirement>${escapeXml(r)}</requirement>`).join("\n")}
|
||||||
|
${indent}</additional_requirements>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spec.development_guidelines && spec.development_guidelines.length > 0) {
|
||||||
|
xml += `
|
||||||
|
|
||||||
|
${indent}<development_guidelines>
|
||||||
|
${spec.development_guidelines.map((g) => `${indent}${indent}<guideline>${escapeXml(g)}</guideline>`).join("\n")}
|
||||||
|
${indent}</development_guidelines>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spec.implementation_roadmap && spec.implementation_roadmap.length > 0) {
|
||||||
|
xml += `
|
||||||
|
|
||||||
|
${indent}<implementation_roadmap>
|
||||||
|
${spec.implementation_roadmap
|
||||||
|
.map(
|
||||||
|
(r) => `${indent}${indent}<phase>
|
||||||
|
${indent}${indent}${indent}<name>${escapeXml(r.phase)}</name>
|
||||||
|
${indent}${indent}${indent}<status>${escapeXml(r.status)}</status>
|
||||||
|
${indent}${indent}${indent}<description>${escapeXml(r.description)}</description>
|
||||||
|
${indent}${indent}</phase>`
|
||||||
|
)
|
||||||
|
.join("\n")}
|
||||||
|
${indent}</implementation_roadmap>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
xml += `
|
||||||
|
</project_specification>`;
|
||||||
|
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get prompt instruction for structured output (simpler than XML instructions)
|
||||||
|
*/
|
||||||
|
export function getStructuredSpecPromptInstruction(): string {
|
||||||
|
return `
|
||||||
|
Analyze the project and provide a comprehensive specification with:
|
||||||
|
|
||||||
|
1. **project_name**: The name of the project
|
||||||
|
2. **overview**: A comprehensive description of what the project does, its purpose, and key goals
|
||||||
|
3. **technology_stack**: List all technologies, frameworks, libraries, and tools used
|
||||||
|
4. **core_capabilities**: List the main features and capabilities the project provides
|
||||||
|
5. **implemented_features**: For each implemented feature, provide:
|
||||||
|
- name: Feature name
|
||||||
|
- description: What it does
|
||||||
|
- file_locations: Key files where it's implemented (optional)
|
||||||
|
6. **additional_requirements**: Any system requirements, dependencies, or constraints (optional)
|
||||||
|
7. **development_guidelines**: Development standards and best practices (optional)
|
||||||
|
8. **implementation_roadmap**: Project phases with status (completed/in_progress/pending) (optional)
|
||||||
|
|
||||||
|
Be thorough in your analysis. The output will be automatically formatted as structured JSON.
|
||||||
|
`;
|
||||||
|
}
|
||||||
export const APP_SPEC_XML_FORMAT = `
|
export const APP_SPEC_XML_FORMAT = `
|
||||||
The app_spec.txt file MUST follow this exact XML format:
|
The app_spec.txt file MUST follow this exact XML format:
|
||||||
|
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ export function createSpecGenerationOptions(
|
|||||||
allowedTools: [...TOOL_PRESETS.specGeneration],
|
allowedTools: [...TOOL_PRESETS.specGeneration],
|
||||||
...(config.systemPrompt && { systemPrompt: config.systemPrompt }),
|
...(config.systemPrompt && { systemPrompt: config.systemPrompt }),
|
||||||
...(config.abortController && { abortController: config.abortController }),
|
...(config.abortController && { abortController: config.abortController }),
|
||||||
|
...(config.outputFormat && { outputFormat: config.outputFormat }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ import { query } from "@anthropic-ai/claude-agent-sdk";
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import type { EventEmitter } from "../../lib/events.js";
|
import type { EventEmitter } from "../../lib/events.js";
|
||||||
import { getAppSpecFormatInstruction } from "../../lib/app-spec-format.js";
|
import {
|
||||||
|
specOutputSchema,
|
||||||
|
specToXml,
|
||||||
|
getStructuredSpecPromptInstruction,
|
||||||
|
type SpecOutput,
|
||||||
|
} from "../../lib/app-spec-format.js";
|
||||||
import { createLogger } from "../../lib/logger.js";
|
import { createLogger } from "../../lib/logger.js";
|
||||||
import { createSpecGenerationOptions } from "../../lib/sdk-options.js";
|
import { createSpecGenerationOptions } from "../../lib/sdk-options.js";
|
||||||
import { logAuthStatus } from "./common.js";
|
import { logAuthStatus } from "./common.js";
|
||||||
@@ -42,9 +47,7 @@ export async function generateSpec(
|
|||||||
- Existing technologies and frameworks
|
- Existing technologies and frameworks
|
||||||
- Project structure and architecture
|
- Project structure and architecture
|
||||||
- Current features and capabilities
|
- 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 {
|
} else {
|
||||||
// Use default tech stack
|
// Use default tech stack
|
||||||
techStackDefaults = `Default Technology Stack:
|
techStackDefaults = `Default Technology Stack:
|
||||||
@@ -54,9 +57,7 @@ After your analysis, OUTPUT the complete XML specification in your response. Do
|
|||||||
- Styling: Tailwind CSS
|
- Styling: Tailwind CSS
|
||||||
- Frontend: React
|
- 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.
|
const prompt = `You are helping to define a software project specification.
|
||||||
@@ -70,7 +71,7 @@ ${techStackDefaults}
|
|||||||
|
|
||||||
${analysisInstructions}
|
${analysisInstructions}
|
||||||
|
|
||||||
${getAppSpecFormatInstruction()}`;
|
${getStructuredSpecPromptInstruction()}`;
|
||||||
|
|
||||||
logger.info("========== PROMPT BEING SENT ==========");
|
logger.info("========== PROMPT BEING SENT ==========");
|
||||||
logger.info(`Prompt length: ${prompt.length} chars`);
|
logger.info(`Prompt length: ${prompt.length} chars`);
|
||||||
@@ -85,6 +86,10 @@ ${getAppSpecFormatInstruction()}`;
|
|||||||
const options = createSpecGenerationOptions({
|
const options = createSpecGenerationOptions({
|
||||||
cwd: projectPath,
|
cwd: projectPath,
|
||||||
abortController,
|
abortController,
|
||||||
|
outputFormat: {
|
||||||
|
type: "json_schema",
|
||||||
|
schema: specOutputSchema,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.debug("SDK Options:", JSON.stringify(options, null, 2));
|
logger.debug("SDK Options:", JSON.stringify(options, null, 2));
|
||||||
@@ -105,6 +110,7 @@ ${getAppSpecFormatInstruction()}`;
|
|||||||
|
|
||||||
let responseText = "";
|
let responseText = "";
|
||||||
let messageCount = 0;
|
let messageCount = 0;
|
||||||
|
let structuredOutput: SpecOutput | null = null;
|
||||||
|
|
||||||
logger.info("Starting to iterate over stream...");
|
logger.info("Starting to iterate over stream...");
|
||||||
|
|
||||||
@@ -118,72 +124,49 @@ ${getAppSpecFormatInstruction()}`;
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (msg.type === "assistant") {
|
if (msg.type === "assistant") {
|
||||||
// Log the full message structure to debug
|
|
||||||
logger.info(`Assistant msg keys: ${Object.keys(msg).join(", ")}`);
|
|
||||||
const msgAny = msg as any;
|
const msgAny = msg as any;
|
||||||
if (msgAny.message) {
|
if (msgAny.message?.content) {
|
||||||
logger.info(
|
for (const block of msgAny.message.content) {
|
||||||
`msg.message keys: ${Object.keys(msgAny.message).join(", ")}`
|
if (block.type === "text") {
|
||||||
);
|
responseText += block.text;
|
||||||
if (msgAny.message.content) {
|
|
||||||
logger.info(
|
|
||||||
`msg.message.content length: ${msgAny.message.content.length}`
|
|
||||||
);
|
|
||||||
for (const block of msgAny.message.content) {
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`Block keys: ${Object.keys(block).join(", ")}, type: ${
|
`Text block received (${block.text.length} chars), total now: ${responseText.length} chars`
|
||||||
block.type
|
|
||||||
}`
|
|
||||||
);
|
);
|
||||||
if (block.type === "text") {
|
events.emit("spec-regeneration:event", {
|
||||||
responseText += block.text;
|
type: "spec_regeneration_progress",
|
||||||
logger.info(
|
content: block.text,
|
||||||
`Text block received (${block.text.length} chars), total now: ${responseText.length} chars`
|
projectPath: projectPath,
|
||||||
);
|
});
|
||||||
logger.info(`Text preview: ${block.text.substring(0, 200)}...`);
|
} else if (block.type === "tool_use") {
|
||||||
events.emit("spec-regeneration:event", {
|
logger.info("Tool use:", block.name);
|
||||||
type: "spec_regeneration_progress",
|
events.emit("spec-regeneration:event", {
|
||||||
content: block.text,
|
type: "spec_tool",
|
||||||
projectPath: projectPath,
|
tool: block.name,
|
||||||
});
|
input: block.input,
|
||||||
} else if (block.type === "tool_use") {
|
});
|
||||||
logger.info("Tool use:", block.name);
|
|
||||||
events.emit("spec-regeneration:event", {
|
|
||||||
type: "spec_tool",
|
|
||||||
tool: block.name,
|
|
||||||
input: block.input,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
logger.warn("msg.message.content is falsy");
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
logger.warn("msg.message is falsy");
|
|
||||||
// Log full message to see structure
|
|
||||||
logger.info(
|
|
||||||
`Full assistant msg: ${JSON.stringify(msg).substring(0, 1000)}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else if (msg.type === "result" && (msg as any).subtype === "success") {
|
} else if (msg.type === "result" && (msg as any).subtype === "success") {
|
||||||
logger.info("Received success result");
|
logger.info("Received success result");
|
||||||
logger.info(`Result value length: ${((msg as any).result || "").length}`);
|
// Check for structured output - this is the reliable way to get spec data
|
||||||
logger.info(
|
const resultMsg = msg as any;
|
||||||
`Final accumulated responseText length: ${responseText.length}`
|
if (resultMsg.structured_output) {
|
||||||
);
|
structuredOutput = resultMsg.structured_output as SpecOutput;
|
||||||
// Don't overwrite responseText - the accumulated text contains the XML spec
|
logger.info("✅ Received structured output");
|
||||||
// The result.result field contains Claude's conversational summary (e.g., "I've created the spec...")
|
logger.debug("Structured output:", JSON.stringify(structuredOutput, null, 2));
|
||||||
// which is NOT what we want to save to app_spec.txt
|
} else {
|
||||||
// See: https://github.com/AutoMaker-Org/automaker/issues/149
|
logger.warn("⚠️ No structured output in result, will fall back to text parsing");
|
||||||
|
}
|
||||||
} else if (msg.type === "result") {
|
} else if (msg.type === "result") {
|
||||||
// Handle all result types
|
// Handle error result types
|
||||||
const subtype = (msg as any).subtype;
|
const subtype = (msg as any).subtype;
|
||||||
logger.info(`Result message: subtype=${subtype}`);
|
logger.info(`Result message: subtype=${subtype}`);
|
||||||
if (subtype === "error_max_turns") {
|
if (subtype === "error_max_turns") {
|
||||||
logger.error(
|
logger.error("❌ Hit max turns limit!");
|
||||||
"❌ Hit max turns limit! Claude used too many tool calls."
|
} else if (subtype === "error_max_structured_output_retries") {
|
||||||
);
|
logger.error("❌ Failed to produce valid structured output after retries");
|
||||||
logger.info(`responseText so far: ${responseText.length} chars`);
|
throw new Error("Could not produce valid spec output");
|
||||||
}
|
}
|
||||||
} else if ((msg as { type: string }).type === "error") {
|
} else if ((msg as { type: string }).type === "error") {
|
||||||
logger.error("❌ Received error message from stream:");
|
logger.error("❌ Received error message from stream:");
|
||||||
@@ -203,32 +186,45 @@ ${getAppSpecFormatInstruction()}`;
|
|||||||
|
|
||||||
logger.info(`Stream iteration complete. Total messages: ${messageCount}`);
|
logger.info(`Stream iteration complete. Total messages: ${messageCount}`);
|
||||||
logger.info(`Response text length: ${responseText.length} chars`);
|
logger.info(`Response text length: ${responseText.length} chars`);
|
||||||
logger.info("========== FINAL RESPONSE TEXT ==========");
|
|
||||||
logger.info(responseText || "(empty)");
|
|
||||||
logger.info("========== END RESPONSE TEXT ==========");
|
|
||||||
|
|
||||||
if (!responseText || responseText.trim().length === 0) {
|
// Determine XML content to save
|
||||||
logger.error("❌ WARNING: responseText is empty! Nothing to save.");
|
let xmlContent: string;
|
||||||
}
|
|
||||||
|
|
||||||
// Extract XML content from response - Claude might include conversational text before/after
|
if (structuredOutput) {
|
||||||
// See: https://github.com/AutoMaker-Org/automaker/issues/149
|
// Use structured output - convert JSON to XML
|
||||||
let xmlContent = responseText;
|
logger.info("✅ Using structured output for XML generation");
|
||||||
const xmlStart = responseText.indexOf("<project_specification>");
|
xmlContent = specToXml(structuredOutput);
|
||||||
const xmlEnd = responseText.lastIndexOf("</project_specification>");
|
logger.info(`Generated XML from structured output: ${xmlContent.length} chars`);
|
||||||
|
|
||||||
if (xmlStart !== -1 && xmlEnd !== -1) {
|
|
||||||
// Extract just the XML content
|
|
||||||
xmlContent = responseText.substring(xmlStart, xmlEnd + "</project_specification>".length);
|
|
||||||
logger.info(`Extracted XML content: ${xmlContent.length} chars (from position ${xmlStart})`);
|
|
||||||
} else if (xmlStart === -1) {
|
|
||||||
logger.warn("⚠️ Response does not contain <project_specification> tag - saving raw response");
|
|
||||||
} else {
|
} else {
|
||||||
logger.warn("⚠️ Response has incomplete XML (missing closing tag) - saving raw response");
|
// Fallback: Extract XML content from response text
|
||||||
|
// Claude might include conversational text before/after
|
||||||
|
// See: https://github.com/AutoMaker-Org/automaker/issues/149
|
||||||
|
logger.warn("⚠️ No structured output, falling back to text parsing");
|
||||||
|
logger.info("========== FINAL RESPONSE TEXT ==========");
|
||||||
|
logger.info(responseText || "(empty)");
|
||||||
|
logger.info("========== END RESPONSE TEXT ==========");
|
||||||
|
|
||||||
|
if (!responseText || responseText.trim().length === 0) {
|
||||||
|
throw new Error("No response text and no structured output - cannot generate spec");
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlContent = responseText;
|
||||||
|
const xmlStart = responseText.indexOf("<project_specification>");
|
||||||
|
const xmlEnd = responseText.lastIndexOf("</project_specification>");
|
||||||
|
|
||||||
|
if (xmlStart !== -1 && xmlEnd !== -1) {
|
||||||
|
// Extract just the XML content
|
||||||
|
xmlContent = responseText.substring(xmlStart, xmlEnd + "</project_specification>".length);
|
||||||
|
logger.info(`Extracted XML content: ${xmlContent.length} chars (from position ${xmlStart})`);
|
||||||
|
} else if (xmlStart === -1) {
|
||||||
|
logger.warn("⚠️ Response does not contain <project_specification> tag - saving raw response");
|
||||||
|
} else {
|
||||||
|
logger.warn("⚠️ Response has incomplete XML (missing closing tag) - saving raw response");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save spec to .automaker directory
|
// Save spec to .automaker directory
|
||||||
const specDir = await ensureAutomakerDir(projectPath);
|
await ensureAutomakerDir(projectPath);
|
||||||
const specPath = getAppSpecPath(projectPath);
|
const specPath = getAppSpecPath(projectPath);
|
||||||
|
|
||||||
logger.info("Saving spec to:", specPath);
|
logger.info("Saving spec to:", specPath);
|
||||||
|
|||||||
Reference in New Issue
Block a user