fix: Add structured output fallback for non-Claude models in app spec generation

This fixes the app spec generation failing for non-Claude models (Cursor, Gemini,
OpenCode, Copilot) that don't support structured output capabilities.

Changes:
- Add `supportsStructuredOutput()` utility function in @automaker/types to
  centralize model capability detection
- Update generate-features-from-spec.ts:
  - Add explicit JSON instructions for non-Claude/Codex models
  - Define featuresOutputSchema for structured output
  - Pre-extract JSON from text responses using extractJsonWithArray
  - Handle both structured_output and text responses properly
- Update generate-spec.ts:
  - Replace isCursorModel with supportsStructuredOutput for consistency
- Update sync-spec.ts:
  - Add techStackOutputSchema for structured output
  - Add JSON extraction fallback for text responses
  - Handle both structured_output and text parsing
- Update validate-issue.ts:
  - Use supportsStructuredOutput for cleaner capability detection

The fix follows the same pattern used in generate-spec.ts where non-Claude models
receive explicit JSON formatting instructions in the prompt and responses are
parsed using extractJson utilities.

Fixes #669

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Shirone
2026-01-24 18:25:39 +01:00
parent d8fa5c4cd1
commit 92b1fb3725
6 changed files with 261 additions and 44 deletions

View File

@@ -9,7 +9,7 @@ import * as secureFs from '../../lib/secure-fs.js';
import type { EventEmitter } from '../../lib/events.js';
import { specOutputSchema, specToXml, type SpecOutput } from '../../lib/app-spec-format.js';
import { createLogger } from '@automaker/utils';
import { DEFAULT_PHASE_MODELS, isCursorModel } from '@automaker/types';
import { DEFAULT_PHASE_MODELS, supportsStructuredOutput } from '@automaker/types';
import { resolvePhaseModel } from '@automaker/model-resolver';
import { extractJson } from '../../lib/json-extractor.js';
import { streamingQuery } from '../../providers/simple-query-service.js';
@@ -120,10 +120,13 @@ ${prompts.appSpec.structuredSpecInstructions}`;
let responseText = '';
let structuredOutput: SpecOutput | null = null;
// Determine if we should use structured output (Claude supports it, Cursor doesn't)
const useStructuredOutput = !isCursorModel(model);
// Determine if we should use structured output based on model type
const useStructuredOutput = supportsStructuredOutput(model);
logger.info(
`Structured output mode: ${useStructuredOutput ? 'enabled (Claude/Codex)' : 'disabled (using JSON instructions)'}`
);
// Build the final prompt - for Cursor, include JSON schema instructions
// Build the final prompt - for non-Claude/Codex models, include JSON schema instructions
let finalPrompt = prompt;
if (!useStructuredOutput) {
finalPrompt = `${prompt}