diff --git a/app/electron/services/feature-loader.js b/app/electron/services/feature-loader.js index 67e6f5df..d259bbf1 100644 --- a/app/electron/services/feature-loader.js +++ b/app/electron/services/feature-loader.js @@ -404,7 +404,7 @@ class FeatureLoader { status: status, images: [], imagePaths: [], - skipTests: true, + skipTests: false, // Auto-generated features should run tests by default model: "sonnet", thinkingLevel: "none", summary: summary || description || '', diff --git a/app/electron/services/spec-regeneration-service.js b/app/electron/services/spec-regeneration-service.js index 119d6445..9244e62b 100644 --- a/app/electron/services/spec-regeneration-service.js +++ b/app/electron/services/spec-regeneration-service.js @@ -461,8 +461,7 @@ Use the UpdateFeatureStatus tool to create features with ALL the fields above.`, const currentQuery = query({ prompt, options }); execution.query = currentQuery; - let toolCallCount = 0; - let messageCount = 0; + const counters = { toolCallCount: 0, messageCount: 0 }; try { for await (const msg of currentQuery) { @@ -472,67 +471,11 @@ Use the UpdateFeatureStatus tool to create features with ALL the fields above.`, } if (msg.type === "assistant" && msg.message?.content) { - messageCount++; - for (const block of msg.message.content) { - if (block.type === "text") { - const preview = block.text.substring(0, 100).replace(/\n/g, " "); - console.log(`[SpecRegeneration] Feature gen message #${messageCount}: ${preview}...`); - sendToRenderer({ - type: "spec_regeneration_progress", - content: block.text, - }); - } else if (block.type === "tool_use") { - toolCallCount++; - const toolName = block.name; - const toolInput = block.input; - console.log(`[SpecRegeneration] Feature gen tool call #${toolCallCount}: ${toolName}`); - - if (toolName === "mcp__automaker-tools__UpdateFeatureStatus" || toolName === "UpdateFeatureStatus") { - const featureId = toolInput?.featureId || "unknown"; - const status = toolInput?.status || "unknown"; - const summary = toolInput?.summary || ""; - sendToRenderer({ - type: "spec_regeneration_progress", - content: `\n[Feature Creation] Creating feature "${featureId}" with status "${status}"${summary ? `\n Summary: ${summary}` : ""}\n`, - }); - } else { - sendToRenderer({ - type: "spec_regeneration_progress", - content: `\n[Tool] Using ${toolName}...\n`, - }); - } - - sendToRenderer({ - type: "spec_regeneration_tool", - tool: toolName, - input: toolInput, - }); - } - } + this._handleAssistantMessage(msg, sendToRenderer, counters); } else if (msg.type === "tool_result") { - const toolName = msg.toolName || "unknown"; - const result = msg.content?.[0]?.text || JSON.stringify(msg.content); - const resultPreview = result.substring(0, 200).replace(/\n/g, " "); - console.log(`[SpecRegeneration] Feature gen tool result (${toolName}): ${resultPreview}...`); - - if (toolName === "mcp__automaker-tools__UpdateFeatureStatus" || toolName === "UpdateFeatureStatus") { - sendToRenderer({ - type: "spec_regeneration_progress", - content: `[Feature Creation] ${result}\n`, - }); - } else { - sendToRenderer({ - type: "spec_regeneration_progress", - content: `[Tool Result] ${toolName} completed successfully\n`, - }); - } + this._handleToolResult(msg, sendToRenderer); } else if (msg.type === "error") { - const errorMsg = msg.error?.message || JSON.stringify(msg.error); - console.error(`[SpecRegeneration] ERROR in feature generation stream: ${errorMsg}`); - sendToRenderer({ - type: "spec_regeneration_error", - error: `Error during feature generation: ${errorMsg}`, - }); + this._handleStreamError(msg, sendToRenderer); } } } catch (streamError) { @@ -544,7 +487,7 @@ Use the UpdateFeatureStatus tool to create features with ALL the fields above.`, throw streamError; } - console.log(`[SpecRegeneration] Feature generation completed - ${messageCount} messages, ${toolCallCount} tool calls`); + console.log(`[SpecRegeneration] Feature generation completed - ${counters.messageCount} messages, ${counters.toolCallCount} tool calls`); execution.query = null; execution.abortController = null; @@ -1003,6 +946,94 @@ Use this general structure: Begin by exploring the project structure.`; } + /** + * Handle assistant message in feature generation stream + * @private + */ + _handleAssistantMessage(msg, sendToRenderer, counters) { + counters.messageCount++; + for (const block of msg.message.content) { + if (block.type === "text") { + const preview = block.text.substring(0, 100).replace(/\n/g, " "); + console.log(`[SpecRegeneration] Feature gen message #${counters.messageCount}: ${preview}...`); + sendToRenderer({ + type: "spec_regeneration_progress", + content: block.text, + }); + } else if (block.type === "tool_use") { + this._handleToolUse(block, sendToRenderer, counters); + } + } + } + + /** + * Handle tool use block in feature generation stream + * @private + */ + _handleToolUse(block, sendToRenderer, counters) { + counters.toolCallCount++; + const toolName = block.name; + const toolInput = block.input; + console.log(`[SpecRegeneration] Feature gen tool call #${counters.toolCallCount}: ${toolName}`); + + if (toolName === "mcp__automaker-tools__UpdateFeatureStatus" || toolName === "UpdateFeatureStatus") { + const featureId = toolInput?.featureId || "unknown"; + const status = toolInput?.status || "unknown"; + const summary = toolInput?.summary || ""; + sendToRenderer({ + type: "spec_regeneration_progress", + content: `\n[Feature Creation] Creating feature "${featureId}" with status "${status}"${summary ? `\n Summary: ${summary}` : ""}\n`, + }); + } else { + sendToRenderer({ + type: "spec_regeneration_progress", + content: `\n[Tool] Using ${toolName}...\n`, + }); + } + + sendToRenderer({ + type: "spec_regeneration_tool", + tool: toolName, + input: toolInput, + }); + } + + /** + * Handle tool result in feature generation stream + * @private + */ + _handleToolResult(msg, sendToRenderer) { + const toolName = msg.toolName || "unknown"; + const result = msg.content?.[0]?.text || JSON.stringify(msg.content); + const resultPreview = result.substring(0, 200).replace(/\n/g, " "); + console.log(`[SpecRegeneration] Feature gen tool result (${toolName}): ${resultPreview}...`); + + if (toolName === "mcp__automaker-tools__UpdateFeatureStatus" || toolName === "UpdateFeatureStatus") { + sendToRenderer({ + type: "spec_regeneration_progress", + content: `[Feature Creation] ${result}\n`, + }); + } else { + sendToRenderer({ + type: "spec_regeneration_progress", + content: `[Tool Result] ${toolName} completed successfully\n`, + }); + } + } + + /** + * Handle error in feature generation stream + * @private + */ + _handleStreamError(msg, sendToRenderer) { + const errorMsg = msg.error?.message || JSON.stringify(msg.error); + console.error(`[SpecRegeneration] ERROR in feature generation stream: ${errorMsg}`); + sendToRenderer({ + type: "spec_regeneration_error", + error: `Error during feature generation: ${errorMsg}`, + }); + } + /** * Stop the current regeneration */ diff --git a/app/src/components/views/spec-view.tsx b/app/src/components/views/spec-view.tsx index 28e58216..608a25ab 100644 --- a/app/src/components/views/spec-view.tsx +++ b/app/src/components/views/spec-view.tsx @@ -19,6 +19,12 @@ import { Checkbox } from "@/components/ui/checkbox"; import { XmlSyntaxEditor } from "@/components/ui/xml-syntax-editor"; import type { SpecRegenerationEvent } from "@/types/electron"; +// Delay before reloading spec file to ensure it's written to disk +const SPEC_FILE_WRITE_DELAY = 500; + +// Interval for polling backend status during generation +const STATUS_CHECK_INTERVAL_MS = 2000; + export function SpecView() { const { currentProject, appSpec, setAppSpec } = useAppStore(); const [isLoading, setIsLoading] = useState(true); @@ -40,6 +46,9 @@ export function SpecView() { // Generate features only state const [isGeneratingFeatures, setIsGeneratingFeatures] = useState(false); + // Logs state (kept for internal tracking, but UI removed) + const [logs, setLogs] = useState(""); + const logsRef = useRef(""); // Phase tracking and status const [currentPhase, setCurrentPhase] = useState(""); @@ -246,7 +255,7 @@ export function SpecView() { loadSpec(); } } - }, 2000); + }, STATUS_CHECK_INTERVAL_MS); } } } catch (error) { @@ -310,7 +319,7 @@ export function SpecView() { } catch (error) { console.error("[SpecView] Periodic status check error:", error); } - }, 2000); // Check every 2 seconds (more frequent) + }, STATUS_CHECK_INTERVAL_MS); return () => { clearInterval(intervalId); @@ -342,7 +351,7 @@ export function SpecView() { // Small delay to ensure spec file is written setTimeout(() => { loadSpec(); - }, 500); + }, SPEC_FILE_WRITE_DELAY); } } @@ -357,7 +366,7 @@ export function SpecView() { stateRestoredRef.current = false; setTimeout(() => { loadSpec(); - }, 500); + }, SPEC_FILE_WRITE_DELAY); } // Append progress to logs @@ -399,37 +408,37 @@ export function SpecView() { logsRef.current = completionLog; setLogs(completionLog); - // Check if this is the final completion - const isFinalCompletion = event.message?.includes("All tasks completed") || - event.message === "All tasks completed!" || - event.message === "All tasks completed"; + // --- Completion Detection Logic --- + // Check 1: Message explicitly indicates all tasks are done + const isFinalCompletionMessage = event.message?.includes("All tasks completed") || + event.message === "All tasks completed!" || + event.message === "All tasks completed"; - // Check if we've already seen a completion phase in logs (including the message we just added) - const hasSeenCompletePhase = logsRef.current.includes("[Phase: complete]"); + // Check 2: We've seen a [Phase: complete] marker in the logs + const hasCompletePhase = logsRef.current.includes("[Phase: complete]"); - // Check recent logs for feature activity + // Check 3: Feature generation has finished (no recent activity and not actively generating) const recentLogs = logsRef.current.slice(-2000); const hasRecentFeatureActivity = recentLogs.includes("Feature Creation") || - recentLogs.includes("Creating feature") || - recentLogs.includes("UpdateFeatureStatus"); + recentLogs.includes("Creating feature") || + recentLogs.includes("UpdateFeatureStatus"); + const isStillGeneratingFeatures = !isFinalCompletionMessage && + !hasCompletePhase && + (event.message?.includes("Features are being generated") || + event.message?.includes("features are being generated")); + const isFeatureGenerationComplete = currentPhase === "feature_generation" && + !hasRecentFeatureActivity && + !isStillGeneratingFeatures; - // Check if we're still generating features (only for intermediate completion) - const isGeneratingFeatures = !isFinalCompletion && - !hasSeenCompletePhase && - (event.message?.includes("Features are being generated") || - event.message?.includes("features are being generated")); - - // If we're in feature_generation but no recent activity and we see completion, we're done - const shouldComplete = isFinalCompletion || - hasSeenCompletePhase || - (currentPhase === "feature_generation" && !hasRecentFeatureActivity && !isGeneratingFeatures); + // Determine if we should mark everything as complete + const shouldComplete = isFinalCompletionMessage || hasCompletePhase || isFeatureGenerationComplete; if (shouldComplete) { // Fully complete - clear all states immediately console.log("[SpecView] Final completion detected - clearing state", { - isFinalCompletion, - hasSeenCompletePhase, - shouldComplete, + isFinalCompletionMessage, + hasCompletePhase, + isFeatureGenerationComplete, hasRecentFeatureActivity, currentPhase, message: event.message @@ -452,7 +461,7 @@ export function SpecView() { setIsRegenerating(true); setCurrentPhase("feature_generation"); console.log("[SpecView] Spec complete, continuing with feature generation", { - isGeneratingFeatures, + isStillGeneratingFeatures, hasRecentFeatureActivity, currentPhase });