mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 20:43:36 +00:00
Merge main into massive-terminal-upgrade
Resolves merge conflicts: - apps/server/src/routes/terminal/common.ts: Keep randomBytes import, use @automaker/utils for createLogger - apps/ui/eslint.config.mjs: Use main's explicit globals list with XMLHttpRequest and MediaQueryListEvent additions - apps/ui/src/components/views/terminal-view.tsx: Keep our terminal improvements (killAllSessions, beforeunload, better error handling) - apps/ui/src/config/terminal-themes.ts: Keep our search highlight colors for all themes - apps/ui/src/store/app-store.ts: Keep our terminal settings persistence improvements (merge function) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
* Common utilities and state management for spec regeneration
|
||||
*/
|
||||
|
||||
import { createLogger } from "../../lib/logger.js";
|
||||
import { createLogger } from '@automaker/utils';
|
||||
|
||||
const logger = createLogger("SpecRegeneration");
|
||||
const logger = createLogger('SpecRegeneration');
|
||||
|
||||
// Shared state for tracking generation status - private
|
||||
let isRunning = false;
|
||||
@@ -23,10 +23,7 @@ export function getSpecRegenerationStatus(): {
|
||||
/**
|
||||
* Set the running state and abort controller
|
||||
*/
|
||||
export function setRunningState(
|
||||
running: boolean,
|
||||
controller: AbortController | null = null
|
||||
): void {
|
||||
export function setRunningState(running: boolean, controller: AbortController | null = null): void {
|
||||
isRunning = running;
|
||||
currentAbortController = controller;
|
||||
}
|
||||
@@ -40,14 +37,12 @@ export function logAuthStatus(context: string): void {
|
||||
logger.info(`${context} - Auth Status:`);
|
||||
logger.info(
|
||||
` ANTHROPIC_API_KEY: ${
|
||||
hasApiKey
|
||||
? "SET (" + process.env.ANTHROPIC_API_KEY?.substring(0, 20) + "...)"
|
||||
: "NOT SET"
|
||||
hasApiKey ? 'SET (' + process.env.ANTHROPIC_API_KEY?.substring(0, 20) + '...)' : 'NOT SET'
|
||||
}`
|
||||
);
|
||||
|
||||
if (!hasApiKey) {
|
||||
logger.warn("⚠️ WARNING: No authentication configured! SDK will fail.");
|
||||
logger.warn('⚠️ WARNING: No authentication configured! SDK will fail.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,16 +51,13 @@ export function logAuthStatus(context: string): void {
|
||||
*/
|
||||
export function logError(error: unknown, context: string): void {
|
||||
logger.error(`❌ ${context}:`);
|
||||
logger.error("Error name:", (error as any)?.name);
|
||||
logger.error("Error message:", (error as Error)?.message);
|
||||
logger.error("Error stack:", (error as Error)?.stack);
|
||||
logger.error(
|
||||
"Full error object:",
|
||||
JSON.stringify(error, Object.getOwnPropertyNames(error), 2)
|
||||
);
|
||||
logger.error('Error name:', (error as any)?.name);
|
||||
logger.error('Error message:', (error as Error)?.message);
|
||||
logger.error('Error stack:', (error as Error)?.stack);
|
||||
logger.error('Full error object:', JSON.stringify(error, Object.getOwnPropertyNames(error), 2));
|
||||
}
|
||||
|
||||
import { getErrorMessage as getErrorMessageShared } from "../common.js";
|
||||
import { getErrorMessage as getErrorMessageShared } from '../common.js';
|
||||
|
||||
// Re-export shared utility
|
||||
export { getErrorMessageShared as getErrorMessage };
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
* Generate features from existing app_spec.txt
|
||||
*/
|
||||
|
||||
import { query } from "@anthropic-ai/claude-agent-sdk";
|
||||
import fs from "fs/promises";
|
||||
import type { EventEmitter } from "../../lib/events.js";
|
||||
import { createLogger } from "../../lib/logger.js";
|
||||
import { createFeatureGenerationOptions } from "../../lib/sdk-options.js";
|
||||
import { logAuthStatus } from "./common.js";
|
||||
import { parseAndCreateFeatures } from "./parse-and-create-features.js";
|
||||
import { getAppSpecPath } from "../../lib/automaker-paths.js";
|
||||
import { query } from '@anthropic-ai/claude-agent-sdk';
|
||||
import * as secureFs from '../../lib/secure-fs.js';
|
||||
import type { EventEmitter } from '../../lib/events.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import { createFeatureGenerationOptions } from '../../lib/sdk-options.js';
|
||||
import { logAuthStatus } from './common.js';
|
||||
import { parseAndCreateFeatures } from './parse-and-create-features.js';
|
||||
import { getAppSpecPath } from '@automaker/platform';
|
||||
|
||||
const logger = createLogger("SpecRegeneration");
|
||||
const logger = createLogger('SpecRegeneration');
|
||||
|
||||
const DEFAULT_MAX_FEATURES = 50;
|
||||
|
||||
@@ -22,28 +22,26 @@ export async function generateFeaturesFromSpec(
|
||||
maxFeatures?: number
|
||||
): Promise<void> {
|
||||
const featureCount = maxFeatures ?? DEFAULT_MAX_FEATURES;
|
||||
logger.debug("========== generateFeaturesFromSpec() started ==========");
|
||||
logger.debug("projectPath:", projectPath);
|
||||
logger.debug("maxFeatures:", featureCount);
|
||||
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);
|
||||
logger.debug('Reading spec from:', specPath);
|
||||
|
||||
try {
|
||||
spec = await fs.readFile(specPath, "utf-8");
|
||||
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)}`
|
||||
);
|
||||
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.",
|
||||
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;
|
||||
@@ -82,16 +80,14 @@ 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 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 ==========");
|
||||
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",
|
||||
events.emit('spec-regeneration:event', {
|
||||
type: 'spec_regeneration_progress',
|
||||
content: 'Analyzing spec and generating features...\n',
|
||||
projectPath: projectPath,
|
||||
});
|
||||
|
||||
@@ -100,73 +96,67 @@ IMPORTANT: Do not ask for clarification. The specification is provided above. Ge
|
||||
abortController,
|
||||
});
|
||||
|
||||
logger.debug("SDK Options:", JSON.stringify(options, null, 2));
|
||||
logger.info("Calling Claude Agent SDK query() for features...");
|
||||
logger.debug('SDK Options:', JSON.stringify(options, null, 2));
|
||||
logger.info('Calling Claude Agent SDK query() for features...');
|
||||
|
||||
logAuthStatus("Right before SDK query() for features");
|
||||
logAuthStatus('Right before SDK query() for features');
|
||||
|
||||
let stream;
|
||||
try {
|
||||
stream = query({ prompt, options });
|
||||
logger.debug("query() returned stream successfully");
|
||||
logger.debug('query() returned stream successfully');
|
||||
} catch (queryError) {
|
||||
logger.error("❌ query() threw an exception:");
|
||||
logger.error("Error:", queryError);
|
||||
logger.error('❌ query() threw an exception:');
|
||||
logger.error('Error:', queryError);
|
||||
throw queryError;
|
||||
}
|
||||
|
||||
let responseText = "";
|
||||
let responseText = '';
|
||||
let messageCount = 0;
|
||||
|
||||
logger.debug("Starting to iterate over feature stream...");
|
||||
logger.debug('Starting to iterate over feature stream...');
|
||||
|
||||
try {
|
||||
for await (const msg of stream) {
|
||||
messageCount++;
|
||||
logger.debug(
|
||||
`Feature stream message #${messageCount}:`,
|
||||
JSON.stringify(
|
||||
{ type: msg.type, subtype: (msg as any).subtype },
|
||||
null,
|
||||
2
|
||||
)
|
||||
JSON.stringify({ type: msg.type, subtype: (msg as any).subtype }, null, 2)
|
||||
);
|
||||
|
||||
if (msg.type === "assistant" && msg.message.content) {
|
||||
if (msg.type === 'assistant' && msg.message.content) {
|
||||
for (const block of msg.message.content) {
|
||||
if (block.type === "text") {
|
||||
if (block.type === 'text') {
|
||||
responseText += block.text;
|
||||
logger.debug(
|
||||
`Feature text block received (${block.text.length} chars)`
|
||||
);
|
||||
events.emit("spec-regeneration:event", {
|
||||
type: "spec_regeneration_progress",
|
||||
logger.debug(`Feature text block received (${block.text.length} chars)`);
|
||||
events.emit('spec-regeneration:event', {
|
||||
type: 'spec_regeneration_progress',
|
||||
content: block.text,
|
||||
projectPath: projectPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (msg.type === "result" && (msg as any).subtype === "success") {
|
||||
logger.debug("Received success result for features");
|
||||
} else if (msg.type === 'result' && (msg as any).subtype === 'success') {
|
||||
logger.debug('Received success result for features');
|
||||
responseText = (msg as any).result || responseText;
|
||||
} else if ((msg as { type: string }).type === "error") {
|
||||
logger.error("❌ Received error message from feature stream:");
|
||||
logger.error("Error message:", JSON.stringify(msg, null, 2));
|
||||
} else if ((msg as { type: string }).type === 'error') {
|
||||
logger.error('❌ Received error message from feature stream:');
|
||||
logger.error('Error message:', JSON.stringify(msg, null, 2));
|
||||
}
|
||||
}
|
||||
} catch (streamError) {
|
||||
logger.error("❌ Error while iterating feature stream:");
|
||||
logger.error("Stream error:", streamError);
|
||||
logger.error('❌ Error while iterating feature stream:');
|
||||
logger.error('Stream error:', streamError);
|
||||
throw streamError;
|
||||
}
|
||||
|
||||
logger.info(`Feature stream complete. Total messages: ${messageCount}`);
|
||||
logger.info(`Feature response length: ${responseText.length} chars`);
|
||||
logger.info("========== FULL RESPONSE TEXT ==========");
|
||||
logger.info('========== FULL RESPONSE TEXT ==========');
|
||||
logger.info(responseText);
|
||||
logger.info("========== END RESPONSE TEXT ==========");
|
||||
logger.info('========== END RESPONSE TEXT ==========');
|
||||
|
||||
await parseAndCreateFeatures(projectPath, responseText, events);
|
||||
|
||||
logger.debug("========== generateFeaturesFromSpec() completed ==========");
|
||||
logger.debug('========== generateFeaturesFromSpec() completed ==========');
|
||||
}
|
||||
|
||||
@@ -2,23 +2,23 @@
|
||||
* Generate app_spec.txt from project overview
|
||||
*/
|
||||
|
||||
import { query } from "@anthropic-ai/claude-agent-sdk";
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
import type { EventEmitter } from "../../lib/events.js";
|
||||
import { query } from '@anthropic-ai/claude-agent-sdk';
|
||||
import path from 'path';
|
||||
import * as secureFs from '../../lib/secure-fs.js';
|
||||
import type { EventEmitter } from '../../lib/events.js';
|
||||
import {
|
||||
specOutputSchema,
|
||||
specToXml,
|
||||
getStructuredSpecPromptInstruction,
|
||||
type SpecOutput,
|
||||
} from "../../lib/app-spec-format.js";
|
||||
import { createLogger } from "../../lib/logger.js";
|
||||
import { createSpecGenerationOptions } from "../../lib/sdk-options.js";
|
||||
import { logAuthStatus } from "./common.js";
|
||||
import { generateFeaturesFromSpec } from "./generate-features-from-spec.js";
|
||||
import { ensureAutomakerDir, getAppSpecPath } from "../../lib/automaker-paths.js";
|
||||
} from '../../lib/app-spec-format.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import { createSpecGenerationOptions } from '../../lib/sdk-options.js';
|
||||
import { logAuthStatus } from './common.js';
|
||||
import { generateFeaturesFromSpec } from './generate-features-from-spec.js';
|
||||
import { ensureAutomakerDir, getAppSpecPath } from '@automaker/platform';
|
||||
|
||||
const logger = createLogger("SpecRegeneration");
|
||||
const logger = createLogger('SpecRegeneration');
|
||||
|
||||
export async function generateSpec(
|
||||
projectPath: string,
|
||||
@@ -29,17 +29,17 @@ export async function generateSpec(
|
||||
analyzeProject?: boolean,
|
||||
maxFeatures?: number
|
||||
): Promise<void> {
|
||||
logger.info("========== generateSpec() started ==========");
|
||||
logger.info("projectPath:", projectPath);
|
||||
logger.info("projectOverview length:", `${projectOverview.length} chars`);
|
||||
logger.info("projectOverview preview:", projectOverview.substring(0, 300));
|
||||
logger.info("generateFeatures:", generateFeatures);
|
||||
logger.info("analyzeProject:", analyzeProject);
|
||||
logger.info("maxFeatures:", maxFeatures);
|
||||
logger.info('========== generateSpec() started ==========');
|
||||
logger.info('projectPath:', projectPath);
|
||||
logger.info('projectOverview length:', `${projectOverview.length} chars`);
|
||||
logger.info('projectOverview preview:', projectOverview.substring(0, 300));
|
||||
logger.info('generateFeatures:', generateFeatures);
|
||||
logger.info('analyzeProject:', analyzeProject);
|
||||
logger.info('maxFeatures:', maxFeatures);
|
||||
|
||||
// Build the prompt based on whether we should analyze the project
|
||||
let analysisInstructions = "";
|
||||
let techStackDefaults = "";
|
||||
let analysisInstructions = '';
|
||||
let techStackDefaults = '';
|
||||
|
||||
if (analyzeProject !== false) {
|
||||
// Default to true - analyze the project
|
||||
@@ -73,114 +73,110 @@ ${analysisInstructions}
|
||||
|
||||
${getStructuredSpecPromptInstruction()}`;
|
||||
|
||||
logger.info("========== PROMPT BEING SENT ==========");
|
||||
logger.info('========== PROMPT BEING SENT ==========');
|
||||
logger.info(`Prompt length: ${prompt.length} chars`);
|
||||
logger.info(`Prompt preview (first 500 chars):\n${prompt.substring(0, 500)}`);
|
||||
logger.info("========== END PROMPT PREVIEW ==========");
|
||||
logger.info('========== END PROMPT PREVIEW ==========');
|
||||
|
||||
events.emit("spec-regeneration:event", {
|
||||
type: "spec_progress",
|
||||
content: "Starting spec generation...\n",
|
||||
events.emit('spec-regeneration:event', {
|
||||
type: 'spec_progress',
|
||||
content: 'Starting spec generation...\n',
|
||||
});
|
||||
|
||||
const options = createSpecGenerationOptions({
|
||||
cwd: projectPath,
|
||||
abortController,
|
||||
outputFormat: {
|
||||
type: "json_schema",
|
||||
type: 'json_schema',
|
||||
schema: specOutputSchema,
|
||||
},
|
||||
});
|
||||
|
||||
logger.debug("SDK Options:", JSON.stringify(options, null, 2));
|
||||
logger.info("Calling Claude Agent SDK query()...");
|
||||
logger.debug('SDK Options:', JSON.stringify(options, null, 2));
|
||||
logger.info('Calling Claude Agent SDK query()...');
|
||||
|
||||
// Log auth status right before the SDK call
|
||||
logAuthStatus("Right before SDK query()");
|
||||
logAuthStatus('Right before SDK query()');
|
||||
|
||||
let stream;
|
||||
try {
|
||||
stream = query({ prompt, options });
|
||||
logger.debug("query() returned stream successfully");
|
||||
logger.debug('query() returned stream successfully');
|
||||
} catch (queryError) {
|
||||
logger.error("❌ query() threw an exception:");
|
||||
logger.error("Error:", queryError);
|
||||
logger.error('❌ query() threw an exception:');
|
||||
logger.error('Error:', queryError);
|
||||
throw queryError;
|
||||
}
|
||||
|
||||
let responseText = "";
|
||||
let responseText = '';
|
||||
let messageCount = 0;
|
||||
let structuredOutput: SpecOutput | null = null;
|
||||
|
||||
logger.info("Starting to iterate over stream...");
|
||||
logger.info('Starting to iterate over stream...');
|
||||
|
||||
try {
|
||||
for await (const msg of stream) {
|
||||
messageCount++;
|
||||
logger.info(
|
||||
`Stream message #${messageCount}: type=${msg.type}, subtype=${
|
||||
(msg as any).subtype
|
||||
}`
|
||||
`Stream message #${messageCount}: type=${msg.type}, subtype=${(msg as any).subtype}`
|
||||
);
|
||||
|
||||
if (msg.type === "assistant") {
|
||||
if (msg.type === 'assistant') {
|
||||
const msgAny = msg as any;
|
||||
if (msgAny.message?.content) {
|
||||
for (const block of msgAny.message.content) {
|
||||
if (block.type === "text") {
|
||||
if (block.type === 'text') {
|
||||
responseText += block.text;
|
||||
logger.info(
|
||||
`Text block received (${block.text.length} chars), total now: ${responseText.length} chars`
|
||||
);
|
||||
events.emit("spec-regeneration:event", {
|
||||
type: "spec_regeneration_progress",
|
||||
events.emit('spec-regeneration:event', {
|
||||
type: 'spec_regeneration_progress',
|
||||
content: block.text,
|
||||
projectPath: projectPath,
|
||||
});
|
||||
} else if (block.type === "tool_use") {
|
||||
logger.info("Tool use:", block.name);
|
||||
events.emit("spec-regeneration:event", {
|
||||
type: "spec_tool",
|
||||
} 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 if (msg.type === "result" && (msg as any).subtype === "success") {
|
||||
logger.info("Received success result");
|
||||
} else if (msg.type === 'result' && (msg as any).subtype === 'success') {
|
||||
logger.info('Received success result');
|
||||
// Check for structured output - this is the reliable way to get spec data
|
||||
const resultMsg = msg as any;
|
||||
if (resultMsg.structured_output) {
|
||||
structuredOutput = resultMsg.structured_output as SpecOutput;
|
||||
logger.info("✅ Received structured output");
|
||||
logger.debug("Structured output:", JSON.stringify(structuredOutput, null, 2));
|
||||
logger.info('✅ Received structured output');
|
||||
logger.debug('Structured output:', JSON.stringify(structuredOutput, null, 2));
|
||||
} else {
|
||||
logger.warn("⚠️ No structured output in result, will fall back to text parsing");
|
||||
logger.warn('⚠️ No structured output in result, will fall back to text parsing');
|
||||
}
|
||||
} else if (msg.type === "result") {
|
||||
} else if (msg.type === 'result') {
|
||||
// Handle error result types
|
||||
const subtype = (msg as any).subtype;
|
||||
logger.info(`Result message: subtype=${subtype}`);
|
||||
if (subtype === "error_max_turns") {
|
||||
logger.error("❌ Hit max turns limit!");
|
||||
} else if (subtype === "error_max_structured_output_retries") {
|
||||
logger.error("❌ Failed to produce valid structured output after retries");
|
||||
throw new Error("Could not produce valid spec output");
|
||||
if (subtype === 'error_max_turns') {
|
||||
logger.error('❌ Hit max turns limit!');
|
||||
} else if (subtype === 'error_max_structured_output_retries') {
|
||||
logger.error('❌ Failed to produce valid structured output after retries');
|
||||
throw new Error('Could not produce valid spec output');
|
||||
}
|
||||
} else if ((msg as { type: string }).type === "error") {
|
||||
logger.error("❌ Received error message from stream:");
|
||||
logger.error("Error message:", JSON.stringify(msg, null, 2));
|
||||
} else if (msg.type === "user") {
|
||||
} else if ((msg as { type: string }).type === 'error') {
|
||||
logger.error('❌ Received error message from stream:');
|
||||
logger.error('Error message:', JSON.stringify(msg, null, 2));
|
||||
} else if (msg.type === 'user') {
|
||||
// Log user messages (tool results)
|
||||
logger.info(
|
||||
`User message (tool result): ${JSON.stringify(msg).substring(0, 500)}`
|
||||
);
|
||||
logger.info(`User message (tool result): ${JSON.stringify(msg).substring(0, 500)}`);
|
||||
}
|
||||
}
|
||||
} catch (streamError) {
|
||||
logger.error("❌ Error while iterating stream:");
|
||||
logger.error("Stream error:", streamError);
|
||||
logger.error('❌ Error while iterating stream:');
|
||||
logger.error('Stream error:', streamError);
|
||||
throw streamError;
|
||||
}
|
||||
|
||||
@@ -192,40 +188,42 @@ ${getStructuredSpecPromptInstruction()}`;
|
||||
|
||||
if (structuredOutput) {
|
||||
// Use structured output - convert JSON to XML
|
||||
logger.info("✅ Using structured output for XML generation");
|
||||
logger.info('✅ Using structured output for XML generation');
|
||||
xmlContent = specToXml(structuredOutput);
|
||||
logger.info(`Generated XML from structured output: ${xmlContent.length} chars`);
|
||||
} else {
|
||||
// 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 ==========");
|
||||
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");
|
||||
throw new Error('No response text and no structured output - cannot generate spec');
|
||||
}
|
||||
|
||||
const xmlStart = responseText.indexOf("<project_specification>");
|
||||
const xmlEnd = responseText.lastIndexOf("</project_specification>");
|
||||
const xmlStart = responseText.indexOf('<project_specification>');
|
||||
const xmlEnd = responseText.lastIndexOf('</project_specification>');
|
||||
|
||||
if (xmlStart !== -1 && xmlEnd !== -1) {
|
||||
// Extract just the XML content, discarding any conversational text before/after
|
||||
xmlContent = responseText.substring(xmlStart, xmlEnd + "</project_specification>".length);
|
||||
xmlContent = responseText.substring(xmlStart, xmlEnd + '</project_specification>'.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 <project_specification> XML structure");
|
||||
logger.error("This typically happens when structured output failed and the agent produced conversational text instead of XML");
|
||||
logger.error('❌ Response does not contain valid <project_specification> 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 <project_specification> tags. " +
|
||||
"Please try again."
|
||||
'Failed to generate spec: No valid XML structure found in response. ' +
|
||||
'The response contained conversational text but no <project_specification> tags. ' +
|
||||
'Please try again.'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -234,60 +232,55 @@ ${getStructuredSpecPromptInstruction()}`;
|
||||
await ensureAutomakerDir(projectPath);
|
||||
const specPath = getAppSpecPath(projectPath);
|
||||
|
||||
logger.info("Saving spec to:", specPath);
|
||||
logger.info('Saving spec to:', specPath);
|
||||
logger.info(`Content to save (${xmlContent.length} chars)`);
|
||||
|
||||
await fs.writeFile(specPath, xmlContent);
|
||||
await secureFs.writeFile(specPath, xmlContent);
|
||||
|
||||
// Verify the file was written
|
||||
const savedContent = await fs.readFile(specPath, "utf-8");
|
||||
const savedContent = await secureFs.readFile(specPath, 'utf-8');
|
||||
logger.info(`Verified saved file: ${savedContent.length} chars`);
|
||||
if (savedContent.length === 0) {
|
||||
logger.error("❌ File was saved but is empty!");
|
||||
logger.error('❌ File was saved but is empty!');
|
||||
}
|
||||
|
||||
logger.info("Spec saved successfully");
|
||||
logger.info('Spec saved successfully');
|
||||
|
||||
// Emit spec completion event
|
||||
if (generateFeatures) {
|
||||
// If features will be generated, emit intermediate completion
|
||||
events.emit("spec-regeneration:event", {
|
||||
type: "spec_regeneration_progress",
|
||||
content: "[Phase: spec_complete] Spec created! Generating features...\n",
|
||||
events.emit('spec-regeneration:event', {
|
||||
type: 'spec_regeneration_progress',
|
||||
content: '[Phase: spec_complete] Spec created! Generating features...\n',
|
||||
projectPath: projectPath,
|
||||
});
|
||||
} else {
|
||||
// If no features, emit final completion
|
||||
events.emit("spec-regeneration:event", {
|
||||
type: "spec_regeneration_complete",
|
||||
message: "Spec regeneration complete!",
|
||||
events.emit('spec-regeneration:event', {
|
||||
type: 'spec_regeneration_complete',
|
||||
message: 'Spec regeneration complete!',
|
||||
projectPath: projectPath,
|
||||
});
|
||||
}
|
||||
|
||||
// If generate features was requested, generate them from the spec
|
||||
if (generateFeatures) {
|
||||
logger.info("Starting feature generation from spec...");
|
||||
logger.info('Starting feature generation from spec...');
|
||||
// Create a new abort controller for feature generation
|
||||
const featureAbortController = new AbortController();
|
||||
try {
|
||||
await generateFeaturesFromSpec(
|
||||
projectPath,
|
||||
events,
|
||||
featureAbortController,
|
||||
maxFeatures
|
||||
);
|
||||
await generateFeaturesFromSpec(projectPath, events, featureAbortController, maxFeatures);
|
||||
// Final completion will be emitted by generateFeaturesFromSpec -> parseAndCreateFeatures
|
||||
} catch (featureError) {
|
||||
logger.error("Feature generation failed:", featureError);
|
||||
logger.error('Feature generation failed:', featureError);
|
||||
// Don't throw - spec generation succeeded, feature generation is optional
|
||||
events.emit("spec-regeneration:event", {
|
||||
type: "spec_regeneration_error",
|
||||
error: (featureError as Error).message || "Feature generation failed",
|
||||
events.emit('spec-regeneration:event', {
|
||||
type: 'spec_regeneration_error',
|
||||
error: (featureError as Error).message || 'Feature generation failed',
|
||||
projectPath: projectPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("========== generateSpec() completed ==========");
|
||||
logger.debug('========== generateSpec() completed ==========');
|
||||
}
|
||||
|
||||
@@ -2,71 +2,71 @@
|
||||
* Parse agent response and create feature files
|
||||
*/
|
||||
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
import type { EventEmitter } from "../../lib/events.js";
|
||||
import { createLogger } from "../../lib/logger.js";
|
||||
import { getFeaturesDir } from "../../lib/automaker-paths.js";
|
||||
import path from 'path';
|
||||
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';
|
||||
|
||||
const logger = createLogger("SpecRegeneration");
|
||||
const logger = createLogger('SpecRegeneration');
|
||||
|
||||
export async function parseAndCreateFeatures(
|
||||
projectPath: string,
|
||||
content: string,
|
||||
events: EventEmitter
|
||||
): Promise<void> {
|
||||
logger.info("========== parseAndCreateFeatures() started ==========");
|
||||
logger.info('========== parseAndCreateFeatures() started ==========');
|
||||
logger.info(`Content length: ${content.length} chars`);
|
||||
logger.info("========== CONTENT RECEIVED FOR PARSING ==========");
|
||||
logger.info('========== CONTENT RECEIVED FOR PARSING ==========');
|
||||
logger.info(content);
|
||||
logger.info("========== END CONTENT ==========");
|
||||
logger.info('========== END CONTENT ==========');
|
||||
|
||||
try {
|
||||
// Extract JSON from response
|
||||
logger.info("Extracting 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");
|
||||
logger.error("Full content received:");
|
||||
logger.error('❌ No valid JSON found in response');
|
||||
logger.error('Full content received:');
|
||||
logger.error(content);
|
||||
throw new Error("No valid JSON found in response");
|
||||
throw new Error('No valid JSON found in response');
|
||||
}
|
||||
|
||||
logger.info(`JSON match found (${jsonMatch[0].length} chars)`);
|
||||
logger.info("========== MATCHED JSON ==========");
|
||||
logger.info('========== MATCHED JSON ==========');
|
||||
logger.info(jsonMatch[0]);
|
||||
logger.info("========== END MATCHED JSON ==========");
|
||||
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));
|
||||
logger.info('Parsed features:', JSON.stringify(parsed.features, null, 2));
|
||||
|
||||
const featuresDir = getFeaturesDir(projectPath);
|
||||
await fs.mkdir(featuresDir, { recursive: true });
|
||||
await secureFs.mkdir(featuresDir, { recursive: true });
|
||||
|
||||
const createdFeatures: Array<{ id: string; title: string }> = [];
|
||||
|
||||
for (const feature of parsed.features) {
|
||||
logger.debug("Creating feature:", feature.id);
|
||||
logger.debug('Creating feature:', feature.id);
|
||||
const featureDir = path.join(featuresDir, feature.id);
|
||||
await fs.mkdir(featureDir, { recursive: true });
|
||||
await secureFs.mkdir(featureDir, { recursive: true });
|
||||
|
||||
const featureData = {
|
||||
id: feature.id,
|
||||
category: feature.category || "Uncategorized",
|
||||
category: feature.category || 'Uncategorized',
|
||||
title: feature.title,
|
||||
description: feature.description,
|
||||
status: "backlog", // Features go to backlog - user must manually start them
|
||||
status: 'backlog', // Features go to backlog - user must manually start them
|
||||
priority: feature.priority || 2,
|
||||
complexity: feature.complexity || "moderate",
|
||||
complexity: feature.complexity || 'moderate',
|
||||
dependencies: feature.dependencies || [],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(featureDir, "feature.json"),
|
||||
await secureFs.writeFile(
|
||||
path.join(featureDir, 'feature.json'),
|
||||
JSON.stringify(featureData, null, 2)
|
||||
);
|
||||
|
||||
@@ -75,20 +75,20 @@ export async function parseAndCreateFeatures(
|
||||
|
||||
logger.info(`✓ Created ${createdFeatures.length} features successfully`);
|
||||
|
||||
events.emit("spec-regeneration:event", {
|
||||
type: "spec_regeneration_complete",
|
||||
events.emit('spec-regeneration:event', {
|
||||
type: 'spec_regeneration_complete',
|
||||
message: `Spec regeneration complete! Created ${createdFeatures.length} features.`,
|
||||
projectPath: projectPath,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("❌ parseAndCreateFeatures() failed:");
|
||||
logger.error("Error:", error);
|
||||
events.emit("spec-regeneration:event", {
|
||||
type: "spec_regeneration_error",
|
||||
logger.error('❌ parseAndCreateFeatures() failed:');
|
||||
logger.error('Error:', error);
|
||||
events.emit('spec-regeneration:event', {
|
||||
type: 'spec_regeneration_error',
|
||||
error: (error as Error).message,
|
||||
projectPath: projectPath,
|
||||
});
|
||||
}
|
||||
|
||||
logger.debug("========== parseAndCreateFeatures() completed ==========");
|
||||
logger.debug('========== parseAndCreateFeatures() completed ==========');
|
||||
}
|
||||
|
||||
@@ -2,24 +2,24 @@
|
||||
* POST /create endpoint - Create project spec from overview
|
||||
*/
|
||||
|
||||
import type { Request, Response } from "express";
|
||||
import type { EventEmitter } from "../../../lib/events.js";
|
||||
import { createLogger } from "../../../lib/logger.js";
|
||||
import type { Request, Response } from 'express';
|
||||
import type { EventEmitter } from '../../../lib/events.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import {
|
||||
getSpecRegenerationStatus,
|
||||
setRunningState,
|
||||
logAuthStatus,
|
||||
logError,
|
||||
getErrorMessage,
|
||||
} from "../common.js";
|
||||
import { generateSpec } from "../generate-spec.js";
|
||||
} from '../common.js';
|
||||
import { generateSpec } from '../generate-spec.js';
|
||||
|
||||
const logger = createLogger("SpecRegeneration");
|
||||
const logger = createLogger('SpecRegeneration');
|
||||
|
||||
export function createCreateHandler(events: EventEmitter) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
logger.info("========== /create endpoint called ==========");
|
||||
logger.debug("Request body:", JSON.stringify(req.body, null, 2));
|
||||
logger.info('========== /create endpoint called ==========');
|
||||
logger.debug('Request body:', JSON.stringify(req.body, null, 2));
|
||||
|
||||
try {
|
||||
const { projectPath, projectOverview, generateFeatures, analyzeProject, maxFeatures } =
|
||||
@@ -31,37 +31,34 @@ export function createCreateHandler(events: EventEmitter) {
|
||||
maxFeatures?: number;
|
||||
};
|
||||
|
||||
logger.debug("Parsed params:");
|
||||
logger.debug(" projectPath:", projectPath);
|
||||
logger.debug(
|
||||
" projectOverview length:",
|
||||
`${projectOverview?.length || 0} chars`
|
||||
);
|
||||
logger.debug(" generateFeatures:", generateFeatures);
|
||||
logger.debug(" analyzeProject:", analyzeProject);
|
||||
logger.debug(" maxFeatures:", maxFeatures);
|
||||
logger.debug('Parsed params:');
|
||||
logger.debug(' projectPath:', projectPath);
|
||||
logger.debug(' projectOverview length:', `${projectOverview?.length || 0} chars`);
|
||||
logger.debug(' generateFeatures:', generateFeatures);
|
||||
logger.debug(' analyzeProject:', analyzeProject);
|
||||
logger.debug(' maxFeatures:', maxFeatures);
|
||||
|
||||
if (!projectPath || !projectOverview) {
|
||||
logger.error("Missing required parameters");
|
||||
logger.error('Missing required parameters');
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: "projectPath and projectOverview required",
|
||||
error: 'projectPath and projectOverview required',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const { isRunning } = getSpecRegenerationStatus();
|
||||
if (isRunning) {
|
||||
logger.warn("Generation already running, rejecting request");
|
||||
res.json({ success: false, error: "Spec generation already running" });
|
||||
logger.warn('Generation already running, rejecting request');
|
||||
res.json({ success: false, error: 'Spec generation already running' });
|
||||
return;
|
||||
}
|
||||
|
||||
logAuthStatus("Before starting generation");
|
||||
logAuthStatus('Before starting generation');
|
||||
|
||||
const abortController = new AbortController();
|
||||
setRunningState(true, abortController);
|
||||
logger.info("Starting background generation task...");
|
||||
logger.info('Starting background generation task...');
|
||||
|
||||
// Start generation in background
|
||||
generateSpec(
|
||||
@@ -74,24 +71,22 @@ export function createCreateHandler(events: EventEmitter) {
|
||||
maxFeatures
|
||||
)
|
||||
.catch((error) => {
|
||||
logError(error, "Generation failed with error");
|
||||
events.emit("spec-regeneration:event", {
|
||||
type: "spec_regeneration_error",
|
||||
logError(error, 'Generation failed with error');
|
||||
events.emit('spec-regeneration:event', {
|
||||
type: 'spec_regeneration_error',
|
||||
error: getErrorMessage(error),
|
||||
projectPath: projectPath,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
logger.info("Generation task finished (success or error)");
|
||||
logger.info('Generation task finished (success or error)');
|
||||
setRunningState(false, null);
|
||||
});
|
||||
|
||||
logger.info(
|
||||
"Returning success response (generation running in background)"
|
||||
);
|
||||
logger.info('Returning success response (generation running in background)');
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
logError(error, "Create spec route handler failed");
|
||||
logError(error, 'Create spec route handler failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,24 +2,24 @@
|
||||
* POST /generate-features endpoint - Generate features from existing spec
|
||||
*/
|
||||
|
||||
import type { Request, Response } from "express";
|
||||
import type { EventEmitter } from "../../../lib/events.js";
|
||||
import { createLogger } from "../../../lib/logger.js";
|
||||
import type { Request, Response } from 'express';
|
||||
import type { EventEmitter } from '../../../lib/events.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import {
|
||||
getSpecRegenerationStatus,
|
||||
setRunningState,
|
||||
logAuthStatus,
|
||||
logError,
|
||||
getErrorMessage,
|
||||
} from "../common.js";
|
||||
import { generateFeaturesFromSpec } from "../generate-features-from-spec.js";
|
||||
} from '../common.js';
|
||||
import { generateFeaturesFromSpec } from '../generate-features-from-spec.js';
|
||||
|
||||
const logger = createLogger("SpecRegeneration");
|
||||
const logger = createLogger('SpecRegeneration');
|
||||
|
||||
export function createGenerateFeaturesHandler(events: EventEmitter) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
logger.info("========== /generate-features endpoint called ==========");
|
||||
logger.debug("Request body:", JSON.stringify(req.body, null, 2));
|
||||
logger.info('========== /generate-features endpoint called ==========');
|
||||
logger.debug('Request body:', JSON.stringify(req.body, null, 2));
|
||||
|
||||
try {
|
||||
const { projectPath, maxFeatures } = req.body as {
|
||||
@@ -27,52 +27,45 @@ export function createGenerateFeaturesHandler(events: EventEmitter) {
|
||||
maxFeatures?: number;
|
||||
};
|
||||
|
||||
logger.debug("projectPath:", projectPath);
|
||||
logger.debug("maxFeatures:", maxFeatures);
|
||||
logger.debug('projectPath:', projectPath);
|
||||
logger.debug('maxFeatures:', maxFeatures);
|
||||
|
||||
if (!projectPath) {
|
||||
logger.error("Missing projectPath parameter");
|
||||
res.status(400).json({ success: false, error: "projectPath required" });
|
||||
logger.error('Missing projectPath parameter');
|
||||
res.status(400).json({ success: false, error: 'projectPath required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { isRunning } = getSpecRegenerationStatus();
|
||||
if (isRunning) {
|
||||
logger.warn("Generation already running, rejecting request");
|
||||
res.json({ success: false, error: "Generation already running" });
|
||||
logger.warn('Generation already running, rejecting request');
|
||||
res.json({ success: false, error: 'Generation already running' });
|
||||
return;
|
||||
}
|
||||
|
||||
logAuthStatus("Before starting feature generation");
|
||||
logAuthStatus('Before starting feature generation');
|
||||
|
||||
const abortController = new AbortController();
|
||||
setRunningState(true, abortController);
|
||||
logger.info("Starting background feature generation task...");
|
||||
logger.info('Starting background feature generation task...');
|
||||
|
||||
generateFeaturesFromSpec(
|
||||
projectPath,
|
||||
events,
|
||||
abortController,
|
||||
maxFeatures
|
||||
)
|
||||
generateFeaturesFromSpec(projectPath, events, abortController, maxFeatures)
|
||||
.catch((error) => {
|
||||
logError(error, "Feature generation failed with error");
|
||||
events.emit("spec-regeneration:event", {
|
||||
type: "features_error",
|
||||
logError(error, 'Feature generation failed with error');
|
||||
events.emit('spec-regeneration:event', {
|
||||
type: 'features_error',
|
||||
error: getErrorMessage(error),
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
logger.info("Feature generation task finished (success or error)");
|
||||
logger.info('Feature generation task finished (success or error)');
|
||||
setRunningState(false, null);
|
||||
});
|
||||
|
||||
logger.info(
|
||||
"Returning success response (generation running in background)"
|
||||
);
|
||||
logger.info('Returning success response (generation running in background)');
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
logError(error, "Generate features route handler failed");
|
||||
logError(error, 'Generate features route handler failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,71 +2,63 @@
|
||||
* POST /generate endpoint - Generate spec from project definition
|
||||
*/
|
||||
|
||||
import type { Request, Response } from "express";
|
||||
import type { EventEmitter } from "../../../lib/events.js";
|
||||
import { createLogger } from "../../../lib/logger.js";
|
||||
import type { Request, Response } from 'express';
|
||||
import type { EventEmitter } from '../../../lib/events.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import {
|
||||
getSpecRegenerationStatus,
|
||||
setRunningState,
|
||||
logAuthStatus,
|
||||
logError,
|
||||
getErrorMessage,
|
||||
} from "../common.js";
|
||||
import { generateSpec } from "../generate-spec.js";
|
||||
} from '../common.js';
|
||||
import { generateSpec } from '../generate-spec.js';
|
||||
|
||||
const logger = createLogger("SpecRegeneration");
|
||||
const logger = createLogger('SpecRegeneration');
|
||||
|
||||
export function createGenerateHandler(events: EventEmitter) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
logger.info("========== /generate endpoint called ==========");
|
||||
logger.debug("Request body:", JSON.stringify(req.body, null, 2));
|
||||
logger.info('========== /generate endpoint called ==========');
|
||||
logger.debug('Request body:', JSON.stringify(req.body, null, 2));
|
||||
|
||||
try {
|
||||
const {
|
||||
projectPath,
|
||||
projectDefinition,
|
||||
generateFeatures,
|
||||
analyzeProject,
|
||||
maxFeatures,
|
||||
} = req.body as {
|
||||
projectPath: string;
|
||||
projectDefinition: string;
|
||||
generateFeatures?: boolean;
|
||||
analyzeProject?: boolean;
|
||||
maxFeatures?: number;
|
||||
};
|
||||
const { projectPath, projectDefinition, generateFeatures, analyzeProject, maxFeatures } =
|
||||
req.body as {
|
||||
projectPath: string;
|
||||
projectDefinition: string;
|
||||
generateFeatures?: boolean;
|
||||
analyzeProject?: boolean;
|
||||
maxFeatures?: number;
|
||||
};
|
||||
|
||||
logger.debug("Parsed params:");
|
||||
logger.debug(" projectPath:", projectPath);
|
||||
logger.debug(
|
||||
" projectDefinition length:",
|
||||
`${projectDefinition?.length || 0} chars`
|
||||
);
|
||||
logger.debug(" generateFeatures:", generateFeatures);
|
||||
logger.debug(" analyzeProject:", analyzeProject);
|
||||
logger.debug(" maxFeatures:", maxFeatures);
|
||||
logger.debug('Parsed params:');
|
||||
logger.debug(' projectPath:', projectPath);
|
||||
logger.debug(' projectDefinition length:', `${projectDefinition?.length || 0} chars`);
|
||||
logger.debug(' generateFeatures:', generateFeatures);
|
||||
logger.debug(' analyzeProject:', analyzeProject);
|
||||
logger.debug(' maxFeatures:', maxFeatures);
|
||||
|
||||
if (!projectPath || !projectDefinition) {
|
||||
logger.error("Missing required parameters");
|
||||
logger.error('Missing required parameters');
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: "projectPath and projectDefinition required",
|
||||
error: 'projectPath and projectDefinition required',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const { isRunning } = getSpecRegenerationStatus();
|
||||
if (isRunning) {
|
||||
logger.warn("Generation already running, rejecting request");
|
||||
res.json({ success: false, error: "Spec generation already running" });
|
||||
logger.warn('Generation already running, rejecting request');
|
||||
res.json({ success: false, error: 'Spec generation already running' });
|
||||
return;
|
||||
}
|
||||
|
||||
logAuthStatus("Before starting generation");
|
||||
logAuthStatus('Before starting generation');
|
||||
|
||||
const abortController = new AbortController();
|
||||
setRunningState(true, abortController);
|
||||
logger.info("Starting background generation task...");
|
||||
logger.info('Starting background generation task...');
|
||||
|
||||
generateSpec(
|
||||
projectPath,
|
||||
@@ -78,24 +70,22 @@ export function createGenerateHandler(events: EventEmitter) {
|
||||
maxFeatures
|
||||
)
|
||||
.catch((error) => {
|
||||
logError(error, "Generation failed with error");
|
||||
events.emit("spec-regeneration:event", {
|
||||
type: "spec_regeneration_error",
|
||||
logError(error, 'Generation failed with error');
|
||||
events.emit('spec-regeneration:event', {
|
||||
type: 'spec_regeneration_error',
|
||||
error: getErrorMessage(error),
|
||||
projectPath: projectPath,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
logger.info("Generation task finished (success or error)");
|
||||
logger.info('Generation task finished (success or error)');
|
||||
setRunningState(false, null);
|
||||
});
|
||||
|
||||
logger.info(
|
||||
"Returning success response (generation running in background)"
|
||||
);
|
||||
logger.info('Returning success response (generation running in background)');
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
logError(error, "Generate spec route handler failed");
|
||||
logError(error, 'Generate spec route handler failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user