diff --git a/app/electron/auto-mode-service.js b/app/electron/auto-mode-service.js index 601683b2..f010e7a1 100644 --- a/app/electron/auto-mode-service.js +++ b/app/electron/auto-mode-service.js @@ -138,10 +138,16 @@ class AutoModeService { const result = await featureExecutor.implementFeature(feature, projectPath, sendToRenderer, execution); // Update feature status based on result - const newStatus = result.passes ? "verified" : "backlog"; + // For skipTests features, go to waiting_approval on success instead of verified + let newStatus; + if (result.passes) { + newStatus = feature.skipTests ? "waiting_approval" : "verified"; + } else { + newStatus = "backlog"; + } await featureLoader.updateFeatureStatus(feature.id, newStatus, projectPath); - // Delete context file if verified + // Delete context file only if verified (not for waiting_approval) if (newStatus === "verified") { await contextManager.deleteContextFile(projectPath, feature.id); } @@ -321,10 +327,16 @@ class AutoModeService { } // Update feature status based on final result - const newStatus = finalResult.passes ? "verified" : "in_progress"; + // For skipTests features, go to waiting_approval on success instead of verified + let newStatus; + if (finalResult.passes) { + newStatus = feature.skipTests ? "waiting_approval" : "verified"; + } else { + newStatus = "in_progress"; + } await featureLoader.updateFeatureStatus(featureId, newStatus, projectPath); - // Delete context file if verified + // Delete context file only if verified (not for waiting_approval) if (newStatus === "verified") { await contextManager.deleteContextFile(projectPath, featureId); } @@ -400,10 +412,16 @@ class AutoModeService { const result = await featureExecutor.implementFeature(nextFeature, projectPath, sendToRenderer, execution); // Update feature status based on result - const newStatus = result.passes ? "verified" : "backlog"; + // For skipTests features, go to waiting_approval on success instead of verified + let newStatus; + if (result.passes) { + newStatus = nextFeature.skipTests ? "waiting_approval" : "verified"; + } else { + newStatus = "backlog"; + } await featureLoader.updateFeatureStatus(nextFeature.id, newStatus, projectPath); - // Delete context file if verified + // Delete context file only if verified (not for waiting_approval) if (newStatus === "verified") { await contextManager.deleteContextFile(projectPath, nextFeature.id); } @@ -500,6 +518,179 @@ class AutoModeService { } } + /** + * Stop a specific feature by ID + */ + async stopFeature({ featureId }) { + if (!this.runningFeatures.has(featureId)) { + return { success: false, error: `Feature ${featureId} is not running` }; + } + + console.log(`[AutoMode] Stopping feature: ${featureId}`); + + const execution = this.runningFeatures.get(featureId); + if (execution && execution.abortController) { + execution.abortController.abort(); + } + + // Clean up + this.runningFeatures.delete(featureId); + + return { success: true }; + } + + /** + * Follow-up on a feature with additional prompt + * This continues work on a feature that's in waiting_approval status + */ + async followUpFeature({ projectPath, featureId, prompt, imagePaths, sendToRenderer }) { + // Check if this feature is already running + if (this.runningFeatures.has(featureId)) { + throw new Error(`Feature ${featureId} is already running`); + } + + console.log(`[AutoMode] Follow-up on feature: ${featureId} with prompt: ${prompt}`); + + // Register this feature as running + const execution = this.createExecutionContext(featureId); + execution.projectPath = projectPath; + execution.sendToRenderer = sendToRenderer; + this.runningFeatures.set(featureId, execution); + + try { + // Load features + const features = await featureLoader.loadFeatures(projectPath); + const feature = features.find((f) => f.id === featureId); + + if (!feature) { + throw new Error(`Feature ${featureId} not found`); + } + + console.log(`[AutoMode] Following up on feature: ${feature.description}`); + + // Update status to in_progress + await featureLoader.updateFeatureStatus(featureId, "in_progress", projectPath); + + sendToRenderer({ + type: "auto_mode_feature_start", + featureId: feature.id, + feature: feature, + }); + + // Read existing context and append follow-up prompt + const previousContext = await contextManager.readContextFile(projectPath, featureId); + + // Append follow-up prompt to context + const followUpContext = `${previousContext}\n\n## Follow-up Instructions\n\n${prompt}`; + await contextManager.writeToContextFile(projectPath, featureId, `\n\n## Follow-up Instructions\n\n${prompt}`); + + // Resume implementation with follow-up context and optional images + const result = await featureExecutor.resumeFeatureWithContext( + { ...feature, followUpPrompt: prompt, followUpImages: imagePaths }, + projectPath, + sendToRenderer, + followUpContext, + execution + ); + + // For skipTests features, go to waiting_approval on success instead of verified + const newStatus = result.passes + ? (feature.skipTests ? "waiting_approval" : "verified") + : "in_progress"; + + await featureLoader.updateFeatureStatus(feature.id, newStatus, projectPath); + + // Delete context file if verified (only for non-skipTests) + if (newStatus === "verified") { + await contextManager.deleteContextFile(projectPath, feature.id); + } + + sendToRenderer({ + type: "auto_mode_feature_complete", + featureId: feature.id, + passes: result.passes, + message: result.message, + }); + + return { success: true, passes: result.passes }; + } catch (error) { + console.error("[AutoMode] Error in follow-up:", error); + sendToRenderer({ + type: "auto_mode_error", + error: error.message, + featureId: featureId, + }); + throw error; + } finally { + this.runningFeatures.delete(featureId); + } + } + + /** + * Commit changes for a feature without doing additional work + * This marks the feature as verified and commits the changes + */ + async commitFeature({ projectPath, featureId, sendToRenderer }) { + console.log(`[AutoMode] Committing feature: ${featureId}`); + + // Register briefly as running for the commit operation + const execution = this.createExecutionContext(featureId); + execution.projectPath = projectPath; + execution.sendToRenderer = sendToRenderer; + this.runningFeatures.set(featureId, execution); + + try { + // Load feature to get description for commit message + const features = await featureLoader.loadFeatures(projectPath); + const feature = features.find((f) => f.id === featureId); + + if (!feature) { + throw new Error(`Feature ${featureId} not found`); + } + + sendToRenderer({ + type: "auto_mode_feature_start", + featureId: feature.id, + feature: { ...feature, description: "Committing changes..." }, + }); + + sendToRenderer({ + type: "auto_mode_phase", + featureId, + phase: "action", + message: "Committing changes to git...", + }); + + // Run git commit via the agent + const commitResult = await featureExecutor.commitChangesOnly(feature, projectPath, sendToRenderer, execution); + + // Update status to verified + await featureLoader.updateFeatureStatus(featureId, "verified", projectPath); + + // Delete context file + await contextManager.deleteContextFile(projectPath, featureId); + + sendToRenderer({ + type: "auto_mode_feature_complete", + featureId: feature.id, + passes: true, + message: "Changes committed successfully", + }); + + return { success: true }; + } catch (error) { + console.error("[AutoMode] Error committing feature:", error); + sendToRenderer({ + type: "auto_mode_error", + error: error.message, + featureId: featureId, + }); + throw error; + } finally { + this.runningFeatures.delete(featureId); + } + } + /** * Sleep helper */ diff --git a/app/electron/main.js b/app/electron/main.js index e55f3999..75d45d72 100644 --- a/app/electron/main.js +++ b/app/electron/main.js @@ -500,3 +500,54 @@ ipcMain.handle("auto-mode:analyze-project", async (_, { projectPath }) => { return { success: false, error: error.message }; } }); + +/** + * Stop a specific feature + */ +ipcMain.handle("auto-mode:stop-feature", async (_, { featureId }) => { + console.log("[IPC] auto-mode:stop-feature called with:", { featureId }); + try { + return await autoModeService.stopFeature({ featureId }); + } catch (error) { + console.error("[IPC] auto-mode:stop-feature error:", error); + return { success: false, error: error.message }; + } +}); + +/** + * Follow-up on a feature with additional prompt + */ +ipcMain.handle("auto-mode:follow-up-feature", async (_, { projectPath, featureId, prompt, imagePaths }) => { + console.log("[IPC] auto-mode:follow-up-feature called with:", { projectPath, featureId, prompt, imagePaths }); + try { + const sendToRenderer = (data) => { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send("auto-mode:event", data); + } + }; + + return await autoModeService.followUpFeature({ projectPath, featureId, prompt, imagePaths, sendToRenderer }); + } catch (error) { + console.error("[IPC] auto-mode:follow-up-feature error:", error); + return { success: false, error: error.message }; + } +}); + +/** + * Commit changes for a feature (no further work, just commit) + */ +ipcMain.handle("auto-mode:commit-feature", async (_, { projectPath, featureId }) => { + console.log("[IPC] auto-mode:commit-feature called with:", { projectPath, featureId }); + try { + const sendToRenderer = (data) => { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send("auto-mode:event", data); + } + }; + + return await autoModeService.commitFeature({ projectPath, featureId, sendToRenderer }); + } catch (error) { + console.error("[IPC] auto-mode:commit-feature error:", error); + return { success: false, error: error.message }; + } +}); diff --git a/app/electron/preload.js b/app/electron/preload.js index 0b6e03c2..1b5f49e0 100644 --- a/app/electron/preload.js +++ b/app/electron/preload.js @@ -115,6 +115,18 @@ contextBridge.exposeInMainWorld("electronAPI", { analyzeProject: (projectPath) => ipcRenderer.invoke("auto-mode:analyze-project", { projectPath }), + // Stop a specific feature + stopFeature: (featureId) => + ipcRenderer.invoke("auto-mode:stop-feature", { featureId }), + + // Follow-up on a feature with additional prompt + followUpFeature: (projectPath, featureId, prompt, imagePaths) => + ipcRenderer.invoke("auto-mode:follow-up-feature", { projectPath, featureId, prompt, imagePaths }), + + // Commit changes for a feature + commitFeature: (projectPath, featureId) => + ipcRenderer.invoke("auto-mode:commit-feature", { projectPath, featureId }), + // Listen for auto mode events onEvent: (callback) => { const subscription = (_, data) => callback(data); diff --git a/app/electron/services/feature-executor.js b/app/electron/services/feature-executor.js index b5109c8b..f3d99a57 100644 --- a/app/electron/services/feature-executor.js +++ b/app/electron/services/feature-executor.js @@ -182,10 +182,12 @@ class FeatureExecutor { content: checkingMsg, }); - // Re-load features to check if it was marked as verified + // Re-load features to check if it was marked as verified or waiting_approval (for skipTests) const updatedFeatures = await featureLoader.loadFeatures(projectPath); const updatedFeature = updatedFeatures.find((f) => f.id === feature.id); - const passes = updatedFeature?.status === "verified"; + // For skipTests features, waiting_approval is also considered a success + const passes = updatedFeature?.status === "verified" || + (updatedFeature?.skipTests && updatedFeature?.status === "waiting_approval"); // Send verification result const resultMsg = passes @@ -312,10 +314,12 @@ class FeatureExecutor { execution.query = null; execution.abortController = null; - // Check if feature was marked as verified + // Check if feature was marked as verified or waiting_approval (for skipTests) const updatedFeatures = await featureLoader.loadFeatures(projectPath); const updatedFeature = updatedFeatures.find((f) => f.id === feature.id); - const passes = updatedFeature?.status === "verified"; + // For skipTests features, waiting_approval is also considered a success + const passes = updatedFeature?.status === "verified" || + (updatedFeature?.skipTests && updatedFeature?.status === "waiting_approval"); const finalMsg = passes ? "āœ“ Feature successfully verified and completed\n" @@ -354,6 +358,138 @@ class FeatureExecutor { throw error; } } + + /** + * Commit changes for a feature without doing additional work + * Just runs git add and git commit with the feature description + */ + async commitChangesOnly(feature, projectPath, sendToRenderer, execution) { + console.log(`[FeatureExecutor] Committing changes for: ${feature.description}`); + + try { + const commitMessage = `\nšŸ“ Committing changes for: ${feature.description}\n`; + await contextManager.writeToContextFile(projectPath, feature.id, commitMessage); + + sendToRenderer({ + type: "auto_mode_progress", + featureId: feature.id, + content: "Committing changes to git...", + }); + + const abortController = new AbortController(); + execution.abortController = abortController; + + // Create custom MCP server with UpdateFeatureStatus tool + const featureToolsServer = mcpServerFactory.createFeatureToolsServer( + featureLoader.updateFeatureStatus.bind(featureLoader), + projectPath + ); + + const options = { + model: "claude-sonnet-4-20250514", // Use sonnet for simple commit task + systemPrompt: `You are a git assistant. Your only task is to commit the current changes with a proper commit message. + +IMPORTANT RULES: +- DO NOT modify any code +- DO NOT write tests +- DO NOT do anything except committing the existing changes +- Use the git command line tools via Bash`, + maxTurns: 10, // Short limit for simple task + cwd: projectPath, + mcpServers: { + "automaker-tools": featureToolsServer + }, + allowedTools: ["Bash", "mcp__automaker-tools__UpdateFeatureStatus"], + permissionMode: "acceptEdits", + sandbox: { + enabled: false, // Need to run git commands + }, + abortController: abortController, + }; + + // Simple commit prompt + const prompt = `Please commit the current changes with this commit message: + +"${feature.category}: ${feature.description}" + +Steps: +1. Run \`git add .\` to stage all changes +2. Run \`git commit -m "message"\` with the provided message +3. Report success + +Do NOT modify any code or run tests. Just commit the existing changes.`; + + const currentQuery = query({ prompt, options }); + execution.query = currentQuery; + + let responseText = ""; + for await (const msg of currentQuery) { + if (!execution.isActive()) break; + + if (msg.type === "assistant" && msg.message?.content) { + for (const block of msg.message.content) { + if (block.type === "text") { + responseText += block.text; + + await contextManager.writeToContextFile(projectPath, feature.id, block.text); + + sendToRenderer({ + type: "auto_mode_progress", + featureId: feature.id, + content: block.text, + }); + } else if (block.type === "tool_use") { + const toolMsg = `\nšŸ”§ Tool: ${block.name}\n`; + await contextManager.writeToContextFile(projectPath, feature.id, toolMsg); + + sendToRenderer({ + type: "auto_mode_tool", + featureId: feature.id, + tool: block.name, + input: block.input, + }); + } + } + } + } + + execution.query = null; + execution.abortController = null; + + const finalMsg = "āœ“ Changes committed successfully\n"; + await contextManager.writeToContextFile(projectPath, feature.id, finalMsg); + + sendToRenderer({ + type: "auto_mode_progress", + featureId: feature.id, + content: finalMsg, + }); + + return { + passes: true, + message: responseText.substring(0, 500), + }; + } catch (error) { + if (error instanceof AbortError || error?.name === "AbortError") { + console.log("[FeatureExecutor] Commit aborted"); + if (execution) { + execution.abortController = null; + execution.query = null; + } + return { + passes: false, + message: "Commit aborted", + }; + } + + console.error("[FeatureExecutor] Error committing feature:", error); + if (execution) { + execution.abortController = null; + execution.query = null; + } + throw error; + } + } } module.exports = new FeatureExecutor(); diff --git a/app/electron/services/feature-loader.js b/app/electron/services/feature-loader.js index 6d77c77c..5ad27319 100644 --- a/app/electron/services/feature-loader.js +++ b/app/electron/services/feature-loader.js @@ -51,13 +51,29 @@ class FeatureLoader { ".automaker", "feature_list.json" ); - const toSave = features.map((f) => ({ - id: f.id, - category: f.category, - description: f.description, - steps: f.steps, - status: f.status, - })); + const toSave = features.map((f) => { + const featureData = { + id: f.id, + category: f.category, + description: f.description, + steps: f.steps, + status: f.status, + }; + // Preserve optional fields if they exist + if (f.skipTests !== undefined) { + featureData.skipTests = f.skipTests; + } + if (f.images !== undefined) { + featureData.images = f.images; + } + if (f.imagePaths !== undefined) { + featureData.imagePaths = f.imagePaths; + } + if (f.startedAt !== undefined) { + featureData.startedAt = f.startedAt; + } + return featureData; + }); await fs.writeFile(featuresPath, JSON.stringify(toSave, null, 2), "utf-8"); console.log(`[FeatureLoader] Updated feature ${featureId}: status=${status}`); @@ -65,11 +81,12 @@ class FeatureLoader { /** * Select the next feature to implement - * Prioritizes: earlier features in the list that are not verified + * Prioritizes: earlier features in the list that are not verified or waiting_approval */ selectNextFeature(features) { // Find first feature that is in backlog or in_progress status - return features.find((f) => f.status !== "verified"); + // Skip verified and waiting_approval (which needs user input) + return features.find((f) => f.status !== "verified" && f.status !== "waiting_approval"); } } diff --git a/app/electron/services/feature-verifier.js b/app/electron/services/feature-verifier.js index 1d3b5708..000ee72c 100644 --- a/app/electron/services/feature-verifier.js +++ b/app/electron/services/feature-verifier.js @@ -101,10 +101,12 @@ class FeatureVerifier { execution.query = null; execution.abortController = null; - // Re-load features to check if it was marked as verified + // Re-load features to check if it was marked as verified or waiting_approval (for skipTests) const updatedFeatures = await featureLoader.loadFeatures(projectPath); const updatedFeature = updatedFeatures.find((f) => f.id === feature.id); - const passes = updatedFeature?.status === "verified"; + // For skipTests features, waiting_approval is also considered a success + const passes = updatedFeature?.status === "verified" || + (updatedFeature?.skipTests && updatedFeature?.status === "waiting_approval"); const finalMsg = passes ? "āœ“ Verification successful: All tests passed\n" diff --git a/app/electron/services/mcp-server-factory.js b/app/electron/services/mcp-server-factory.js index c508906b..65102729 100644 --- a/app/electron/services/mcp-server-factory.js +++ b/app/electron/services/mcp-server-factory.js @@ -1,5 +1,6 @@ const { createSdkMcpServer, tool } = require("@anthropic-ai/claude-agent-sdk"); const { z } = require("zod"); +const featureLoader = require("./feature-loader"); /** * MCP Server Factory - Creates custom MCP servers with tools @@ -18,22 +19,41 @@ class McpServerFactory { tools: [ tool( "UpdateFeatureStatus", - "Update the status of a feature in the feature list. Use this tool instead of directly modifying feature_list.json to safely update feature status.", + "Update the status of a feature in the feature list. Use this tool instead of directly modifying feature_list.json to safely update feature status. IMPORTANT: If the feature has skipTests=true, you should NOT mark it as verified - instead it will automatically go to waiting_approval status for manual review.", { featureId: z.string().describe("The ID of the feature to update"), - status: z.enum(["backlog", "in_progress", "verified"]).describe("The new status for the feature") + status: z.enum(["backlog", "in_progress", "verified"]).describe("The new status for the feature. Note: If skipTests=true, verified will be converted to waiting_approval automatically.") }, async (args) => { try { console.log(`[McpServerFactory] UpdateFeatureStatus tool called: featureId=${args.featureId}, status=${args.status}`); + // Load the feature to check skipTests flag + const features = await featureLoader.loadFeatures(projectPath); + const feature = features.find((f) => f.id === args.featureId); + + if (!feature) { + throw new Error(`Feature ${args.featureId} not found`); + } + + // If agent tries to mark as verified but feature has skipTests=true, convert to waiting_approval + let finalStatus = args.status; + if (args.status === "verified" && feature.skipTests === true) { + console.log(`[McpServerFactory] Feature ${args.featureId} has skipTests=true, converting verified -> waiting_approval`); + finalStatus = "waiting_approval"; + } + // Call the provided callback to update feature status - await updateFeatureStatusCallback(args.featureId, args.status, projectPath); + await updateFeatureStatusCallback(args.featureId, finalStatus, projectPath); + + const statusMessage = finalStatus !== args.status + ? `Successfully updated feature ${args.featureId} to status "${finalStatus}" (converted from "${args.status}" because skipTests=true)` + : `Successfully updated feature ${args.featureId} to status "${finalStatus}"`; return { content: [{ type: "text", - text: `Successfully updated feature ${args.featureId} to status "${args.status}"` + text: statusMessage }] }; } catch (error) { diff --git a/app/electron/services/prompt-builder.js b/app/electron/services/prompt-builder.js index a070bc38..8f6389bd 100644 --- a/app/electron/services/prompt-builder.js +++ b/app/electron/services/prompt-builder.js @@ -6,6 +6,10 @@ class PromptBuilder { * Build the prompt for implementing a specific feature */ buildFeaturePrompt(feature) { + const skipTestsNote = feature.skipTests + ? `\n**āš ļø IMPORTANT - Manual Testing Mode:**\nThis feature has skipTests=true, which means:\n- DO NOT commit changes automatically\n- DO NOT mark as verified - it will automatically go to "waiting_approval" status\n- The user will manually review and commit the changes\n- Just implement the feature and mark it as verified (it will be converted to waiting_approval)\n` + : ""; + return `You are working on a feature implementation task. **Current Feature to Implement:** @@ -13,7 +17,7 @@ class PromptBuilder { ID: ${feature.id} Category: ${feature.category} Description: ${feature.description} - +${skipTestsNote} **Steps to Complete:** ${feature.steps.map((step, i) => `${i + 1}. ${step}`).join("\n")} @@ -21,30 +25,34 @@ ${feature.steps.map((step, i) => `${i + 1}. ${step}`).join("\n")} 1. Read the project files to understand the current codebase structure 2. Implement the feature according to the description and steps -3. Write Playwright tests to verify the feature works correctly -4. Run the tests and ensure they pass -5. **DELETE the test file(s) you created** - tests are only for immediate verification -6. **CRITICAL: Use the UpdateFeatureStatus tool to mark this feature as verified** - DO NOT manually edit .automaker/feature_list.json -7. Commit your changes with git +${feature.skipTests + ? "3. Test the implementation manually (no automated tests needed for skipTests features)" + : "3. Write Playwright tests to verify the feature works correctly\n4. Run the tests and ensure they pass\n5. **DELETE the test file(s) you created** - tests are only for immediate verification"} +${feature.skipTests ? "4" : "6"}. **CRITICAL: Use the UpdateFeatureStatus tool to mark this feature as verified** - DO NOT manually edit .automaker/feature_list.json +${feature.skipTests + ? "5. **DO NOT commit changes** - the user will review and commit manually" + : "7. Commit your changes with git"} **IMPORTANT - Updating Feature Status:** -When you have completed the feature and all tests pass, you MUST use the \`mcp__automaker-tools__UpdateFeatureStatus\` tool to update the feature status: +When you have completed the feature${feature.skipTests ? "" : " and all tests pass"}, you MUST use the \`mcp__automaker-tools__UpdateFeatureStatus\` tool to update the feature status: - Call the tool with: featureId="${feature.id}" and status="verified" - **DO NOT manually edit the .automaker/feature_list.json file** - this can cause race conditions - The UpdateFeatureStatus tool safely updates the feature status without risk of corrupting other data +- **If skipTests=true, the tool will automatically convert "verified" to "waiting_approval"** - this is correct behavior **Important Guidelines:** - Focus ONLY on implementing this specific feature - Write clean, production-quality code - Add proper error handling -- Write comprehensive Playwright tests -- Ensure all existing tests still pass -- Mark the feature as passing only when all tests are green -- **CRITICAL: Delete test files after verification** - tests accumulate and become brittle +${feature.skipTests + ? "- Skip automated testing (skipTests=true) - user will manually verify" + : "- Write comprehensive Playwright tests\n- Ensure all existing tests still pass\n- Mark the feature as passing only when all tests are green\n- **CRITICAL: Delete test files after verification** - tests accumulate and become brittle"} - **CRITICAL: Use UpdateFeatureStatus tool instead of editing feature_list.json directly** -- Make a git commit when complete +${feature.skipTests + ? "- **DO NOT commit changes** - user will review and commit manually" + : "- Make a git commit when complete"} **Testing Utilities (CRITICAL):** @@ -75,6 +83,10 @@ Begin by reading the project structure and then implementing the feature.`; * Build the prompt for verifying a specific feature */ buildVerificationPrompt(feature) { + const skipTestsNote = feature.skipTests + ? `\n**āš ļø IMPORTANT - Manual Testing Mode:**\nThis feature has skipTests=true, which means:\n- DO NOT commit changes automatically\n- DO NOT mark as verified - it will automatically go to "waiting_approval" status\n- The user will manually review and commit the changes\n- Just implement the feature and mark it as verified (it will be converted to waiting_approval)\n` + : ""; + return `You are implementing and verifying a feature until it is complete and working correctly. **Feature to Implement/Verify:** @@ -83,7 +95,7 @@ ID: ${feature.id} Category: ${feature.category} Description: ${feature.description} Current Status: ${feature.status} - +${skipTestsNote} **Steps that should be implemented:** ${feature.steps.map((step, i) => `${i + 1}. ${step}`).join("\n")} @@ -91,7 +103,9 @@ ${feature.steps.map((step, i) => `${i + 1}. ${step}`).join("\n")} 1. Read the project files to understand the current implementation 2. If the feature is not fully implemented, continue implementing it -3. Write or update Playwright tests to verify the feature works correctly +${feature.skipTests + ? "3. Test the implementation manually (no automated tests needed for skipTests features)" + : `3. Write or update Playwright tests to verify the feature works correctly 4. Run the Playwright tests: npx playwright test tests/[feature-name].spec.ts 5. Check if all tests pass 6. **If ANY tests fail:** @@ -101,17 +115,19 @@ ${feature.steps.map((step, i) => `${i + 1}. ${step}`).join("\n")} - Re-run the tests to verify the fixes - **REPEAT this process until ALL tests pass** 7. **If ALL tests pass:** - - **DELETE the test file(s) for this feature** - tests are only for immediate verification - - **CRITICAL: Use the UpdateFeatureStatus tool to mark this feature as verified** - DO NOT manually edit .automaker/feature_list.json - - Explain what was implemented/fixed and that all tests passed - - Commit your changes with git + - **DELETE the test file(s) for this feature** - tests are only for immediate verification`} +${feature.skipTests ? "4" : "8"}. **CRITICAL: Use the UpdateFeatureStatus tool to mark this feature as verified** - DO NOT manually edit .automaker/feature_list.json +${feature.skipTests + ? "5. **DO NOT commit changes** - the user will review and commit manually" + : "9. Explain what was implemented/fixed and that all tests passed\n10. Commit your changes with git"} **IMPORTANT - Updating Feature Status:** -When all tests pass, you MUST use the \`mcp__automaker-tools__UpdateFeatureStatus\` tool to update the feature status: +When you have completed the feature${feature.skipTests ? "" : " and all tests pass"}, you MUST use the \`mcp__automaker-tools__UpdateFeatureStatus\` tool to update the feature status: - Call the tool with: featureId="${feature.id}" and status="verified" - **DO NOT manually edit the .automaker/feature_list.json file** - this can cause race conditions - The UpdateFeatureStatus tool safely updates the feature status without risk of corrupting other data +- **If skipTests=true, the tool will automatically convert "verified" to "waiting_approval"** - this is correct behavior **Testing Utilities:** - Check if tests/utils.ts exists and is being used @@ -126,13 +142,10 @@ rm tests/[feature-name].spec.ts \`\`\` **Important:** -- **CONTINUE IMPLEMENTING until all tests pass** - don't stop at the first failure -- Only mark as "verified" if Playwright tests pass -- **CRITICAL: Delete test files after they pass** - tests should not accumulate +${feature.skipTests + ? "- Skip automated testing (skipTests=true) - user will manually verify\n- **DO NOT commit changes** - user will review and commit manually" + : "- **CONTINUE IMPLEMENTING until all tests pass** - don't stop at the first failure\n- Only mark as verified if Playwright tests pass\n- **CRITICAL: Delete test files after they pass** - tests should not accumulate\n- Update test utilities if functionality changed\n- Make a git commit when the feature is complete\n- Be thorough and persistent in fixing issues"} - **CRITICAL: Use UpdateFeatureStatus tool instead of editing feature_list.json directly** -- Update test utilities if functionality changed -- Make a git commit when the feature is complete -- Be thorough and persistent in fixing issues Begin by reading the project structure and understanding what needs to be implemented or fixed.`; } @@ -141,6 +154,10 @@ Begin by reading the project structure and understanding what needs to be implem * Build prompt for resuming feature with previous context */ buildResumePrompt(feature, previousContext) { + const skipTestsNote = feature.skipTests + ? `\n**āš ļø IMPORTANT - Manual Testing Mode:**\nThis feature has skipTests=true, which means:\n- DO NOT commit changes automatically\n- DO NOT mark as verified - it will automatically go to "waiting_approval" status\n- The user will manually review and commit the changes\n- Just implement the feature and mark it as verified (it will be converted to waiting_approval)\n` + : ""; + return `You are resuming work on a feature implementation that was previously started. **Current Feature:** @@ -148,7 +165,7 @@ Begin by reading the project structure and understanding what needs to be implem ID: ${feature.id} Category: ${feature.category} Description: ${feature.description} - +${skipTestsNote} **Steps to Complete:** ${feature.steps.map((step, i) => `${i + 1}. ${step}`).join("\n")} @@ -162,29 +179,34 @@ Continue where you left off and complete the feature implementation: 1. Review the previous work context above to understand what has been done 2. Continue implementing the feature according to the description and steps -3. Write Playwright tests to verify the feature works correctly (if not already done) -4. Run the tests and ensure they pass -5. **DELETE the test file(s) you created** - tests are only for immediate verification -6. **CRITICAL: Use the UpdateFeatureStatus tool to mark this feature as verified** - DO NOT manually edit .automaker/feature_list.json -7. Commit your changes with git +${feature.skipTests + ? "3. Test the implementation manually (no automated tests needed for skipTests features)" + : "3. Write Playwright tests to verify the feature works correctly (if not already done)\n4. Run the tests and ensure they pass\n5. **DELETE the test file(s) you created** - tests are only for immediate verification"} +${feature.skipTests ? "4" : "6"}. **CRITICAL: Use the UpdateFeatureStatus tool to mark this feature as verified** - DO NOT manually edit .automaker/feature_list.json +${feature.skipTests + ? "5. **DO NOT commit changes** - the user will review and commit manually" + : "7. Commit your changes with git"} **IMPORTANT - Updating Feature Status:** -When all tests pass, you MUST use the \`mcp__automaker-tools__UpdateFeatureStatus\` tool to update the feature status: +When you have completed the feature${feature.skipTests ? "" : " and all tests pass"}, you MUST use the \`mcp__automaker-tools__UpdateFeatureStatus\` tool to update the feature status: - Call the tool with: featureId="${feature.id}" and status="verified" - **DO NOT manually edit the .automaker/feature_list.json file** - this can cause race conditions - The UpdateFeatureStatus tool safely updates the feature status without risk of corrupting other data +- **If skipTests=true, the tool will automatically convert "verified" to "waiting_approval"** - this is correct behavior **Important Guidelines:** - Review what was already done in the previous context - Don't redo work that's already complete - continue from where it left off - Focus on completing any remaining tasks -- Write comprehensive Playwright tests if not already done -- Ensure all tests pass before marking as verified -- **CRITICAL: Delete test files after verification** +${feature.skipTests + ? "- Skip automated testing (skipTests=true) - user will manually verify" + : "- Write comprehensive Playwright tests if not already done\n- Ensure all tests pass before marking as verified\n- **CRITICAL: Delete test files after verification**"} - **CRITICAL: Use UpdateFeatureStatus tool instead of editing feature_list.json directly** -- Make a git commit when complete +${feature.skipTests + ? "- **DO NOT commit changes** - user will review and commit manually" + : "- Make a git commit when complete"} Begin by assessing what's been done and what remains to be completed.`; } @@ -278,18 +300,27 @@ Begin by exploring the project structure.`; Your role is to: - Implement features exactly as specified - Write production-quality code -- Create comprehensive Playwright tests using testing utilities -- Ensure all tests pass before marking features complete -- **DELETE test files after successful verification** - tests are only for immediate feature verification +- Check if feature.skipTests is true - if so, skip automated testing and don't commit +- Create comprehensive Playwright tests using testing utilities (only if skipTests is false) +- Ensure all tests pass before marking features complete (only if skipTests is false) +- **DELETE test files after successful verification** - tests are only for immediate feature verification (only if skipTests is false) - **Use the UpdateFeatureStatus tool to mark features as verified** - NEVER manually edit feature_list.json -- Commit working code to git +- Commit working code to git (only if skipTests is false - skipTests features require manual review) - Be thorough and detail-oriented +**IMPORTANT - Manual Testing Mode (skipTests=true):** +If a feature has skipTests=true: +- DO NOT write automated tests +- DO NOT commit changes - the user will review and commit manually +- Still mark the feature as verified using UpdateFeatureStatus - it will automatically convert to "waiting_approval" for manual review +- The user will manually verify and commit the changes + **IMPORTANT - UpdateFeatureStatus Tool:** -You have access to the \`mcp__automaker-tools__UpdateFeatureStatus\` tool. When all tests pass, use this tool to update the feature status: +You have access to the \`mcp__automaker-tools__UpdateFeatureStatus\` tool. When the feature is complete${""} (and all tests pass if skipTests is false), use this tool to update the feature status: - Call with featureId and status="verified" - **DO NOT manually edit .automaker/feature_list.json** - this can cause race conditions and restore old state - The tool safely updates the status without corrupting other feature data +- **If skipTests=true, the tool will automatically convert "verified" to "waiting_approval"** - this is correct **Testing Utilities (CRITICAL):** - **Create and maintain tests/utils.ts** with helper functions for finding elements and common operations @@ -327,21 +358,30 @@ Focus on one feature at a time and complete it fully before finishing. Always de Your role is to: - **Continue implementing features until they are complete** - don't stop at the first failure -- Write or update code to fix failing tests -- Run Playwright tests to verify feature implementations -- If tests fail, analyze errors and fix the implementation -- If other tests fail, verify if those tests are still accurate or should be updated or deleted -- Continue rerunning tests and fixing issues until ALL tests pass -- **DELETE test files after successful verification** - tests are only for immediate feature verification +- Check if feature.skipTests is true - if so, skip automated testing and don't commit +- Write or update code to fix failing tests (only if skipTests is false) +- Run Playwright tests to verify feature implementations (only if skipTests is false) +- If tests fail, analyze errors and fix the implementation (only if skipTests is false) +- If other tests fail, verify if those tests are still accurate or should be updated or deleted (only if skipTests is false) +- Continue rerunning tests and fixing issues until ALL tests pass (only if skipTests is false) +- **DELETE test files after successful verification** - tests are only for immediate feature verification (only if skipTests is false) - **Use the UpdateFeatureStatus tool to mark features as verified** - NEVER manually edit feature_list.json -- **Update test utilities (tests/utils.ts) if functionality changed** - keep helpers in sync with code -- Commit working code to git +- **Update test utilities (tests/utils.ts) if functionality changed** - keep helpers in sync with code (only if skipTests is false) +- Commit working code to git (only if skipTests is false - skipTests features require manual review) + +**IMPORTANT - Manual Testing Mode (skipTests=true):** +If a feature has skipTests=true: +- DO NOT write automated tests +- DO NOT commit changes - the user will review and commit manually +- Still mark the feature as verified using UpdateFeatureStatus - it will automatically convert to "waiting_approval" for manual review +- The user will manually verify and commit the changes **IMPORTANT - UpdateFeatureStatus Tool:** -You have access to the \`mcp__automaker-tools__UpdateFeatureStatus\` tool. When all tests pass, use this tool to update the feature status: +You have access to the \`mcp__automaker-tools__UpdateFeatureStatus\` tool. When the feature is complete${""} (and all tests pass if skipTests is false), use this tool to update the feature status: - Call with featureId and status="verified" - **DO NOT manually edit .automaker/feature_list.json** - this can cause race conditions and restore old state - The tool safely updates the status without corrupting other feature data +- **If skipTests=true, the tool will automatically convert "verified" to "waiting_approval"** - this is correct **Testing Utilities:** - Check if tests/utils.ts needs updates based on code changes diff --git a/app/src/components/views/agent-output-modal.tsx b/app/src/components/views/agent-output-modal.tsx index e38a8799..14bb1787 100644 --- a/app/src/components/views/agent-output-modal.tsx +++ b/app/src/components/views/agent-output-modal.tsx @@ -205,7 +205,7 @@ export function AgentOutputModal({ className="max-w-4xl max-h-[80vh] flex flex-col" data-testid="agent-output-modal" > - +
@@ -238,7 +238,10 @@ export function AgentOutputModal({
- + {featureDescription}
@@ -266,7 +269,7 @@ export function AgentOutputModal({ )} -
+
{autoScrollRef.current ? "Auto-scrolling enabled" : "Scroll to bottom to enable auto-scroll"} diff --git a/app/src/components/views/board-view.tsx b/app/src/components/views/board-view.tsx index 46594c79..705030cf 100644 --- a/app/src/components/views/board-view.tsx +++ b/app/src/components/views/board-view.tsx @@ -44,7 +44,7 @@ import { KanbanColumn } from "./kanban-column"; import { KanbanCard } from "./kanban-card"; import { AutoModeLog } from "./auto-mode-log"; import { AgentOutputModal } from "./agent-output-modal"; -import { Plus, RefreshCw, Play, StopCircle, Loader2, ChevronUp, ChevronDown, Users, Trash2, FastForward, FlaskConical, CheckCircle2 } from "lucide-react"; +import { Plus, RefreshCw, Play, StopCircle, Loader2, ChevronUp, ChevronDown, Users, Trash2, FastForward, FlaskConical, CheckCircle2, MessageSquare, GitCommit } from "lucide-react"; import { toast } from "sonner"; import { Slider } from "@/components/ui/slider"; import { Checkbox } from "@/components/ui/checkbox"; @@ -60,6 +60,7 @@ type ColumnId = Feature["status"]; const COLUMNS: { id: ColumnId; title: string; color: string }[] = [ { id: "backlog", title: "Backlog", color: "bg-zinc-500" }, { id: "in_progress", title: "In Progress", color: "bg-yellow-500" }, + { id: "waiting_approval", title: "Waiting Approval", color: "bg-orange-500" }, { id: "verified", title: "Verified", color: "bg-green-500" }, ]; @@ -95,6 +96,10 @@ export function BoardView() { const [featuresWithContext, setFeaturesWithContext] = useState>(new Set()); const [showDeleteAllVerifiedDialog, setShowDeleteAllVerifiedDialog] = useState(false); const [persistedCategories, setPersistedCategories] = useState([]); + const [showFollowUpDialog, setShowFollowUpDialog] = useState(false); + const [followUpFeature, setFollowUpFeature] = useState(null); + const [followUpPrompt, setFollowUpPrompt] = useState(""); + const [followUpImagePaths, setFollowUpImagePaths] = useState([]); // Make current project available globally for modal useEffect(() => { @@ -688,6 +693,125 @@ export function BoardView() { }); }; + // Open follow-up dialog for waiting_approval features + const handleOpenFollowUp = (feature: Feature) => { + console.log("[Board] Opening follow-up dialog for feature:", { id: feature.id, description: feature.description }); + setFollowUpFeature(feature); + setFollowUpPrompt(""); + setFollowUpImagePaths([]); + setShowFollowUpDialog(true); + }; + + // Handle sending follow-up prompt + const handleSendFollowUp = async () => { + if (!currentProject || !followUpFeature || !followUpPrompt.trim()) return; + + console.log("[Board] Sending follow-up prompt for feature:", { + id: followUpFeature.id, + prompt: followUpPrompt, + imagePaths: followUpImagePaths + }); + + try { + const api = getElectronAPI(); + if (!api?.autoMode?.followUpFeature) { + console.error("Follow-up feature API not available"); + toast.error("Follow-up not available", { + description: "This feature is not available in the current version.", + }); + return; + } + + // Move feature back to in_progress before sending follow-up + updateFeature(followUpFeature.id, { status: "in_progress", startedAt: new Date().toISOString() }); + + // Call the API to send follow-up prompt + const result = await api.autoMode.followUpFeature( + currentProject.path, + followUpFeature.id, + followUpPrompt, + followUpImagePaths.map(img => img.path) + ); + + if (result.success) { + console.log("[Board] Follow-up started successfully"); + toast.success("Follow-up started", { + description: `Continuing work on: ${followUpFeature.description.slice(0, 50)}${followUpFeature.description.length > 50 ? "..." : ""}`, + }); + setShowFollowUpDialog(false); + setFollowUpFeature(null); + setFollowUpPrompt(""); + setFollowUpImagePaths([]); + } else { + console.error("[Board] Failed to send follow-up:", result.error); + toast.error("Failed to send follow-up", { + description: result.error || "An error occurred", + }); + await loadFeatures(); + } + } catch (error) { + console.error("[Board] Error sending follow-up:", error); + toast.error("Failed to send follow-up", { + description: error instanceof Error ? error.message : "An error occurred", + }); + await loadFeatures(); + } + }; + + // Handle commit-only for waiting_approval features (marks as verified and commits) + const handleCommitFeature = async (feature: Feature) => { + if (!currentProject) return; + + console.log("[Board] Committing feature:", { id: feature.id, description: feature.description }); + + try { + const api = getElectronAPI(); + if (!api?.autoMode?.commitFeature) { + console.error("Commit feature API not available"); + toast.error("Commit not available", { + description: "This feature is not available in the current version.", + }); + return; + } + + // Call the API to commit this feature + const result = await api.autoMode.commitFeature( + currentProject.path, + feature.id + ); + + if (result.success) { + console.log("[Board] Feature committed successfully"); + // Move to verified status + moveFeature(feature.id, "verified"); + toast.success("Feature committed", { + description: `Committed and verified: ${feature.description.slice(0, 50)}${feature.description.length > 50 ? "..." : ""}`, + }); + } else { + console.error("[Board] Failed to commit feature:", result.error); + toast.error("Failed to commit feature", { + description: result.error || "An error occurred", + }); + await loadFeatures(); + } + } catch (error) { + console.error("[Board] Error committing feature:", error); + toast.error("Failed to commit feature", { + description: error instanceof Error ? error.message : "An error occurred", + }); + await loadFeatures(); + } + }; + + // Move feature to waiting_approval (for skipTests features when agent completes) + const handleMoveToWaitingApproval = (feature: Feature) => { + console.log("[Board] Moving feature to waiting_approval:", { id: feature.id, description: feature.description }); + updateFeature(feature.id, { status: "waiting_approval" }); + toast.info("Feature ready for review", { + description: `Ready for approval: ${feature.description.slice(0, 50)}${feature.description.length > 50 ? "..." : ""}`, + }); + }; + const checkContextExists = async (featureId: string): Promise => { if (!currentProject) return false; @@ -1000,6 +1124,8 @@ export function BoardView() { onForceStop={() => handleForceStopFeature(feature)} onManualVerify={() => handleManualVerify(feature)} onMoveBackToInProgress={() => handleMoveBackToInProgress(feature)} + onFollowUp={() => handleOpenFollowUp(feature)} + onCommit={() => handleCommitFeature(feature)} hasContext={featuresWithContext.has(feature.id)} isCurrentAutoTask={runningAutoTasks.includes(feature.id)} shortcutKey={shortcutKey} @@ -1318,6 +1444,77 @@ export function BoardView() { + + {/* Follow-Up Prompt Dialog */} + { + if (!open) { + setShowFollowUpDialog(false); + setFollowUpFeature(null); + setFollowUpPrompt(""); + setFollowUpImagePaths([]); + } + }}> + { + if ((e.metaKey || e.ctrlKey) && e.key === "Enter" && followUpPrompt.trim()) { + e.preventDefault(); + handleSendFollowUp(); + } + }} + > + + Follow-Up Prompt + + Send additional instructions to continue working on this feature. + {followUpFeature && ( + + Feature: {followUpFeature.description.slice(0, 100)}{followUpFeature.description.length > 100 ? "..." : ""} + + )} + + +
+
+ + +
+

+ The agent will continue from where it left off, using the existing context. + You can attach screenshots to help explain the issue. +

+
+ + + + +
+
); } diff --git a/app/src/components/views/kanban-card.tsx b/app/src/components/views/kanban-card.tsx index da3c16ff..d6943206 100644 --- a/app/src/components/views/kanban-card.tsx +++ b/app/src/components/views/kanban-card.tsx @@ -34,6 +34,8 @@ import { StopCircle, FlaskConical, ArrowLeft, + MessageSquare, + GitCommit, } from "lucide-react"; import { CountUpTimer } from "@/components/ui/count-up-timer"; @@ -47,6 +49,8 @@ interface KanbanCardProps { onForceStop?: () => void; onManualVerify?: () => void; onMoveBackToInProgress?: () => void; + onFollowUp?: () => void; + onCommit?: () => void; hasContext?: boolean; isCurrentAutoTask?: boolean; shortcutKey?: string; @@ -62,6 +66,8 @@ export function KanbanCard({ onForceStop, onManualVerify, onMoveBackToInProgress, + onFollowUp, + onCommit, hasContext, isCurrentAutoTask, shortcutKey, @@ -364,6 +370,51 @@ export function KanbanCard({ )} + {!isCurrentAutoTask && feature.status === "waiting_approval" && ( + <> + {/* Follow-up prompt button */} + {onFollowUp && ( + + )} + {/* Commit and verify button */} + {onCommit && ( + + )} + + + )} {!isCurrentAutoTask && feature.status === "backlog" && ( <>