mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
- Introduced a new Simple Query Service to streamline basic AI queries, allowing for structured JSON outputs. - Updated existing routes to utilize the new service, replacing direct SDK calls with a unified interface for querying. - Enhanced provider handling in various routes, including generate-spec, generate-features-from-spec, and validate-issue, to support both Claude and Cursor models seamlessly. - Added structured output support for improved response handling and error management across the application.
148 lines
5.0 KiB
TypeScript
148 lines
5.0 KiB
TypeScript
/**
|
|
* Generate features from existing app_spec.txt
|
|
*
|
|
* Model is configurable via phaseModels.featureGenerationModel in settings
|
|
* (defaults to Sonnet for balanced speed and quality).
|
|
*/
|
|
|
|
import * as secureFs from '../../lib/secure-fs.js';
|
|
import type { EventEmitter } from '../../lib/events.js';
|
|
import { createLogger } from '@automaker/utils';
|
|
import { DEFAULT_PHASE_MODELS } from '@automaker/types';
|
|
import { resolvePhaseModel } from '@automaker/model-resolver';
|
|
import { streamingQuery } from '../../providers/simple-query-service.js';
|
|
import { parseAndCreateFeatures } from './parse-and-create-features.js';
|
|
import { getAppSpecPath } from '@automaker/platform';
|
|
import type { SettingsService } from '../../services/settings-service.js';
|
|
import { getAutoLoadClaudeMdSetting } from '../../lib/settings-helpers.js';
|
|
|
|
const logger = createLogger('SpecRegeneration');
|
|
|
|
const DEFAULT_MAX_FEATURES = 50;
|
|
|
|
export async function generateFeaturesFromSpec(
|
|
projectPath: string,
|
|
events: EventEmitter,
|
|
abortController: AbortController,
|
|
maxFeatures?: number,
|
|
settingsService?: SettingsService
|
|
): Promise<void> {
|
|
const featureCount = maxFeatures ?? DEFAULT_MAX_FEATURES;
|
|
logger.debug('========== generateFeaturesFromSpec() started ==========');
|
|
logger.debug('projectPath:', projectPath);
|
|
logger.debug('maxFeatures:', featureCount);
|
|
|
|
// Read existing spec from .automaker directory
|
|
const specPath = getAppSpecPath(projectPath);
|
|
let spec: string;
|
|
|
|
logger.debug('Reading spec from:', specPath);
|
|
|
|
try {
|
|
spec = (await secureFs.readFile(specPath, 'utf-8')) as string;
|
|
logger.info(`Spec loaded successfully (${spec.length} chars)`);
|
|
logger.info(`Spec preview (first 500 chars): ${spec.substring(0, 500)}`);
|
|
logger.info(`Spec preview (last 500 chars): ${spec.substring(spec.length - 500)}`);
|
|
} catch (readError) {
|
|
logger.error('❌ Failed to read spec file:', readError);
|
|
events.emit('spec-regeneration:event', {
|
|
type: 'spec_regeneration_error',
|
|
error: 'No project spec found. Generate spec first.',
|
|
projectPath: projectPath,
|
|
});
|
|
return;
|
|
}
|
|
|
|
const prompt = `Based on this project specification:
|
|
|
|
${spec}
|
|
|
|
Generate a prioritized list of implementable features. For each feature provide:
|
|
|
|
1. **id**: A unique lowercase-hyphenated identifier
|
|
2. **category**: Functional category (e.g., "Core", "UI", "API", "Authentication", "Database")
|
|
3. **title**: Short descriptive title
|
|
4. **description**: What this feature does (2-3 sentences)
|
|
5. **priority**: 1 (high), 2 (medium), or 3 (low)
|
|
6. **complexity**: "simple", "moderate", or "complex"
|
|
7. **dependencies**: Array of feature IDs this depends on (can be empty)
|
|
|
|
Format as JSON:
|
|
{
|
|
"features": [
|
|
{
|
|
"id": "feature-id",
|
|
"category": "Feature Category",
|
|
"title": "Feature Title",
|
|
"description": "What it does",
|
|
"priority": 1,
|
|
"complexity": "moderate",
|
|
"dependencies": []
|
|
}
|
|
]
|
|
}
|
|
|
|
Generate ${featureCount} features that build on each other logically.
|
|
|
|
IMPORTANT: Do not ask for clarification. The specification is provided above. Generate the JSON immediately.`;
|
|
|
|
logger.info('========== PROMPT BEING SENT ==========');
|
|
logger.info(`Prompt length: ${prompt.length} chars`);
|
|
logger.info(`Prompt preview (first 1000 chars):\n${prompt.substring(0, 1000)}`);
|
|
logger.info('========== END PROMPT PREVIEW ==========');
|
|
|
|
events.emit('spec-regeneration:event', {
|
|
type: 'spec_regeneration_progress',
|
|
content: 'Analyzing spec and generating features...\n',
|
|
projectPath: projectPath,
|
|
});
|
|
|
|
// Load autoLoadClaudeMd setting
|
|
const autoLoadClaudeMd = await getAutoLoadClaudeMdSetting(
|
|
projectPath,
|
|
settingsService,
|
|
'[FeatureGeneration]'
|
|
);
|
|
|
|
// Get model from phase settings
|
|
const settings = await settingsService?.getGlobalSettings();
|
|
const phaseModelEntry =
|
|
settings?.phaseModels?.featureGenerationModel || DEFAULT_PHASE_MODELS.featureGenerationModel;
|
|
const { model, thinkingLevel } = resolvePhaseModel(phaseModelEntry);
|
|
|
|
logger.info('Using model:', model);
|
|
|
|
// Use streamingQuery with event callbacks
|
|
const result = await streamingQuery({
|
|
prompt,
|
|
model,
|
|
cwd: projectPath,
|
|
maxTurns: 250,
|
|
allowedTools: ['Read', 'Glob', 'Grep'],
|
|
abortController,
|
|
thinkingLevel,
|
|
readOnly: true, // Feature generation only reads code, doesn't write
|
|
settingSources: autoLoadClaudeMd ? ['user', 'project', 'local'] : undefined,
|
|
onText: (text) => {
|
|
logger.debug(`Feature text block received (${text.length} chars)`);
|
|
events.emit('spec-regeneration:event', {
|
|
type: 'spec_regeneration_progress',
|
|
content: text,
|
|
projectPath: projectPath,
|
|
});
|
|
},
|
|
});
|
|
|
|
const responseText = result.text;
|
|
|
|
logger.info(`Feature stream complete.`);
|
|
logger.info(`Feature response length: ${responseText.length} chars`);
|
|
logger.info('========== FULL RESPONSE TEXT ==========');
|
|
logger.info(responseText);
|
|
logger.info('========== END RESPONSE TEXT ==========');
|
|
|
|
await parseAndCreateFeatures(projectPath, responseText, events);
|
|
|
|
logger.debug('========== generateFeaturesFromSpec() completed ==========');
|
|
}
|