mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-31 20:03:37 +00:00
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:
@@ -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
|
||||
@@ -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
148
app/electron/services/feature-verifier.js
Normal file
148
app/electron/services/feature-verifier.js
Normal 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();
|
||||
112
app/electron/services/project-analyzer.js
Normal file
112
app/electron/services/project-analyzer.js
Normal 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();
|
||||
Reference in New Issue
Block a user