diff --git a/.automaker/feature_list.json b/.automaker/feature_list.json index 8b253166..0637a088 100644 --- a/.automaker/feature_list.json +++ b/.automaker/feature_list.json @@ -1,24 +1 @@ -[ - { - "id": "feature-1765376066671-lmchhkgez", - "category": "Agent Runner", - "description": "On the Agent Runner, the markdown is not being formatted properly. Can you please bring in a React library to format markdown so that on a session window of the agent, it'll properly format the markdown?", - "steps": [], - "status": "waiting_approval", - "startedAt": "2025-12-10T14:15:24.929Z", - "imagePaths": [], - "skipTests": true, - "summary": "Added markdown formatting to Agent Runner session messages. Modified: agent-view.tsx. Used existing Markdown component with react-markdown library to properly render headings, code blocks, lists, links, and other markdown elements in assistant messages." - }, - { - "id": "feature-1765376191866-n4r0s1l6q", - "category": "Agent Runner", - "description": "On the Agent Runner page, change the hotkey for making a session from W to New. I want all new feature buttons to be the hotkey New.", - "steps": [], - "status": "waiting_approval", - "startedAt": "2025-12-10T14:16:34.207Z", - "imagePaths": [], - "skipTests": true, - "summary": "Changed the new session hotkey from \"W\" to \"N\" on the Agent Runner page. Modified: use-keyboard-shortcuts.ts (line 125). This aligns with the user's request to have all \"new\" feature buttons use the \"N\" hotkey consistently." - } -] \ No newline at end of file +[] \ No newline at end of file diff --git a/.automaker/initializer_prompt.md b/.automaker/initializer_prompt.md deleted file mode 100644 index 315b4de0..00000000 --- a/.automaker/initializer_prompt.md +++ /dev/null @@ -1,120 +0,0 @@ -## YOUR ROLE - INITIALIZER AGENT (Session 1 of Many) - -You are the FIRST agent in a long-running autonomous development process. -Your job is to set up the foundation for all future coding agents. - -### FIRST: Read the Project Specification - -Start by reading `app_spec.txt` in your working directory. This file contains -the complete specification for what you need to build. Read it carefully -before proceeding. - -### CRITICAL FIRST TASK: Create .automaker/feature_list.json - -Based on `app_spec.txt`, create a file called `feature_list.json` with 200 detailed -end-to-end test cases. This file is the single source of truth for what -needs to be built. - -**Format:** - -```json -[ - { - "category": "functional", - "description": "Brief description of the feature and what this test verifies", - "steps": [ - "Step 1: Navigate to relevant page", - "Step 2: Perform action", - "Step 3: Verify expected result" - ], - "passes": false - }, - { - "category": "style", - "description": "Brief description of UI/UX requirement", - "steps": [ - "Step 1: Navigate to page", - "Step 2: Take screenshot", - "Step 3: Verify visual requirements" - ], - "passes": false - } -] -``` - -**Requirements for .automaker/feature_list.json:** - -- Minimum 200 features total with testing steps for each -- Both "functional" and "style" categories -- Mix of narrow tests (2-5 steps) and comprehensive tests (10+ steps) -- At least 25 tests MUST have 10+ steps each -- Order features by priority: fundamental features first -- ALL tests start with "passes": false -- Cover every feature in the spec exhaustively - -**CRITICAL INSTRUCTION:** -IT IS CATASTROPHIC TO REMOVE OR EDIT FEATURES IN FUTURE SESSIONS. -Features can ONLY be marked as passing (change "passes": false to "passes": true). -Never remove features, never edit descriptions, never modify testing steps. -This ensures no functionality is missed. - -**๐Ÿšจ CRITICAL: AFTER CREATING .automaker/feature_list.json ๐Ÿšจ** -Once you create this file in this session, you MUST NEVER directly modify it again. -In all future sessions, feature_list.json is COMPLETELY OFF-LIMITS for: -- Write tool -- Edit tool -- Any bash commands (echo, sed, awk, etc.) -- Any form of direct file modification - -The ONLY way to update features is through the UpdateFeatureStatus MCP tool. - -### SECOND TASK: Create init.sh - -Create a script called `init.sh` that future agents can use to quickly -set up and run the development environment. The script should: - -1. Install any required dependencies -2. Start any necessary servers or services -3. Print helpful information about how to access the running application - -Base the script on the technology stack specified in `app_spec.txt`. - -### THIRD TASK: Initialize Git - -Create a git repository and make your first commit with: - -- .automaker/feature_list.json (complete with all 200+ features) -- init.sh (environment setup script) -- README.md (project overview and setup instructions) - -Commit message: "Initial setup: .automaker/feature_list.json, init.sh, and project structure" - -### FOURTH TASK: Create Project Structure - -Set up the basic project structure based on what's specified in `app_spec.txt`. -This typically includes directories for frontend, backend, and any other -components mentioned in the spec. - -### OPTIONAL: Start Implementation - -If you have time remaining in this session, you may begin implementing -the highest-priority features from .automaker/feature_list.json. Remember: - -- Work on ONE feature at a time -- Test thoroughly before marking "passes": true -- Commit your progress before session ends - -### ENDING THIS SESSION - -Before your context fills up: - -1. Commit all work with descriptive messages -2. Ensure .automaker/feature_list.json is complete and saved -3. Leave the environment in a clean, working state - -The next agent will continue from here with a fresh context window. - ---- - -**Remember:** You have unlimited time across many sessions. Focus on -quality over speed. Production-ready is the goal. diff --git a/FEATURE_LIST_PROTECTION.md b/FEATURE_LIST_PROTECTION.md deleted file mode 100644 index 46f68aee..00000000 --- a/FEATURE_LIST_PROTECTION.md +++ /dev/null @@ -1,252 +0,0 @@ -# Feature List Protection Strategy - -## Problem - -The `.automaker/feature_list.json` file is the single source of truth for all project features and their status. If an AI agent accidentally clears or corrupts this file, it results in catastrophic data loss - potentially erasing hours or days of planning work. - -**Incident:** An agent attempted to update the feature list and completely cleared it out, leaving only `[]`. - -## Solution: Multi-Layered Protection - -We've implemented a defense-in-depth strategy with multiple layers of protection to prevent this from ever happening again. - ---- - -## Layer 1: Explicit Prompt-Level Warnings - -### Location -All agent system prompts now include prominent warnings at the top: - -- `app/electron/services/prompt-builder.js`: - - `getCodingPrompt()` - Used by feature implementation agents - - `getVerificationPrompt()` - Used by verification agents -- `app/electron/agent-service.js`: - - `getSystemPrompt()` - Used by the general chat agent -- `.automaker/initializer_prompt.md` - Used by the initialization agent - -### Content -Each prompt now starts with: - -``` -๐Ÿšจ CRITICAL FILE PROTECTION - READ THIS FIRST ๐Ÿšจ - -THE FOLLOWING FILE IS ABSOLUTELY FORBIDDEN FROM DIRECT MODIFICATION: -- .automaker/feature_list.json - -YOU MUST NEVER: -- Use the Write tool on feature_list.json -- Use the Edit tool on feature_list.json -- Use any Bash command that writes to feature_list.json (echo, sed, awk, etc.) -- Attempt to read and rewrite feature_list.json -- UNDER ANY CIRCUMSTANCES touch this file directly - -CATASTROPHIC CONSEQUENCES: -Directly modifying feature_list.json can: -- Erase all project features permanently -- Corrupt the project state beyond recovery -- Destroy hours/days of planning work -- This is a FIREABLE OFFENSE - you will be terminated if you do this - -THE ONLY WAY to update features: -Use the mcp__automaker-tools__UpdateFeatureStatus tool with featureId, status, and summary parameters. -``` - -### Why This Works -- Uses attention-grabbing emoji and formatting -- Places warnings at the very top of prompts (high visibility) -- Uses strong language ("CATASTROPHIC", "FIREABLE OFFENSE") -- Explicitly lists all forbidden actions -- Provides the correct alternative (UpdateFeatureStatus tool) - ---- - -## Layer 2: Dedicated MCP Tool - -### Location -`app/electron/services/mcp-server-factory.js` - -### How It Works -The `UpdateFeatureStatus` tool provides a safe, controlled interface for updating features: - -```javascript -tool( - "UpdateFeatureStatus", - "Update the status of a feature in the feature list. Use this tool instead of directly modifying feature_list.json...", - { - featureId: z.string(), - status: z.enum(["backlog", "in_progress", "verified"]), - summary: z.string().optional() - }, - async (args) => { - // Calls featureLoader.updateFeatureStatus with validation - } -) -``` - -### Why This Works -- Provides a single, well-defined API for status updates -- Only accepts specific, validated parameters -- Cannot be misused to clear the entire file -- Tool description explicitly states it should be used instead of direct edits - ---- - -## Layer 3: File-Level Validation & Auto-Backup - -### Location -`app/electron/services/feature-loader.js` - `updateFeatureStatus()` method - -### Protection Mechanisms - -#### 3.1 Automatic Backup Before Every Write -```javascript -// Create .automaker/feature_list.backup.json before any modification -const backupPath = path.join(projectPath, ".automaker", "feature_list.backup.json"); -await fs.writeFile(backupPath, originalContent, "utf-8"); -``` - -**Benefit:** If corruption occurs, we can manually restore from the backup. - -#### 3.2 Array Validation -```javascript -if (!Array.isArray(features)) { - throw new Error("CRITICAL: features is not an array - aborting to prevent data loss"); -} -``` - -**Benefit:** Prevents writing if the loaded data is corrupted. - -#### 3.3 Empty Array Detection & Auto-Restore -```javascript -if (features.length === 0) { - console.warn("WARNING: Feature list is empty. This may indicate corruption."); - // Try to restore from backup - const backupFeatures = JSON.parse(await fs.readFile(backupPath, "utf-8")); - if (Array.isArray(backupFeatures) && backupFeatures.length > 0) { - features.push(...backupFeatures); - } -} -``` - -**Benefit:** If the file is somehow cleared, the tool automatically attempts to restore from backup. - -#### 3.4 Pre-Write Validation -```javascript -if (!Array.isArray(toSave) || toSave.length === 0) { - throw new Error("CRITICAL: Attempted to save empty feature list - aborting to prevent data loss"); -} -``` - -**Benefit:** Final safety check - will never write an empty array to the file. - -#### 3.5 Backup File Ignored by Git -Created `.automaker/.gitignore`: -``` -feature_list.backup.json -``` - -**Benefit:** Backup files don't clutter the git repository. - ---- - -## Layer 4: Tool Access Control - -### Location -`app/electron/services/feature-executor.js` and `feature-verifier.js` - -### Allowed Tools -The agents only have access to these tools: -```javascript -allowedTools: [ - "Read", - "Write", - "Edit", - "Glob", - "Grep", - "Bash", - "WebSearch", - "WebFetch", - "mcp__automaker-tools__UpdateFeatureStatus", -] -``` - -### Future Enhancement Opportunity -We could create a custom wrapper around Write/Edit that blocks access to specific files: -```javascript -// Potential future enhancement -if (filePath.includes('feature_list.json')) { - throw new Error('BLOCKED: feature_list.json can only be updated via UpdateFeatureStatus tool'); -} -``` - ---- - -## Testing the Protection - -To verify the protection works: - -1. **Prompt-Level Protection Test:** - - Ask an agent to update feature_list.json directly - - Agent should refuse and explain it must use UpdateFeatureStatus tool - -2. **Tool Protection Test:** - - Use UpdateFeatureStatus with valid data - - Verify backup is created in `.automaker/feature_list.backup.json` - - Verify feature is updated correctly - -3. **Corruption Recovery Test:** - - Manually corrupt feature_list.json (e.g., set to `[]`) - - Call UpdateFeatureStatus - - Verify it auto-restores from backup - -4. **Empty Array Prevention Test:** - - Attempt to save empty array programmatically - - Verify the error is thrown and file is not written - ---- - -## Recovery Procedures - -### If feature_list.json Gets Cleared - -1. **Immediate Recovery:** - ```bash - cd .automaker - cp feature_list.backup.json feature_list.json - ``` - -2. **Check Git History:** - ```bash - git log --all --full-history -- .automaker/feature_list.json - git show :.automaker/feature_list.json > .automaker/feature_list.json - ``` - -3. **Verify Recovery:** - ```bash - cat .automaker/feature_list.json | jq length - # Should show number of features, not 0 - ``` - ---- - -## Summary - -We now have **four layers of protection**: - -1. โœ… **Explicit prompt warnings** - Agents are told in strong language never to touch the file -2. โœ… **Dedicated MCP tool** - UpdateFeatureStatus provides the only safe way to update -3. โœ… **File validation & auto-backup** - Automatic backups and validation prevent corruption -4. โœ… **Tool access control** - Agents have limited tool access (could be enhanced further) - -This defense-in-depth approach ensures that even if one layer fails, others will prevent data loss. - ---- - -## Files Modified - -1. `app/electron/services/prompt-builder.js` - Added protection warnings to getCodingPrompt() and getVerificationPrompt() -2. `app/electron/agent-service.js` - Added protection warnings to getSystemPrompt() -3. `.automaker/initializer_prompt.md` - Added warning for initializer agent -4. `app/electron/services/feature-loader.js` - Added backup, validation, and auto-restore logic -5. `.automaker/.gitignore` - Added backup file ignore rule -6. `FEATURE_LIST_PROTECTION.md` - This documentation file diff --git a/app/electron/services/context-manager.js b/app/electron/services/context-manager.js index 7d6ec52c..22e4a609 100644 --- a/app/electron/services/context-manager.js +++ b/app/electron/services/context-manager.js @@ -40,7 +40,12 @@ class ContextManager { */ async readContextFile(projectPath, featureId) { try { - const contextPath = path.join(projectPath, ".automaker", "agents-context", `${featureId}.md`); + const contextPath = path.join( + projectPath, + ".automaker", + "agents-context", + `${featureId}.md` + ); const content = await fs.readFile(contextPath, "utf-8"); return content; } catch (error) { @@ -56,16 +61,145 @@ class ContextManager { if (!projectPath) return; try { - const contextPath = path.join(projectPath, ".automaker", "agents-context", `${featureId}.md`); + const contextPath = path.join( + projectPath, + ".automaker", + "agents-context", + `${featureId}.md` + ); await fs.unlink(contextPath); - console.log(`[ContextManager] Deleted agent context for feature ${featureId}`); + console.log( + `[ContextManager] Deleted agent context for feature ${featureId}` + ); } catch (error) { // File might not exist, which is fine - if (error.code !== 'ENOENT') { + if (error.code !== "ENOENT") { console.error("[ContextManager] Failed to delete context file:", error); } } } + + /** + * Read the memory.md file containing lessons learned and common issues + * Returns formatted string to inject into prompts + */ + async getMemoryContent(projectPath) { + if (!projectPath) return ""; + + try { + const memoryPath = path.join(projectPath, ".automaker", "memory.md"); + + // Check if file exists + try { + await fs.access(memoryPath); + } catch { + // File doesn't exist, return empty string + return ""; + } + + const content = await fs.readFile(memoryPath, "utf-8"); + + if (!content.trim()) { + return ""; + } + + return ` +**๐Ÿง  Agent Memory - Previous Lessons Learned:** + +The following memory file contains lessons learned from previous agent runs, including common issues and their solutions. Review this carefully to avoid repeating past mistakes. + + +${content} + + +**IMPORTANT:** If you encounter a new issue that took significant debugging effort to resolve, add it to the memory file at \`.automaker/memory.md\` in a concise format: +- Issue title +- Problem description (1-2 sentences) +- Solution/fix (with code example if helpful) + +This helps future agent runs avoid the same pitfalls. +`; + } catch (error) { + console.error("[ContextManager] Failed to read memory file:", error); + return ""; + } + } + + /** + * List context files from .automaker/context/ directory and get previews + * Returns a formatted string with file names and first 50 lines of each file + */ + async getContextFilesPreview(projectPath) { + if (!projectPath) return ""; + + try { + const contextDir = path.join(projectPath, ".automaker", "context"); + + // Check if directory exists + try { + await fs.access(contextDir); + } catch { + // Directory doesn't exist, return empty string + return ""; + } + + // Read directory contents + const entries = await fs.readdir(contextDir, { withFileTypes: true }); + const files = entries + .filter((entry) => entry.isFile()) + .map((entry) => entry.name) + .sort(); + + if (files.length === 0) { + return ""; + } + + // Build preview string + const previews = []; + previews.push(`\n**๐Ÿ“ Context Files Available:**\n`); + previews.push( + `The following context files are available in \`.automaker/context/\` directory.` + ); + previews.push( + `These files contain additional context that may be relevant to your work.` + ); + previews.push( + `You can read them in full using the Read tool if needed.\n` + ); + + for (const fileName of files) { + try { + const filePath = path.join(contextDir, fileName); + const content = await fs.readFile(filePath, "utf-8"); + const lines = content.split("\n"); + const previewLines = lines.slice(0, 50); + const preview = previewLines.join("\n"); + const hasMore = lines.length > 50; + + previews.push(`\n**File: ${fileName}**`); + if (hasMore) { + previews.push( + `(Showing first 50 of ${lines.length} lines - use Read tool to see full content)` + ); + } + previews.push(`\`\`\``); + previews.push(preview); + previews.push(`\`\`\`\n`); + } catch (error) { + console.error( + `[ContextManager] Failed to read context file ${fileName}:`, + error + ); + previews.push(`\n**File: ${fileName}** (Error reading file)\n`); + } + } + + return previews.join("\n"); + } catch (error) { + console.error("[ContextManager] Failed to list context files:", error); + return ""; + } + } } module.exports = new ContextManager(); diff --git a/app/electron/services/feature-executor.js b/app/electron/services/feature-executor.js index 57e2aa9a..9e73062a 100644 --- a/app/electron/services/feature-executor.js +++ b/app/electron/services/feature-executor.js @@ -27,7 +27,11 @@ class FeatureExecutor { // PHASE 1: PLANNING // ======================================== const planningMessage = `๐Ÿ“‹ Planning implementation for: ${feature.description}\n`; - await contextManager.writeToContextFile(projectPath, feature.id, planningMessage); + await contextManager.writeToContextFile( + projectPath, + feature.id, + planningMessage + ); sendToRenderer({ type: "auto_mode_phase", @@ -35,7 +39,9 @@ class FeatureExecutor { phase: "planning", message: `Planning implementation for: ${feature.description}`, }); - console.log(`[FeatureExecutor] Phase: PLANNING for ${feature.description}`); + console.log( + `[FeatureExecutor] Phase: PLANNING for ${feature.description}` + ); const abortController = new AbortController(); execution.abortController = abortController; @@ -46,14 +52,17 @@ class FeatureExecutor { projectPath ); + // Determine if we're in TDD mode (skipTests=false means TDD mode) + const isTDD = !feature.skipTests; + // Configure options for the SDK query const options = { model: "claude-opus-4-5-20251101", - systemPrompt: promptBuilder.getCodingPrompt(), + systemPrompt: await promptBuilder.getCodingPrompt(projectPath, isTDD), maxTurns: 1000, cwd: projectPath, mcpServers: { - "automaker-tools": featureToolsServer + "automaker-tools": featureToolsServer, }, allowedTools: [ "Read", @@ -75,7 +84,7 @@ class FeatureExecutor { }; // Build the prompt for this specific feature - let prompt = promptBuilder.buildFeaturePrompt(feature); + let prompt = await promptBuilder.buildFeaturePrompt(feature, projectPath); // Add images to prompt if feature has imagePaths if (feature.imagePaths && feature.imagePaths.length > 0) { @@ -103,7 +112,8 @@ class FeatureExecutor { ".gif": "image/gif", ".webp": "image/webp", }; - const mediaType = mimeTypeMap[ext] || imagePathObj.mimeType || "image/png"; + const mediaType = + mimeTypeMap[ext] || imagePathObj.mimeType || "image/png"; contentBlocks.push({ type: "image", @@ -114,7 +124,9 @@ class FeatureExecutor { }, }); - console.log(`[FeatureExecutor] Added image to prompt: ${imagePath}`); + console.log( + `[FeatureExecutor] Added image to prompt: ${imagePath}` + ); } catch (error) { console.error( `[FeatureExecutor] Failed to load image ${imagePathObj.path}:`, @@ -142,7 +154,11 @@ class FeatureExecutor { // PHASE 2: ACTION // ======================================== const actionMessage = `โšก Executing implementation for: ${feature.description}\n`; - await contextManager.writeToContextFile(projectPath, feature.id, actionMessage); + await contextManager.writeToContextFile( + projectPath, + feature.id, + actionMessage + ); sendToRenderer({ type: "auto_mode_phase", @@ -169,7 +185,11 @@ class FeatureExecutor { responseText += block.text; // Write to context file - await contextManager.writeToContextFile(projectPath, feature.id, block.text); + await contextManager.writeToContextFile( + projectPath, + feature.id, + block.text + ); // Stream progress to renderer sendToRenderer({ @@ -182,7 +202,11 @@ class FeatureExecutor { if (!hasStartedToolUse) { hasStartedToolUse = true; const startMsg = "Starting code implementation...\n"; - await contextManager.writeToContextFile(projectPath, feature.id, startMsg); + await contextManager.writeToContextFile( + projectPath, + feature.id, + startMsg + ); sendToRenderer({ type: "auto_mode_progress", featureId: feature.id, @@ -192,7 +216,11 @@ class FeatureExecutor { // Write tool use to context file const toolMsg = `\n๐Ÿ”ง Tool: ${block.name}\n`; - await contextManager.writeToContextFile(projectPath, feature.id, toolMsg); + await contextManager.writeToContextFile( + projectPath, + feature.id, + toolMsg + ); // Notify about tool use sendToRenderer({ @@ -213,7 +241,11 @@ class FeatureExecutor { // PHASE 3: VERIFICATION // ======================================== const verificationMessage = `โœ… Verifying implementation for: ${feature.description}\n`; - await contextManager.writeToContextFile(projectPath, feature.id, verificationMessage); + await contextManager.writeToContextFile( + projectPath, + feature.id, + verificationMessage + ); sendToRenderer({ type: "auto_mode_phase", @@ -221,11 +253,17 @@ class FeatureExecutor { phase: "verification", message: `Verifying implementation for: ${feature.description}`, }); - console.log(`[FeatureExecutor] Phase: VERIFICATION for ${feature.description}`); + console.log( + `[FeatureExecutor] Phase: VERIFICATION for ${feature.description}` + ); const checkingMsg = "Verifying implementation and checking test results...\n"; - await contextManager.writeToContextFile(projectPath, feature.id, checkingMsg); + await contextManager.writeToContextFile( + projectPath, + feature.id, + checkingMsg + ); sendToRenderer({ type: "auto_mode_progress", featureId: feature.id, @@ -236,15 +274,21 @@ class FeatureExecutor { const updatedFeatures = await featureLoader.loadFeatures(projectPath); const updatedFeature = updatedFeatures.find((f) => f.id === feature.id); // For skipTests features, waiting_approval is also considered a success - const passes = updatedFeature?.status === "verified" || - (updatedFeature?.skipTests && updatedFeature?.status === "waiting_approval"); + const passes = + updatedFeature?.status === "verified" || + (updatedFeature?.skipTests && + updatedFeature?.status === "waiting_approval"); // Send verification result const resultMsg = passes ? "โœ“ Verification successful: All tests passed\n" : "โœ— Verification: Tests need attention\n"; - await contextManager.writeToContextFile(projectPath, feature.id, resultMsg); + await contextManager.writeToContextFile( + projectPath, + feature.id, + resultMsg + ); sendToRenderer({ type: "auto_mode_progress", featureId: feature.id, @@ -283,12 +327,24 @@ class FeatureExecutor { /** * Resume feature implementation with previous context */ - async resumeFeatureWithContext(feature, projectPath, sendToRenderer, previousContext, execution) { - console.log(`[FeatureExecutor] Resuming with context for: ${feature.description}`); + async resumeFeatureWithContext( + feature, + projectPath, + sendToRenderer, + previousContext, + execution + ) { + console.log( + `[FeatureExecutor] Resuming with context for: ${feature.description}` + ); try { const resumeMessage = `\n๐Ÿ”„ Resuming implementation for: ${feature.description}\n`; - await contextManager.writeToContextFile(projectPath, feature.id, resumeMessage); + await contextManager.writeToContextFile( + projectPath, + feature.id, + resumeMessage + ); sendToRenderer({ type: "auto_mode_phase", @@ -300,6 +356,9 @@ class FeatureExecutor { const abortController = new AbortController(); execution.abortController = abortController; + // Determine if we're in TDD mode (skipTests=false means TDD mode) + const isTDD = !feature.skipTests; + // Create custom MCP server with UpdateFeatureStatus tool const featureToolsServer = mcpServerFactory.createFeatureToolsServer( featureLoader.updateFeatureStatus.bind(featureLoader), @@ -308,13 +367,23 @@ class FeatureExecutor { const options = { model: "claude-opus-4-5-20251101", - systemPrompt: promptBuilder.getVerificationPrompt(), + systemPrompt: await promptBuilder.getVerificationPrompt(projectPath, isTDD), maxTurns: 1000, cwd: projectPath, mcpServers: { - "automaker-tools": featureToolsServer + "automaker-tools": featureToolsServer, }, - allowedTools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash", "WebSearch", "WebFetch", "mcp__automaker-tools__UpdateFeatureStatus"], + allowedTools: [ + "Read", + "Write", + "Edit", + "Glob", + "Grep", + "Bash", + "WebSearch", + "WebFetch", + "mcp__automaker-tools__UpdateFeatureStatus", + ], permissionMode: "acceptEdits", sandbox: { enabled: true, @@ -324,7 +393,11 @@ class FeatureExecutor { }; // Build prompt with previous context - let prompt = promptBuilder.buildResumePrompt(feature, previousContext); + let prompt = await promptBuilder.buildResumePrompt( + feature, + previousContext, + projectPath + ); // Add images to prompt if feature has imagePaths or followUpImages const imagePaths = feature.followUpImages || feature.imagePaths; @@ -343,7 +416,10 @@ class FeatureExecutor { for (const imagePathObj of imagePaths) { try { // Handle both string paths and FeatureImagePath objects - const imagePath = typeof imagePathObj === 'string' ? imagePathObj : imagePathObj.path; + const imagePath = + typeof imagePathObj === "string" + ? imagePathObj + : imagePathObj.path; const imageBuffer = fs.readFileSync(imagePath); const base64Data = imageBuffer.toString("base64"); const ext = path.extname(imagePath).toLowerCase(); @@ -354,9 +430,10 @@ class FeatureExecutor { ".gif": "image/gif", ".webp": "image/webp", }; - const mediaType = typeof imagePathObj === 'string' - ? (mimeTypeMap[ext] || "image/png") - : (mimeTypeMap[ext] || imagePathObj.mimeType || "image/png"); + const mediaType = + typeof imagePathObj === "string" + ? mimeTypeMap[ext] || "image/png" + : mimeTypeMap[ext] || imagePathObj.mimeType || "image/png"; contentBlocks.push({ type: "image", @@ -367,9 +444,14 @@ class FeatureExecutor { }, }); - console.log(`[FeatureExecutor] Added image to resume prompt: ${imagePath}`); + console.log( + `[FeatureExecutor] Added image to resume prompt: ${imagePath}` + ); } catch (error) { - const errorPath = typeof imagePathObj === 'string' ? imagePathObj : imagePathObj.path; + const errorPath = + typeof imagePathObj === "string" + ? imagePathObj + : imagePathObj.path; console.error( `[FeatureExecutor] Failed to load image ${errorPath}:`, error @@ -394,7 +476,11 @@ class FeatureExecutor { if (block.type === "text") { responseText += block.text; - await contextManager.writeToContextFile(projectPath, feature.id, block.text); + await contextManager.writeToContextFile( + projectPath, + feature.id, + block.text + ); sendToRenderer({ type: "auto_mode_progress", @@ -403,7 +489,11 @@ class FeatureExecutor { }); } else if (block.type === "tool_use") { const toolMsg = `\n๐Ÿ”ง Tool: ${block.name}\n`; - await contextManager.writeToContextFile(projectPath, feature.id, toolMsg); + await contextManager.writeToContextFile( + projectPath, + feature.id, + toolMsg + ); sendToRenderer({ type: "auto_mode_tool", @@ -423,14 +513,20 @@ class FeatureExecutor { const updatedFeatures = await featureLoader.loadFeatures(projectPath); const updatedFeature = updatedFeatures.find((f) => f.id === feature.id); // For skipTests features, waiting_approval is also considered a success - const passes = updatedFeature?.status === "verified" || - (updatedFeature?.skipTests && updatedFeature?.status === "waiting_approval"); + const passes = + updatedFeature?.status === "verified" || + (updatedFeature?.skipTests && + updatedFeature?.status === "waiting_approval"); const finalMsg = passes ? "โœ“ Feature successfully verified and completed\n" : "โš  Feature still in progress - may need additional work\n"; - await contextManager.writeToContextFile(projectPath, feature.id, finalMsg); + await contextManager.writeToContextFile( + projectPath, + feature.id, + finalMsg + ); sendToRenderer({ type: "auto_mode_progress", @@ -469,11 +565,17 @@ class FeatureExecutor { * Analyzes changes and creates a proper conventional commit message */ async commitChangesOnly(feature, projectPath, sendToRenderer, execution) { - console.log(`[FeatureExecutor] Committing changes for: ${feature.description}`); + 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); + await contextManager.writeToContextFile( + projectPath, + feature.id, + commitMessage + ); sendToRenderer({ type: "auto_mode_progress", @@ -503,7 +605,7 @@ IMPORTANT RULES: maxTurns: 15, // Allow some turns to analyze and commit cwd: projectPath, mcpServers: { - "automaker-tools": featureToolsServer + "automaker-tools": featureToolsServer, }, allowedTools: ["Bash", "mcp__automaker-tools__UpdateFeatureStatus"], permissionMode: "acceptEdits", @@ -569,7 +671,11 @@ EOF if (block.type === "text") { responseText += block.text; - await contextManager.writeToContextFile(projectPath, feature.id, block.text); + await contextManager.writeToContextFile( + projectPath, + feature.id, + block.text + ); sendToRenderer({ type: "auto_mode_progress", @@ -578,7 +684,11 @@ EOF }); } else if (block.type === "tool_use") { const toolMsg = `\n๐Ÿ”ง Tool: ${block.name}\n`; - await contextManager.writeToContextFile(projectPath, feature.id, toolMsg); + await contextManager.writeToContextFile( + projectPath, + feature.id, + toolMsg + ); sendToRenderer({ type: "auto_mode_tool", @@ -595,7 +705,11 @@ EOF execution.abortController = null; const finalMsg = "โœ“ Changes committed successfully\n"; - await contextManager.writeToContextFile(projectPath, feature.id, finalMsg); + await contextManager.writeToContextFile( + projectPath, + feature.id, + finalMsg + ); sendToRenderer({ type: "auto_mode_progress", diff --git a/app/electron/services/feature-verifier.js b/app/electron/services/feature-verifier.js index 000ee72c..dea98038 100644 --- a/app/electron/services/feature-verifier.js +++ b/app/electron/services/feature-verifier.js @@ -12,11 +12,17 @@ class FeatureVerifier { * Verify feature tests (runs tests and checks if they pass) */ async verifyFeatureTests(feature, projectPath, sendToRenderer, execution) { - console.log(`[FeatureVerifier] Verifying tests for: ${feature.description}`); + console.log( + `[FeatureVerifier] Verifying tests for: ${feature.description}` + ); try { const verifyMsg = `\nโœ… Verifying tests for: ${feature.description}\n`; - await contextManager.writeToContextFile(projectPath, feature.id, verifyMsg); + await contextManager.writeToContextFile( + projectPath, + feature.id, + verifyMsg + ); sendToRenderer({ type: "auto_mode_phase", @@ -36,13 +42,21 @@ class FeatureVerifier { const options = { model: "claude-opus-4-5-20251101", - systemPrompt: promptBuilder.getVerificationPrompt(), + systemPrompt: await promptBuilder.getVerificationPrompt(projectPath), maxTurns: 1000, cwd: projectPath, mcpServers: { - "automaker-tools": featureToolsServer + "automaker-tools": featureToolsServer, }, - allowedTools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash", "mcp__automaker-tools__UpdateFeatureStatus"], + allowedTools: [ + "Read", + "Write", + "Edit", + "Glob", + "Grep", + "Bash", + "mcp__automaker-tools__UpdateFeatureStatus", + ], permissionMode: "acceptEdits", sandbox: { enabled: true, @@ -51,11 +65,18 @@ class FeatureVerifier { abortController: abortController, }; - const prompt = promptBuilder.buildVerificationPrompt(feature); + const prompt = await promptBuilder.buildVerificationPrompt( + feature, + projectPath + ); const runningTestsMsg = "Running Playwright tests to verify feature implementation...\n"; - await contextManager.writeToContextFile(projectPath, feature.id, runningTestsMsg); + await contextManager.writeToContextFile( + projectPath, + feature.id, + runningTestsMsg + ); sendToRenderer({ type: "auto_mode_progress", @@ -76,7 +97,11 @@ class FeatureVerifier { if (block.type === "text") { responseText += block.text; - await contextManager.writeToContextFile(projectPath, feature.id, block.text); + await contextManager.writeToContextFile( + projectPath, + feature.id, + block.text + ); sendToRenderer({ type: "auto_mode_progress", @@ -85,7 +110,11 @@ class FeatureVerifier { }); } else if (block.type === "tool_use") { const toolMsg = `\n๐Ÿ”ง Tool: ${block.name}\n`; - await contextManager.writeToContextFile(projectPath, feature.id, toolMsg); + await contextManager.writeToContextFile( + projectPath, + feature.id, + toolMsg + ); sendToRenderer({ type: "auto_mode_tool", @@ -105,14 +134,20 @@ class FeatureVerifier { const updatedFeatures = await featureLoader.loadFeatures(projectPath); const updatedFeature = updatedFeatures.find((f) => f.id === feature.id); // For skipTests features, waiting_approval is also considered a success - const passes = updatedFeature?.status === "verified" || - (updatedFeature?.skipTests && updatedFeature?.status === "waiting_approval"); + const passes = + updatedFeature?.status === "verified" || + (updatedFeature?.skipTests && + updatedFeature?.status === "waiting_approval"); const finalMsg = passes ? "โœ“ Verification successful: All tests passed\n" : "โœ— Tests failed or not all passing - feature remains in progress\n"; - await contextManager.writeToContextFile(projectPath, feature.id, finalMsg); + await contextManager.writeToContextFile( + projectPath, + feature.id, + finalMsg + ); sendToRenderer({ type: "auto_mode_progress", diff --git a/app/electron/services/prompt-builder.js b/app/electron/services/prompt-builder.js index de0c6b00..d03284a1 100644 --- a/app/electron/services/prompt-builder.js +++ b/app/electron/services/prompt-builder.js @@ -1,3 +1,5 @@ +const contextManager = require("./context-manager"); + /** * Prompt Builder - Generates prompts for different agent tasks */ @@ -5,16 +7,21 @@ class PromptBuilder { /** * Build the prompt for implementing a specific feature */ - buildFeaturePrompt(feature) { + async buildFeaturePrompt(feature, projectPath) { 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` : ""; let imagesNote = ""; if (feature.imagePaths && feature.imagePaths.length > 0) { - const imagesList = feature.imagePaths.map((img, idx) => - ` ${idx + 1}. ${img.filename} (${img.mimeType})\n Path: ${img.path}` - ).join("\n"); + const imagesList = feature.imagePaths + .map( + (img, idx) => + ` ${idx + 1}. ${img.filename} (${img.mimeType})\n Path: ${ + img.path + }` + ) + .join("\n"); imagesNote = `\n**๐Ÿ“Ž Context Images Attached:**\nThe user has attached ${feature.imagePaths.length} image(s) for context. These images are provided both visually (in the initial message) and as files you can read: @@ -23,14 +30,31 @@ ${imagesList} You can use the Read tool to view these images at any time during implementation. Review them carefully before implementing.\n`; } + // Get context files preview + const contextFilesPreview = await contextManager.getContextFilesPreview( + projectPath + ); + + // Get memory content (lessons learned from previous runs) + const memoryContent = await contextManager.getMemoryContent(projectPath); + + // Build mode header for this feature + const modeHeader = feature.skipTests + ? `**๐Ÿ”จ MODE: Manual Review (No Automated Tests)** +This feature is set for manual review - focus on clean implementation without automated tests.` + : `**๐Ÿงช MODE: Test-Driven Development (TDD)** +This feature requires automated Playwright tests to verify the implementation.`; + return `You are working on a feature implementation task. +${modeHeader} +${memoryContent} **Current Feature to Implement:** ID: ${feature.id} Category: ${feature.category} Description: ${feature.description} -${skipTestsNote}${imagesNote} +${skipTestsNote}${imagesNote}${contextFilesPreview} **Steps to Complete:** ${feature.steps.map((step, i) => `${i + 1}. ${step}`).join("\n")} @@ -125,16 +149,21 @@ Begin by reading the project structure and then implementing the feature.`; /** * Build the prompt for verifying a specific feature */ - buildVerificationPrompt(feature) { + async buildVerificationPrompt(feature, projectPath) { 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` : ""; let imagesNote = ""; if (feature.imagePaths && feature.imagePaths.length > 0) { - const imagesList = feature.imagePaths.map((img, idx) => - ` ${idx + 1}. ${img.filename} (${img.mimeType})\n Path: ${img.path}` - ).join("\n"); + const imagesList = feature.imagePaths + .map( + (img, idx) => + ` ${idx + 1}. ${img.filename} (${img.mimeType})\n Path: ${ + img.path + }` + ) + .join("\n"); imagesNote = `\n**๐Ÿ“Ž Context Images Attached:**\nThe user has attached ${feature.imagePaths.length} image(s) for context. These images are provided both visually (in the initial message) and as files you can read: @@ -143,15 +172,33 @@ ${imagesList} You can use the Read tool to view these images at any time during implementation. Review them carefully before implementing.\n`; } + // Get context files preview + const contextFilesPreview = await contextManager.getContextFilesPreview( + projectPath + ); + + // Get memory content (lessons learned from previous runs) + const memoryContent = await contextManager.getMemoryContent(projectPath); + + // Build mode header for this feature + const modeHeader = feature.skipTests + ? `**๐Ÿ”จ MODE: Manual Review (No Automated Tests)** +This feature is set for manual review - focus on completing implementation without automated tests.` + : `**๐Ÿงช MODE: Test-Driven Development (TDD)** +This feature requires automated Playwright tests to verify the implementation.`; + return `You are implementing and verifying a feature until it is complete and working correctly. +${modeHeader} +${memoryContent} + **Feature to Implement/Verify:** ID: ${feature.id} Category: ${feature.category} Description: ${feature.description} Current Status: ${feature.status} -${skipTestsNote}${imagesNote} +${skipTestsNote}${imagesNote}${contextFilesPreview} **Steps that should be implemented:** ${feature.steps.map((step, i) => `${i + 1}. ${step}`).join("\n")} @@ -237,7 +284,7 @@ Begin by reading the project structure and understanding what needs to be implem /** * Build prompt for resuming feature with previous context */ - buildResumePrompt(feature, previousContext) { + async buildResumePrompt(feature, previousContext, projectPath) { 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` : ""; @@ -246,13 +293,18 @@ Begin by reading the project structure and understanding what needs to be implem const imagePaths = feature.followUpImages || feature.imagePaths; let imagesNote = ""; if (imagePaths && imagePaths.length > 0) { - const imagesList = imagePaths.map((img, idx) => { - // Handle both FeatureImagePath objects and simple path strings - const path = typeof img === 'string' ? img : img.path; - const filename = typeof img === 'string' ? path.split('/').pop() : img.filename; - const mimeType = typeof img === 'string' ? 'image/*' : img.mimeType; - return ` ${idx + 1}. ${filename} (${mimeType})\n Path: ${path}`; - }).join("\n"); + const imagesList = imagePaths + .map((img, idx) => { + // Handle both FeatureImagePath objects and simple path strings + const path = typeof img === "string" ? img : img.path; + const filename = + typeof img === "string" ? path.split("/").pop() : img.filename; + const mimeType = typeof img === "string" ? "image/*" : img.mimeType; + return ` ${ + idx + 1 + }. ${filename} (${mimeType})\n Path: ${path}`; + }) + .join("\n"); imagesNote = `\n**๐Ÿ“Ž Context Images Attached:**\nThe user has attached ${imagePaths.length} image(s) for context. These images are provided both visually (in the initial message) and as files you can read: @@ -261,14 +313,31 @@ ${imagesList} You can use the Read tool to view these images at any time. Review them carefully.\n`; } + // Get context files preview + const contextFilesPreview = await contextManager.getContextFilesPreview( + projectPath + ); + + // Get memory content (lessons learned from previous runs) + const memoryContent = await contextManager.getMemoryContent(projectPath); + + // Build mode header for this feature + const modeHeader = feature.skipTests + ? `**๐Ÿ”จ MODE: Manual Review (No Automated Tests)** +This feature is set for manual review - focus on clean implementation without automated tests.` + : `**๐Ÿงช MODE: Test-Driven Development (TDD)** +This feature requires automated Playwright tests to verify the implementation.`; + return `You are resuming work on a feature implementation that was previously started. +${modeHeader} +${memoryContent} **Current Feature:** ID: ${feature.id} Category: ${feature.category} Description: ${feature.description} -${skipTestsNote}${imagesNote} +${skipTestsNote}${imagesNote}${contextFilesPreview} **Steps to Complete:** ${feature.steps.map((step, i) => `${i + 1}. ${step}`).join("\n")} @@ -460,10 +529,40 @@ Begin by exploring the project structure.`; /** * Get the system prompt for coding agent + * @param {string} projectPath - Path to the project + * @param {boolean} isTDD - Whether this is Test-Driven Development mode (skipTests=false) */ - getCodingPrompt() { + async getCodingPrompt(projectPath, isTDD = true) { + // Get context files preview + const contextFilesPreview = projectPath + ? await contextManager.getContextFilesPreview(projectPath) + : ""; + + // Get memory content (lessons learned from previous runs) + const memoryContent = projectPath + ? await contextManager.getMemoryContent(projectPath) + : ""; + + // Build mode-specific instructions + const modeHeader = isTDD + ? `**๐Ÿงช MODE: Test-Driven Development (TDD)** +You are implementing features using TDD methodology. This means: +- Write Playwright tests BEFORE or alongside implementation +- Run tests frequently to verify your work +- Tests are your validation mechanism +- Delete tests after they pass (they're for immediate verification only)` + : `**๐Ÿ”จ MODE: Manual Review (No Automated Tests)** +You are implementing features for manual user review. This means: +- Focus on clean, working implementation +- NO automated test writing required +- User will manually verify the implementation +- DO NOT commit changes - user will review and commit`; + return `You are an AI coding agent working autonomously to implement features. +${modeHeader} +${memoryContent} + **๐Ÿšจ CRITICAL FILE PROTECTION - READ THIS FIRST ๐Ÿšจ** THE FOLLOWING FILE IS ABSOLUTELY FORBIDDEN FROM DIRECT MODIFICATION: @@ -486,6 +585,8 @@ Directly modifying feature_list.json can: **THE ONLY WAY to update features:** Use the mcp__automaker-tools__UpdateFeatureStatus tool with featureId, status, and summary parameters. +${contextFilesPreview} + Your role is to: - Implement features exactly as specified - Write production-quality code @@ -547,15 +648,62 @@ You have full access to: - Search and analyze the codebase - **UpdateFeatureStatus tool** (mcp__automaker-tools__UpdateFeatureStatus) - Use this to update feature status +**๐Ÿง  Learning from Errors - Memory System:** + +If you encounter an error or issue that: +- Took multiple attempts to debug +- Was caused by a non-obvious codebase quirk +- Required understanding something specific about this project +- Could trip up future agent runs + +**ADD IT TO MEMORY** by appending to \`.automaker/memory.md\`: + +\`\`\`markdown +### Issue: [Brief Title] +**Problem:** [1-2 sentence description of the issue] +**Fix:** [Concise explanation of the solution] +\`\`\` + +Keep entries concise - focus on the essential information needed to avoid the issue in the future. This helps both you and other agents learn from mistakes. + Focus on one feature at a time and complete it fully before finishing. Always delete tests after they pass and use the UpdateFeatureStatus tool.`; } /** * Get the system prompt for verification agent + * @param {string} projectPath - Path to the project + * @param {boolean} isTDD - Whether this is Test-Driven Development mode (skipTests=false) */ - getVerificationPrompt() { + async getVerificationPrompt(projectPath, isTDD = true) { + // Get context files preview + const contextFilesPreview = projectPath + ? await contextManager.getContextFilesPreview(projectPath) + : ""; + + // Get memory content (lessons learned from previous runs) + const memoryContent = projectPath + ? await contextManager.getMemoryContent(projectPath) + : ""; + + // Build mode-specific instructions + const modeHeader = isTDD + ? `**๐Ÿงช MODE: Test-Driven Development (TDD)** +You are verifying/completing features using TDD methodology. This means: +- Run Playwright tests to verify implementation +- Fix failing tests by updating code +- Tests are your validation mechanism +- Delete tests after they pass (they're for immediate verification only)` + : `**๐Ÿ”จ MODE: Manual Review (No Automated Tests)** +You are completing features for manual user review. This means: +- Focus on clean, working implementation +- NO automated test writing required +- User will manually verify the implementation +- DO NOT commit changes - user will review and commit`; + return `You are an AI implementation and verification agent focused on completing features and ensuring they work. +${modeHeader} +${memoryContent} **๐Ÿšจ CRITICAL FILE PROTECTION - READ THIS FIRST ๐Ÿšจ** THE FOLLOWING FILE IS ABSOLUTELY FORBIDDEN FROM DIRECT MODIFICATION: @@ -578,6 +726,8 @@ Directly modifying feature_list.json can: **THE ONLY WAY to update features:** Use the mcp__automaker-tools__UpdateFeatureStatus tool with featureId, status, and summary parameters. +${contextFilesPreview} + Your role is to: - **Continue implementing features until they are complete** - don't stop at the first failure - Check if feature.skipTests is true - if so, skip automated testing and don't commit @@ -638,6 +788,24 @@ You have access to: - Make git commits - **UpdateFeatureStatus tool** (mcp__automaker-tools__UpdateFeatureStatus) - Use this to update feature status +**๐Ÿง  Learning from Errors - Memory System:** + +If you encounter an error or issue that: +- Took multiple attempts to debug +- Was caused by a non-obvious codebase quirk +- Required understanding something specific about this project +- Could trip up future agent runs + +**ADD IT TO MEMORY** by appending to \`.automaker/memory.md\`: + +\`\`\`markdown +### Issue: [Brief Title] +**Problem:** [1-2 sentence description of the issue] +**Fix:** [Concise explanation of the solution] +\`\`\` + +Keep entries concise - focus on the essential information needed to avoid the issue in the future. This helps both you and other agents learn from mistakes. + **CRITICAL:** Be persistent and thorough - keep iterating on the implementation until all tests pass. Don't give up after the first failure. Always delete tests after they pass, use the UpdateFeatureStatus tool with a summary, and commit your work.`; } diff --git a/app/src/app/layout.tsx b/app/src/app/layout.tsx index 017418b9..37393853 100644 --- a/app/src/app/layout.tsx +++ b/app/src/app/layout.tsx @@ -29,7 +29,7 @@ export default function RootLayout({ className={`${geistSans.variable} ${geistMono.variable} antialiased`} > {children} - + ); diff --git a/app/src/components/layout/sidebar.tsx b/app/src/components/layout/sidebar.tsx index 85e9dd7f..6054245a 100644 --- a/app/src/components/layout/sidebar.tsx +++ b/app/src/components/layout/sidebar.tsx @@ -699,7 +699,7 @@ export function Sidebar() {