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

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();