refactor(server): Use shared JSON extractor in feature and plan parsing

Update parseAndCreateFeatures and parsePlanResponse to use the shared
extractJson/extractJsonWithArray utilities instead of manual regex
parsing for more robust and consistent JSON extraction from AI responses.

- parse-and-create-features.ts: Use extractJsonWithArray for features
- generate-plan.ts: Use extractJson with requiredKey for backlog plans

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kacper
2025-12-30 15:36:17 +01:00
parent b0f83b7c76
commit 948fdb6352
2 changed files with 36 additions and 28 deletions

View File

@@ -7,6 +7,7 @@ import * as secureFs from '../../lib/secure-fs.js';
import type { EventEmitter } from '../../lib/events.js';
import { createLogger } from '@automaker/utils';
import { getFeaturesDir } from '@automaker/platform';
import { extractJsonWithArray } from '../../lib/json-extractor.js';
const logger = createLogger('SpecRegeneration');
@@ -22,23 +23,30 @@ export async function parseAndCreateFeatures(
logger.info('========== END CONTENT ==========');
try {
// Extract JSON from response
logger.info('Extracting JSON from response...');
logger.info(`Looking for pattern: /{[\\s\\S]*"features"[\\s\\S]*}/`);
const jsonMatch = content.match(/\{[\s\S]*"features"[\s\S]*\}/);
if (!jsonMatch) {
logger.error('❌ No valid JSON found in response');
// Extract JSON from response using shared utility
logger.info('Extracting JSON from response using extractJsonWithArray...');
interface FeaturesResponse {
features: Array<{
id: string;
category?: string;
title: string;
description: string;
priority?: number;
complexity?: string;
dependencies?: string[];
}>;
}
const parsed = extractJsonWithArray<FeaturesResponse>(content, 'features', { logger });
if (!parsed || !parsed.features) {
logger.error('❌ No valid JSON with "features" array found in response');
logger.error('Full content received:');
logger.error(content);
throw new Error('No valid JSON found in response');
}
logger.info(`JSON match found (${jsonMatch[0].length} chars)`);
logger.info('========== MATCHED JSON ==========');
logger.info(jsonMatch[0]);
logger.info('========== END MATCHED JSON ==========');
const parsed = JSON.parse(jsonMatch[0]);
logger.info(`Parsed ${parsed.features?.length || 0} features`);
logger.info('Parsed features:', JSON.stringify(parsed.features, null, 2));

View File

@@ -10,6 +10,7 @@ import type { Feature, BacklogPlanResult, BacklogChange, DependencyUpdate } from
import { DEFAULT_PHASE_MODELS } from '@automaker/types';
import { FeatureLoader } from '../../services/feature-loader.js';
import { ProviderFactory } from '../../providers/provider-factory.js';
import { extractJson } from '../../lib/json-extractor.js';
import { logger, setRunningState, getErrorMessage } from './common.js';
import type { SettingsService } from '../../services/settings-service.js';
import { getAutoLoadClaudeMdSetting } from '../../lib/settings-helpers.js';
@@ -43,24 +44,23 @@ function formatFeaturesForPrompt(features: Feature[]): string {
* Parse the AI response into a BacklogPlanResult
*/
function parsePlanResponse(response: string): BacklogPlanResult {
try {
// Try to extract JSON from the response
const jsonMatch = response.match(/```json\n?([\s\S]*?)\n?```/);
if (jsonMatch) {
return JSON.parse(jsonMatch[1]);
}
// Use shared JSON extraction utility for robust parsing
const parsed = extractJson<BacklogPlanResult>(response, {
logger,
requiredKey: 'changes',
});
// Try to parse the whole response as JSON
return JSON.parse(response);
} catch {
// If parsing fails, return an empty result
logger.warn('[BacklogPlan] Failed to parse AI response as JSON');
return {
changes: [],
summary: 'Failed to parse AI response',
dependencyUpdates: [],
};
if (parsed) {
return parsed;
}
// If parsing fails, return an empty result
logger.warn('[BacklogPlan] Failed to parse AI response as JSON');
return {
changes: [],
summary: 'Failed to parse AI response',
dependencyUpdates: [],
};
}
/**