Refactor Auto Mode Service and add feature verification and project analysis capabilities

- Refactored AutoModeService to delegate tasks to specialized services: featureLoader, featureExecutor, featureVerifier, contextManager, and projectAnalyzer.
- Implemented feature verification logic in a new FeatureVerifier service, which runs tests and updates feature status.
- Added ProjectAnalyzer service to scan project structure and update app_spec.txt.
- Removed obsolete methods related to feature loading and context management from AutoModeService.
- Updated feature status handling to ensure context files are deleted when features are verified.

This refactor enhances modularity and maintainability of the codebase, allowing for better separation of concerns in feature management.

🤖 Generated with Claude Code
This commit is contained in:
Cody Seibert
2025-12-09 13:03:03 -05:00
parent 081f7c6007
commit 08014f3a4a
5 changed files with 346 additions and 1302 deletions

View File

@@ -1,85 +0,0 @@
📋 Planning implementation for: I want the ability to press P which will automatically select my projects drop down and show all my projects. And then for each one, put a hotkey in the left that says 12345 and selecting one of those with my keyboard should automatically select that project.
⚡ Executing implementation for: I want the ability to press P which will automatically select my projects drop down and show all my projects. And then for each one, put a hotkey in the left that says 12345 and selecting one of those with my keyboard should automatically select that project.
Starting code implementation...
🔧 Tool: Glob
Let me search for more specific project files:
🔧 Tool: Glob
🔧 Tool: Read
🔧 Tool: Read
🔧 Tool: Read
Now let me read the keyboard shortcuts hook to understand the current shortcut system:
🔧 Tool: Read
🔧 Tool: Read
Now I have a good understanding of the codebase. Let me check if there are existing tests to understand the testing pattern:
🔧 Tool: Glob
🔧 Tool: Glob
🔧 Tool: Bash
🔧 Tool: Bash
🔧 Tool: Read
🔧 Tool: Bash
🔧 Tool: Read
Now I have a good understanding of the codebase. Let me implement the feature:
1. Press "P" to open the project selector dropdown
2. Show hotkeys (1-5 or up to available projects) next to each project option
3. Pressing a number key selects that project
Let me first add the necessary shortcut and then update the sidebar component:
🔧 Tool: TodoWrite
🔧 Tool: Edit
🔧 Tool: TodoWrite
Now let me update the sidebar to implement the full functionality. I need to:
1. Add state to control the dropdown open state
2. Add keyboard shortcut for "P" to open the dropdown
3. Handle number key selection when dropdown is open
4. Display hotkey indicators in the dropdown items
🔧 Tool: Edit
Now let me add the state for the dropdown and the keyboard handling:
🔧 Tool: Edit
Now I need to add the keyboard event handler for when the dropdown is open to handle number keys, and add the 'P' key shortcut:
🔧 Tool: Edit
Now I need to update the DropdownMenu to use controlled state and add hotkey indicators:
🔧 Tool: Edit
🔧 Tool: TodoWrite
Now let me update the utils.ts file to add helpers for testing this feature and then write the tests:
🔧 Tool: Edit
Now let me write the Playwright tests:
🔧 Tool: Write
Now let me run the tests to verify the feature works:
🔧 Tool: Bash
The server seems to be blocked. Let me try a different port or reuse existing server:
🔧 Tool: Bash
🔧 Tool: Bash
The server is already running. Let me use the reuse flag:
🔧 Tool: Bash
🔧 Tool: BashOutput
🔧 Tool: BashOutput
🔧 Tool: Bash
🔧 Tool: BashOutput
🔧 Tool: Bash
🔧 Tool: BashOutput
🔧 Tool: Bash

View File

@@ -25,7 +25,7 @@
"category": "Kanban",
"description": "In creating new cards in Kanban, I need the ability to drag and drop images into the description section, which will attach the image as context in store in the temp directory, so that later on when the agent runs, it can know where to fetch that image from.",
"steps": [],
"status": "in_progress"
"status": "verified"
},
{
"id": "feature-1765301184184-ttvhd8kkt",

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,148 @@
const { query, AbortError } = require("@anthropic-ai/claude-agent-sdk");
const promptBuilder = require("./prompt-builder");
const contextManager = require("./context-manager");
const featureLoader = require("./feature-loader");
const mcpServerFactory = require("./mcp-server-factory");
/**
* Feature Verifier - Handles feature verification by running tests
*/
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}`);
try {
const verifyMsg = `\n✅ Verifying tests for: ${feature.description}\n`;
await contextManager.writeToContextFile(projectPath, feature.id, verifyMsg);
sendToRenderer({
type: "auto_mode_phase",
featureId: feature.id,
phase: "verification",
message: `Verifying tests for: ${feature.description}`,
});
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-opus-4-5-20251101",
systemPrompt: promptBuilder.getVerificationPrompt(),
maxTurns: 1000,
cwd: projectPath,
mcpServers: {
"automaker-tools": featureToolsServer
},
allowedTools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash", "mcp__automaker-tools__UpdateFeatureStatus"],
permissionMode: "acceptEdits",
sandbox: {
enabled: true,
autoAllowBashIfSandboxed: true,
},
abortController: abortController,
};
const prompt = promptBuilder.buildVerificationPrompt(feature);
const runningTestsMsg =
"Running Playwright tests to verify feature implementation...\n";
await contextManager.writeToContextFile(projectPath, feature.id, runningTestsMsg);
sendToRenderer({
type: "auto_mode_progress",
featureId: feature.id,
content: runningTestsMsg,
});
const currentQuery = query({ prompt, options });
execution.query = currentQuery;
let responseText = "";
for await (const msg of currentQuery) {
// Check if this specific feature was aborted
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;
// Re-load features to check if it was marked as verified
const updatedFeatures = await featureLoader.loadFeatures(projectPath);
const updatedFeature = updatedFeatures.find((f) => f.id === feature.id);
const passes = updatedFeature?.status === "verified";
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);
sendToRenderer({
type: "auto_mode_progress",
featureId: feature.id,
content: finalMsg,
});
return {
passes,
message: responseText.substring(0, 500),
};
} catch (error) {
if (error instanceof AbortError || error?.name === "AbortError") {
console.log("[FeatureVerifier] Verification aborted");
if (execution) {
execution.abortController = null;
execution.query = null;
}
return {
passes: false,
message: "Verification aborted",
};
}
console.error("[FeatureVerifier] Error verifying feature:", error);
if (execution) {
execution.abortController = null;
execution.query = null;
}
throw error;
}
}
}
module.exports = new FeatureVerifier();

View File

@@ -0,0 +1,112 @@
const { query, AbortError } = require("@anthropic-ai/claude-agent-sdk");
const promptBuilder = require("./prompt-builder");
/**
* Project Analyzer - Scans codebase and updates app_spec.txt
*/
class ProjectAnalyzer {
/**
* Run the project analysis using Claude Agent SDK
*/
async runProjectAnalysis(projectPath, analysisId, sendToRenderer, execution) {
console.log(`[ProjectAnalyzer] Running project analysis for: ${projectPath}`);
try {
sendToRenderer({
type: "auto_mode_phase",
featureId: analysisId,
phase: "planning",
message: "Scanning project structure...",
});
const abortController = new AbortController();
execution.abortController = abortController;
const options = {
model: "claude-sonnet-4-20250514",
systemPrompt: promptBuilder.getProjectAnalysisSystemPrompt(),
maxTurns: 50,
cwd: projectPath,
allowedTools: ["Read", "Glob", "Grep", "Bash"],
permissionMode: "acceptEdits",
sandbox: {
enabled: true,
autoAllowBashIfSandboxed: true,
},
abortController: abortController,
};
const prompt = promptBuilder.buildProjectAnalysisPrompt(projectPath);
sendToRenderer({
type: "auto_mode_progress",
featureId: analysisId,
content: "Starting project analysis...\n",
});
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;
sendToRenderer({
type: "auto_mode_progress",
featureId: analysisId,
content: block.text,
});
} else if (block.type === "tool_use") {
sendToRenderer({
type: "auto_mode_tool",
featureId: analysisId,
tool: block.name,
input: block.input,
});
}
}
}
}
execution.query = null;
execution.abortController = null;
sendToRenderer({
type: "auto_mode_phase",
featureId: analysisId,
phase: "verification",
message: "Project analysis complete",
});
return {
success: true,
message: "Project analyzed successfully",
};
} catch (error) {
if (error instanceof AbortError || error?.name === "AbortError") {
console.log("[ProjectAnalyzer] Project analysis aborted");
if (execution) {
execution.abortController = null;
execution.query = null;
}
return {
success: false,
message: "Analysis aborted",
};
}
console.error("[ProjectAnalyzer] Error in project analysis:", error);
if (execution) {
execution.abortController = null;
execution.query = null;
}
throw error;
}
}
}
module.exports = new ProjectAnalyzer();