From bcec178bbebd8c17d5485d47d9aa60ffdbbe15dd Mon Sep 17 00:00:00 2001 From: Seonfx Date: Fri, 16 Jan 2026 08:37:53 -0400 Subject: [PATCH 1/2] fix: add JSON fallback for spec generation with custom API endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes spec generation failure when using custom API endpoints (e.g., GLM proxy) that don't support structured output. The AI returns JSON instead of XML, but the fallback parser only looked for XML tags. Changes: - escapeXml: Handle undefined/null values gracefully (converts to empty string) - generate-spec: Add JSON extraction fallback when XML tags aren't found - Reuses existing extractJson() utility (already used for Cursor models) - Converts extracted JSON to XML using specToXml() Closes #510 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/server/src/lib/app-spec-format.ts | 8 +++-- .../src/routes/app-spec/generate-spec.ts | 33 +++++++++++-------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/apps/server/src/lib/app-spec-format.ts b/apps/server/src/lib/app-spec-format.ts index 2894bbc4..0941fa4c 100644 --- a/apps/server/src/lib/app-spec-format.ts +++ b/apps/server/src/lib/app-spec-format.ts @@ -11,9 +11,13 @@ export { specOutputSchema } from '@automaker/types'; /** * Escape special XML characters + * Handles undefined/null values by converting them to empty strings */ -function escapeXml(str: string): string { - return str +function escapeXml(str: string | undefined | null): string { + if (str === undefined || str === null) { + return ''; + } + return String(str) .replace(/&/g, '&') .replace(//g, '>') diff --git a/apps/server/src/routes/app-spec/generate-spec.ts b/apps/server/src/routes/app-spec/generate-spec.ts index d79ffc5f..f519aca5 100644 --- a/apps/server/src/routes/app-spec/generate-spec.ts +++ b/apps/server/src/routes/app-spec/generate-spec.ts @@ -201,19 +201,26 @@ Your entire response should be valid JSON starting with { and ending with }. No xmlContent = responseText.substring(xmlStart, xmlEnd + ''.length); logger.info(`Extracted XML content: ${xmlContent.length} chars (from position ${xmlStart})`); } else { - // No valid XML structure found in the response text - // This happens when structured output was expected but not received, and the agent - // output conversational text instead of XML (e.g., "The project directory appears to be empty...") - // We should NOT save this conversational text as it's not a valid spec - logger.error('❌ Response does not contain valid XML structure'); - logger.error( - 'This typically happens when structured output failed and the agent produced conversational text instead of XML' - ); - throw new Error( - 'Failed to generate spec: No valid XML structure found in response. ' + - 'The response contained conversational text but no tags. ' + - 'Please try again.' - ); + // No XML found, try JSON extraction + logger.warn('⚠️ No XML tags found, attempting JSON extraction...'); + const extractedJson = extractJson(responseText, { logger }); + + if (extractedJson) { + logger.info('✅ Successfully extracted JSON from response text'); + xmlContent = specToXml(extractedJson); + logger.info(`✅ Converted extracted JSON to XML: ${xmlContent.length} chars`); + } else { + // Neither XML nor valid JSON found + logger.error('❌ Response does not contain valid XML or JSON structure'); + logger.error( + 'This typically happens when structured output failed and the agent produced conversational text instead of structured output' + ); + throw new Error( + 'Failed to generate spec: No valid XML or JSON structure found in response. ' + + 'The response contained conversational text but no tags or valid JSON. ' + + 'Please try again.' + ); + } } } From d651e9d8d6541208e478e4392773373fbe7cd5bc Mon Sep 17 00:00:00 2001 From: Seonfx Date: Fri, 16 Jan 2026 13:43:56 -0400 Subject: [PATCH 2/2] fix: address PR review feedback for JSON fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Simplify escapeXml() using 'str == null' check (type narrowing) - Add validation for extracted JSON before passing to specToXml() - Prevents runtime errors when JSON doesn't match SpecOutput schema 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/server/src/lib/app-spec-format.ts | 4 ++-- apps/server/src/routes/app-spec/generate-spec.ts | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/server/src/lib/app-spec-format.ts b/apps/server/src/lib/app-spec-format.ts index 0941fa4c..a52bf1f7 100644 --- a/apps/server/src/lib/app-spec-format.ts +++ b/apps/server/src/lib/app-spec-format.ts @@ -14,10 +14,10 @@ export { specOutputSchema } from '@automaker/types'; * Handles undefined/null values by converting them to empty strings */ function escapeXml(str: string | undefined | null): string { - if (str === undefined || str === null) { + if (str == null) { return ''; } - return String(str) + return str .replace(/&/g, '&') .replace(//g, '>') diff --git a/apps/server/src/routes/app-spec/generate-spec.ts b/apps/server/src/routes/app-spec/generate-spec.ts index f519aca5..4fa3d11a 100644 --- a/apps/server/src/routes/app-spec/generate-spec.ts +++ b/apps/server/src/routes/app-spec/generate-spec.ts @@ -205,7 +205,14 @@ Your entire response should be valid JSON starting with { and ending with }. No logger.warn('⚠️ No XML tags found, attempting JSON extraction...'); const extractedJson = extractJson(responseText, { logger }); - if (extractedJson) { + if ( + extractedJson && + typeof extractedJson.project_name === 'string' && + typeof extractedJson.overview === 'string' && + Array.isArray(extractedJson.technology_stack) && + Array.isArray(extractedJson.core_capabilities) && + Array.isArray(extractedJson.implemented_features) + ) { logger.info('✅ Successfully extracted JSON from response text'); xmlContent = specToXml(extractedJson); logger.info(`✅ Converted extracted JSON to XML: ${xmlContent.length} chars`);