|
|
|
|
@@ -1,6 +1,8 @@
|
|
|
|
|
const { query, AbortError } = require("@anthropic-ai/claude-agent-sdk");
|
|
|
|
|
const fs = require("fs/promises");
|
|
|
|
|
const path = require("path");
|
|
|
|
|
const mcpServerFactory = require("./mcp-server-factory");
|
|
|
|
|
const featureLoader = require("./feature-loader");
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* XML template for app_spec.txt
|
|
|
|
|
@@ -95,18 +97,60 @@ class SpecRegenerationService {
|
|
|
|
|
* @param {boolean} generateFeatures - Whether to generate feature entries in features folder
|
|
|
|
|
*/
|
|
|
|
|
async createInitialSpec(projectPath, projectOverview, sendToRenderer, execution, generateFeatures = true) {
|
|
|
|
|
console.log(`[SpecRegeneration] Creating initial spec for: ${projectPath}, generateFeatures: ${generateFeatures}`);
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
console.log(`[SpecRegeneration] ===== Starting initial spec creation =====`);
|
|
|
|
|
console.log(`[SpecRegeneration] Project path: ${projectPath}`);
|
|
|
|
|
console.log(`[SpecRegeneration] Generate features: ${generateFeatures}`);
|
|
|
|
|
console.log(`[SpecRegeneration] Project overview length: ${projectOverview.length} characters`);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const abortController = new AbortController();
|
|
|
|
|
execution.abortController = abortController;
|
|
|
|
|
|
|
|
|
|
// Phase tracking
|
|
|
|
|
let currentPhase = "initialization";
|
|
|
|
|
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `[Phase: ${currentPhase}] Initializing spec generation process...\n`,
|
|
|
|
|
});
|
|
|
|
|
console.log(`[SpecRegeneration] Phase: ${currentPhase}`);
|
|
|
|
|
|
|
|
|
|
// Create custom MCP server with UpdateFeatureStatus tool if generating features
|
|
|
|
|
let featureToolsServer = null;
|
|
|
|
|
if (generateFeatures) {
|
|
|
|
|
console.log("[SpecRegeneration] Setting up feature generation tools...");
|
|
|
|
|
try {
|
|
|
|
|
featureToolsServer = mcpServerFactory.createFeatureToolsServer(
|
|
|
|
|
featureLoader.updateFeatureStatus.bind(featureLoader),
|
|
|
|
|
projectPath
|
|
|
|
|
);
|
|
|
|
|
console.log("[SpecRegeneration] Feature tools server created successfully");
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("[SpecRegeneration] ERROR: Failed to create feature tools server:", error);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_error",
|
|
|
|
|
error: `Failed to initialize feature generation tools: ${error.message}`,
|
|
|
|
|
});
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentPhase = "setup";
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `[Phase: ${currentPhase}] Configuring AI agent and tools...\n`,
|
|
|
|
|
});
|
|
|
|
|
console.log(`[SpecRegeneration] Phase: ${currentPhase}`);
|
|
|
|
|
|
|
|
|
|
// Phase 1: Generate spec WITHOUT UpdateFeatureStatus tool
|
|
|
|
|
// This prevents features from being created before the spec is complete
|
|
|
|
|
const options = {
|
|
|
|
|
model: "claude-sonnet-4-20250514",
|
|
|
|
|
systemPrompt: this.getInitialCreationSystemPrompt(generateFeatures),
|
|
|
|
|
systemPrompt: this.getInitialCreationSystemPrompt(false), // Always false - no feature tools during spec gen
|
|
|
|
|
maxTurns: 50,
|
|
|
|
|
cwd: projectPath,
|
|
|
|
|
allowedTools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash"],
|
|
|
|
|
allowedTools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash"], // No UpdateFeatureStatus during spec gen
|
|
|
|
|
permissionMode: "acceptEdits",
|
|
|
|
|
sandbox: {
|
|
|
|
|
enabled: true,
|
|
|
|
|
@@ -115,54 +159,157 @@ class SpecRegenerationService {
|
|
|
|
|
abortController: abortController,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const prompt = this.buildInitialCreationPrompt(projectOverview, generateFeatures);
|
|
|
|
|
const prompt = this.buildInitialCreationPrompt(projectOverview); // No feature generation during spec creation
|
|
|
|
|
|
|
|
|
|
currentPhase = "analysis";
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: "Starting project analysis and spec creation...\n",
|
|
|
|
|
content: `[Phase: ${currentPhase}] Starting project analysis and spec creation...\n`,
|
|
|
|
|
});
|
|
|
|
|
console.log(`[SpecRegeneration] Phase: ${currentPhase} - Starting AI agent query`);
|
|
|
|
|
|
|
|
|
|
if (generateFeatures) {
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `[Phase: ${currentPhase}] Feature generation is enabled - features will be created after spec is complete.\n`,
|
|
|
|
|
});
|
|
|
|
|
console.log("[SpecRegeneration] Feature generation enabled - will create features after spec");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const currentQuery = query({ prompt, options });
|
|
|
|
|
execution.query = currentQuery;
|
|
|
|
|
|
|
|
|
|
let fullResponse = "";
|
|
|
|
|
for await (const msg of currentQuery) {
|
|
|
|
|
if (!execution.isActive()) break;
|
|
|
|
|
let toolCallCount = 0;
|
|
|
|
|
let messageCount = 0;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
for await (const msg of currentQuery) {
|
|
|
|
|
if (!execution.isActive()) {
|
|
|
|
|
console.log("[SpecRegeneration] Execution aborted by user");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (msg.type === "assistant" && msg.message?.content) {
|
|
|
|
|
for (const block of msg.message.content) {
|
|
|
|
|
if (block.type === "text") {
|
|
|
|
|
fullResponse += block.text;
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: block.text,
|
|
|
|
|
});
|
|
|
|
|
} else if (block.type === "tool_use") {
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_tool",
|
|
|
|
|
tool: block.name,
|
|
|
|
|
input: block.input,
|
|
|
|
|
});
|
|
|
|
|
if (msg.type === "assistant" && msg.message?.content) {
|
|
|
|
|
messageCount++;
|
|
|
|
|
for (const block of msg.message.content) {
|
|
|
|
|
if (block.type === "text") {
|
|
|
|
|
fullResponse += block.text;
|
|
|
|
|
const preview = block.text.substring(0, 100).replace(/\n/g, " ");
|
|
|
|
|
console.log(`[SpecRegeneration] Agent message #${messageCount}: ${preview}...`);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: block.text,
|
|
|
|
|
});
|
|
|
|
|
} else if (block.type === "tool_use") {
|
|
|
|
|
toolCallCount++;
|
|
|
|
|
const toolName = block.name;
|
|
|
|
|
console.log(`[SpecRegeneration] Tool call #${toolCallCount}: ${toolName}`);
|
|
|
|
|
console.log(`[SpecRegeneration] Tool input: ${JSON.stringify(block.input).substring(0, 200)}...`);
|
|
|
|
|
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `\n[Tool] Using ${toolName}...\n`,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_tool",
|
|
|
|
|
tool: toolName,
|
|
|
|
|
input: block.input,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (msg.type === "tool_result") {
|
|
|
|
|
const toolName = msg.toolName || "unknown";
|
|
|
|
|
const result = msg.content?.[0]?.text || JSON.stringify(msg.content);
|
|
|
|
|
const resultPreview = result.substring(0, 200).replace(/\n/g, " ");
|
|
|
|
|
console.log(`[SpecRegeneration] Tool result (${toolName}): ${resultPreview}...`);
|
|
|
|
|
|
|
|
|
|
// During spec generation, UpdateFeatureStatus is not available
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `[Tool Result] ${toolName} completed successfully\n`,
|
|
|
|
|
});
|
|
|
|
|
} else if (msg.type === "error") {
|
|
|
|
|
const errorMsg = msg.error?.message || JSON.stringify(msg.error);
|
|
|
|
|
console.error(`[SpecRegeneration] ERROR in query stream: ${errorMsg}`);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_error",
|
|
|
|
|
error: `Error during spec generation: ${errorMsg}`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (streamError) {
|
|
|
|
|
console.error("[SpecRegeneration] ERROR in query stream:", streamError);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_error",
|
|
|
|
|
error: `Stream error: ${streamError.message || String(streamError)}`,
|
|
|
|
|
});
|
|
|
|
|
throw streamError;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[SpecRegeneration] Query completed - ${messageCount} messages, ${toolCallCount} tool calls`);
|
|
|
|
|
|
|
|
|
|
execution.query = null;
|
|
|
|
|
execution.abortController = null;
|
|
|
|
|
|
|
|
|
|
const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
|
|
|
currentPhase = "spec_complete";
|
|
|
|
|
console.log(`[SpecRegeneration] Phase: ${currentPhase} - Spec creation completed in ${elapsedTime}s`);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_complete",
|
|
|
|
|
message: "Initial spec creation complete!",
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `\n[Phase: ${currentPhase}] ✓ App specification created successfully! (${elapsedTime}s)\n`,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (generateFeatures) {
|
|
|
|
|
// Phase 2: Generate features AFTER spec is complete
|
|
|
|
|
console.log(`[SpecRegeneration] Starting Phase 2: Feature generation from app_spec.txt`);
|
|
|
|
|
|
|
|
|
|
// Send intermediate completion event for spec creation
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_complete",
|
|
|
|
|
message: "Initial spec creation complete! Features are being generated...",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Now start feature generation in a separate query
|
|
|
|
|
try {
|
|
|
|
|
await this.generateFeaturesFromSpec(projectPath, sendToRenderer, execution, startTime);
|
|
|
|
|
console.log(`[SpecRegeneration] Feature generation completed successfully`);
|
|
|
|
|
} catch (featureError) {
|
|
|
|
|
console.error(`[SpecRegeneration] Feature generation failed:`, featureError);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_error",
|
|
|
|
|
error: `Feature generation failed: ${featureError.message || String(featureError)}`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
currentPhase = "complete";
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `[Phase: ${currentPhase}] All tasks completed!\n`,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Send final completion event
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_complete",
|
|
|
|
|
message: "Initial spec creation complete!",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[SpecRegeneration] ===== Initial spec creation finished successfully =====`);
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
message: "Initial spec creation complete",
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
|
|
|
|
|
|
|
|
if (error instanceof AbortError || error?.name === "AbortError") {
|
|
|
|
|
console.log("[SpecRegeneration] Creation aborted");
|
|
|
|
|
console.log(`[SpecRegeneration] Creation aborted after ${elapsedTime}s`);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_error",
|
|
|
|
|
error: "Spec generation was aborted by user",
|
|
|
|
|
});
|
|
|
|
|
if (execution) {
|
|
|
|
|
execution.abortController = null;
|
|
|
|
|
execution.query = null;
|
|
|
|
|
@@ -173,7 +320,252 @@ class SpecRegenerationService {
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.error("[SpecRegeneration] Error creating initial spec:", error);
|
|
|
|
|
const errorMessage = error.message || String(error);
|
|
|
|
|
const errorStack = error.stack || "";
|
|
|
|
|
console.error(`[SpecRegeneration] ERROR creating initial spec after ${elapsedTime}s:`);
|
|
|
|
|
console.error(`[SpecRegeneration] Error message: ${errorMessage}`);
|
|
|
|
|
console.error(`[SpecRegeneration] Error stack: ${errorStack}`);
|
|
|
|
|
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_error",
|
|
|
|
|
error: `Failed to create spec: ${errorMessage}`,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (execution) {
|
|
|
|
|
execution.abortController = null;
|
|
|
|
|
execution.query = null;
|
|
|
|
|
}
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generate features from the implementation roadmap in app_spec.txt
|
|
|
|
|
* This is called AFTER the spec has been created
|
|
|
|
|
*/
|
|
|
|
|
async generateFeaturesFromSpec(projectPath, sendToRenderer, execution, startTime) {
|
|
|
|
|
const featureStartTime = Date.now();
|
|
|
|
|
let currentPhase = "feature_generation";
|
|
|
|
|
|
|
|
|
|
console.log(`[SpecRegeneration] ===== Starting Phase 2: Feature Generation =====`);
|
|
|
|
|
console.log(`[SpecRegeneration] Project path: ${projectPath}`);
|
|
|
|
|
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `\n[Phase: ${currentPhase}] Starting feature creation from implementation roadmap...\n`,
|
|
|
|
|
});
|
|
|
|
|
console.log(`[SpecRegeneration] Phase: ${currentPhase} - Starting feature generation query`);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Create feature tools server
|
|
|
|
|
const featureToolsServer = mcpServerFactory.createFeatureToolsServer(
|
|
|
|
|
featureLoader.updateFeatureStatus.bind(featureLoader),
|
|
|
|
|
projectPath
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const abortController = new AbortController();
|
|
|
|
|
execution.abortController = abortController;
|
|
|
|
|
|
|
|
|
|
const options = {
|
|
|
|
|
model: "claude-sonnet-4-20250514",
|
|
|
|
|
systemPrompt: `You are a feature management assistant. Your job is to read the app_spec.txt file and create feature entries based on the implementation_roadmap section.
|
|
|
|
|
|
|
|
|
|
**Your Task:**
|
|
|
|
|
1. Read the .automaker/app_spec.txt file
|
|
|
|
|
2. Parse the implementation_roadmap section (it contains phases with features listed)
|
|
|
|
|
3. For each feature listed in the roadmap, use the UpdateFeatureStatus tool to create a feature entry
|
|
|
|
|
4. Set the initial status to "todo" for all features
|
|
|
|
|
5. Extract a meaningful summary/description for each feature from the roadmap
|
|
|
|
|
|
|
|
|
|
**Feature Storage:**
|
|
|
|
|
Features are stored in .automaker/features/{id}/feature.json - each feature has its own folder.
|
|
|
|
|
Use the UpdateFeatureStatus tool to create features. The tool will handle creating the directory structure and feature.json file.
|
|
|
|
|
|
|
|
|
|
**Important:**
|
|
|
|
|
- Create features ONLY from the implementation_roadmap section
|
|
|
|
|
- Use the UpdateFeatureStatus tool for each feature
|
|
|
|
|
- Set status to "todo" initially
|
|
|
|
|
- Use a descriptive featureId based on the feature name (lowercase, hyphens for spaces)`,
|
|
|
|
|
maxTurns: 50,
|
|
|
|
|
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 = `Please read the .automaker/app_spec.txt file and create feature entries for all features listed in the implementation_roadmap section.
|
|
|
|
|
|
|
|
|
|
For each feature in the roadmap:
|
|
|
|
|
1. Use the UpdateFeatureStatus tool to create the feature
|
|
|
|
|
2. Set status to "todo"
|
|
|
|
|
3. Provide a clear summary/description based on what's in the roadmap
|
|
|
|
|
4. Use a featureId that's descriptive and follows the pattern: lowercase with hyphens (e.g., "user-authentication", "payment-processing")
|
|
|
|
|
|
|
|
|
|
Start by reading the app_spec.txt file to see the implementation roadmap.`;
|
|
|
|
|
|
|
|
|
|
const currentQuery = query({ prompt, options });
|
|
|
|
|
execution.query = currentQuery;
|
|
|
|
|
|
|
|
|
|
let toolCallCount = 0;
|
|
|
|
|
let messageCount = 0;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
for await (const msg of currentQuery) {
|
|
|
|
|
if (!execution.isActive()) {
|
|
|
|
|
console.log("[SpecRegeneration] Feature generation aborted by user");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (msg.type === "assistant" && msg.message?.content) {
|
|
|
|
|
messageCount++;
|
|
|
|
|
for (const block of msg.message.content) {
|
|
|
|
|
if (block.type === "text") {
|
|
|
|
|
const preview = block.text.substring(0, 100).replace(/\n/g, " ");
|
|
|
|
|
console.log(`[SpecRegeneration] Feature gen message #${messageCount}: ${preview}...`);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: block.text,
|
|
|
|
|
});
|
|
|
|
|
} else if (block.type === "tool_use") {
|
|
|
|
|
toolCallCount++;
|
|
|
|
|
const toolName = block.name;
|
|
|
|
|
const toolInput = block.input;
|
|
|
|
|
console.log(`[SpecRegeneration] Feature gen tool call #${toolCallCount}: ${toolName}`);
|
|
|
|
|
|
|
|
|
|
if (toolName === "mcp__automaker-tools__UpdateFeatureStatus" || toolName === "UpdateFeatureStatus") {
|
|
|
|
|
const featureId = toolInput?.featureId || "unknown";
|
|
|
|
|
const status = toolInput?.status || "unknown";
|
|
|
|
|
const summary = toolInput?.summary || "";
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `\n[Feature Creation] Creating feature "${featureId}" with status "${status}"${summary ? `\n Summary: ${summary}` : ""}\n`,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `\n[Tool] Using ${toolName}...\n`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_tool",
|
|
|
|
|
tool: toolName,
|
|
|
|
|
input: toolInput,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (msg.type === "tool_result") {
|
|
|
|
|
const toolName = msg.toolName || "unknown";
|
|
|
|
|
const result = msg.content?.[0]?.text || JSON.stringify(msg.content);
|
|
|
|
|
const resultPreview = result.substring(0, 200).replace(/\n/g, " ");
|
|
|
|
|
console.log(`[SpecRegeneration] Feature gen tool result (${toolName}): ${resultPreview}...`);
|
|
|
|
|
|
|
|
|
|
if (toolName === "mcp__automaker-tools__UpdateFeatureStatus" || toolName === "UpdateFeatureStatus") {
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `[Feature Creation] ${result}\n`,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `[Tool Result] ${toolName} completed successfully\n`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else if (msg.type === "error") {
|
|
|
|
|
const errorMsg = msg.error?.message || JSON.stringify(msg.error);
|
|
|
|
|
console.error(`[SpecRegeneration] ERROR in feature generation stream: ${errorMsg}`);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_error",
|
|
|
|
|
error: `Error during feature generation: ${errorMsg}`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (streamError) {
|
|
|
|
|
console.error("[SpecRegeneration] ERROR in feature generation stream:", streamError);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_error",
|
|
|
|
|
error: `Feature generation stream error: ${streamError.message || String(streamError)}`,
|
|
|
|
|
});
|
|
|
|
|
throw streamError;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[SpecRegeneration] Feature generation completed - ${messageCount} messages, ${toolCallCount} tool calls`);
|
|
|
|
|
|
|
|
|
|
execution.query = null;
|
|
|
|
|
execution.abortController = null;
|
|
|
|
|
|
|
|
|
|
currentPhase = "complete";
|
|
|
|
|
const featureElapsedTime = ((Date.now() - featureStartTime) / 1000).toFixed(1);
|
|
|
|
|
const totalElapsedTime = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `\n[Phase: ${currentPhase}] ✓ All tasks completed! (${totalElapsedTime}s total, ${featureElapsedTime}s for features)\n`,
|
|
|
|
|
});
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_complete",
|
|
|
|
|
message: "All tasks completed!",
|
|
|
|
|
});
|
|
|
|
|
console.log(`[SpecRegeneration] All tasks completed including feature generation`);
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const errorMessage = error.message || String(error);
|
|
|
|
|
console.error(`[SpecRegeneration] ERROR generating features: ${errorMessage}`);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_error",
|
|
|
|
|
error: `Failed to generate features: ${errorMessage}`,
|
|
|
|
|
});
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generate features from existing app_spec.txt
|
|
|
|
|
* This is a standalone method that can be called without generating a new spec
|
|
|
|
|
* Useful for retroactively generating features from an existing spec
|
|
|
|
|
*/
|
|
|
|
|
async generateFeaturesOnly(projectPath, sendToRenderer, execution) {
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
console.log(`[SpecRegeneration] ===== Starting standalone feature generation =====`);
|
|
|
|
|
console.log(`[SpecRegeneration] Project path: ${projectPath}`);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Verify app_spec.txt exists
|
|
|
|
|
const specPath = path.join(projectPath, ".automaker", "app_spec.txt");
|
|
|
|
|
try {
|
|
|
|
|
await fs.access(specPath);
|
|
|
|
|
} catch {
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_error",
|
|
|
|
|
error: "No app_spec.txt found. Please create a spec first before generating features.",
|
|
|
|
|
});
|
|
|
|
|
throw new Error("No app_spec.txt found");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `[Phase: initialization] Starting feature generation from existing app_spec.txt...\n`,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Use the existing feature generation method
|
|
|
|
|
await this.generateFeaturesFromSpec(projectPath, sendToRenderer, execution, startTime);
|
|
|
|
|
|
|
|
|
|
console.log(`[SpecRegeneration] ===== Standalone feature generation finished successfully =====`);
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
message: "Feature generation complete",
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
|
|
|
const errorMessage = error.message || String(error);
|
|
|
|
|
console.error(`[SpecRegeneration] ERROR in standalone feature generation after ${elapsedTime}s: ${errorMessage}`);
|
|
|
|
|
|
|
|
|
|
if (execution) {
|
|
|
|
|
execution.abortController = null;
|
|
|
|
|
execution.query = null;
|
|
|
|
|
@@ -205,14 +597,12 @@ When analyzing, look at:
|
|
|
|
|
- Database configurations and schemas
|
|
|
|
|
- API structures and patterns
|
|
|
|
|
|
|
|
|
|
**Feature Storage:**
|
|
|
|
|
Features are stored in .automaker/features/{id}/feature.json - each feature has its own folder.
|
|
|
|
|
Do NOT manually create feature files. Use the UpdateFeatureStatus tool to manage features.
|
|
|
|
|
|
|
|
|
|
You CAN and SHOULD modify:
|
|
|
|
|
- .automaker/app_spec.txt (this is your primary target)
|
|
|
|
|
|
|
|
|
|
You have access to file reading, writing, and search tools. Use them to understand the codebase and write the new spec.`;
|
|
|
|
|
You have access to file reading, writing, and search tools. Use them to understand the codebase and write the new spec.
|
|
|
|
|
|
|
|
|
|
**IMPORTANT:** Focus ONLY on creating the app_spec.txt file. Do NOT create any feature files or use any feature management tools during this phase.`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -266,12 +656,29 @@ Begin by exploring the project structure.`;
|
|
|
|
|
* Regenerate the app spec based on user's project definition
|
|
|
|
|
*/
|
|
|
|
|
async regenerateSpec(projectPath, projectDefinition, sendToRenderer, execution) {
|
|
|
|
|
console.log(`[SpecRegeneration] Regenerating spec for: ${projectPath}`);
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
console.log(`[SpecRegeneration] ===== Starting spec regeneration =====`);
|
|
|
|
|
console.log(`[SpecRegeneration] Project path: ${projectPath}`);
|
|
|
|
|
console.log(`[SpecRegeneration] Project definition length: ${projectDefinition.length} characters`);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
let currentPhase = "initialization";
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `[Phase: ${currentPhase}] Initializing spec regeneration process...\n`,
|
|
|
|
|
});
|
|
|
|
|
console.log(`[SpecRegeneration] Phase: ${currentPhase}`);
|
|
|
|
|
|
|
|
|
|
const abortController = new AbortController();
|
|
|
|
|
execution.abortController = abortController;
|
|
|
|
|
|
|
|
|
|
currentPhase = "setup";
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `[Phase: ${currentPhase}] Configuring AI agent and tools...\n`,
|
|
|
|
|
});
|
|
|
|
|
console.log(`[SpecRegeneration] Phase: ${currentPhase}`);
|
|
|
|
|
|
|
|
|
|
const options = {
|
|
|
|
|
model: "claude-sonnet-4-20250514",
|
|
|
|
|
systemPrompt: this.getSystemPrompt(),
|
|
|
|
|
@@ -288,52 +695,137 @@ Begin by exploring the project structure.`;
|
|
|
|
|
|
|
|
|
|
const prompt = this.buildRegenerationPrompt(projectDefinition);
|
|
|
|
|
|
|
|
|
|
currentPhase = "regeneration";
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: "Starting spec regeneration...\n",
|
|
|
|
|
content: `[Phase: ${currentPhase}] Starting spec regeneration...\n`,
|
|
|
|
|
});
|
|
|
|
|
console.log(`[SpecRegeneration] Phase: ${currentPhase} - Starting AI agent query`);
|
|
|
|
|
|
|
|
|
|
const currentQuery = query({ prompt, options });
|
|
|
|
|
execution.query = currentQuery;
|
|
|
|
|
|
|
|
|
|
let fullResponse = "";
|
|
|
|
|
for await (const msg of currentQuery) {
|
|
|
|
|
if (!execution.isActive()) break;
|
|
|
|
|
let toolCallCount = 0;
|
|
|
|
|
let messageCount = 0;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
for await (const msg of currentQuery) {
|
|
|
|
|
if (!execution.isActive()) {
|
|
|
|
|
console.log("[SpecRegeneration] Execution aborted by user");
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (msg.type === "assistant" && msg.message?.content) {
|
|
|
|
|
for (const block of msg.message.content) {
|
|
|
|
|
if (block.type === "text") {
|
|
|
|
|
fullResponse += block.text;
|
|
|
|
|
if (msg.type === "assistant" && msg.message?.content) {
|
|
|
|
|
messageCount++;
|
|
|
|
|
for (const block of msg.message.content) {
|
|
|
|
|
if (block.type === "text") {
|
|
|
|
|
fullResponse += block.text;
|
|
|
|
|
const preview = block.text.substring(0, 100).replace(/\n/g, " ");
|
|
|
|
|
console.log(`[SpecRegeneration] Agent message #${messageCount}: ${preview}...`);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `[Agent] ${block.text}`,
|
|
|
|
|
});
|
|
|
|
|
} else if (block.type === "tool_use") {
|
|
|
|
|
toolCallCount++;
|
|
|
|
|
const toolName = block.name;
|
|
|
|
|
const toolInput = block.input;
|
|
|
|
|
console.log(`[SpecRegeneration] Tool call #${toolCallCount}: ${toolName}`);
|
|
|
|
|
console.log(`[SpecRegeneration] Tool input: ${JSON.stringify(toolInput).substring(0, 200)}...`);
|
|
|
|
|
|
|
|
|
|
// Special handling for UpdateFeatureStatus to show feature creation
|
|
|
|
|
if (toolName === "mcp__automaker-tools__UpdateFeatureStatus" || toolName === "UpdateFeatureStatus") {
|
|
|
|
|
const featureId = toolInput?.featureId || "unknown";
|
|
|
|
|
const status = toolInput?.status || "unknown";
|
|
|
|
|
const summary = toolInput?.summary || "";
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `\n[Feature Creation] Creating feature "${featureId}" with status "${status}"${summary ? `\n Summary: ${summary}` : ""}\n`,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `\n[Tool] Using ${toolName}...\n`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_tool",
|
|
|
|
|
tool: toolName,
|
|
|
|
|
input: toolInput,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (msg.type === "tool_result") {
|
|
|
|
|
// Log tool results for better visibility
|
|
|
|
|
const toolName = msg.toolName || "unknown";
|
|
|
|
|
const result = msg.content?.[0]?.text || JSON.stringify(msg.content);
|
|
|
|
|
const resultPreview = result.substring(0, 200).replace(/\n/g, " ");
|
|
|
|
|
console.log(`[SpecRegeneration] Tool result (${toolName}): ${resultPreview}...`);
|
|
|
|
|
|
|
|
|
|
// Special handling for UpdateFeatureStatus results
|
|
|
|
|
if (toolName === "mcp__automaker-tools__UpdateFeatureStatus" || toolName === "UpdateFeatureStatus") {
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: block.text,
|
|
|
|
|
content: `[Feature Creation] ${result}\n`,
|
|
|
|
|
});
|
|
|
|
|
} else if (block.type === "tool_use") {
|
|
|
|
|
} else {
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_tool",
|
|
|
|
|
tool: block.name,
|
|
|
|
|
input: block.input,
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `[Tool Result] ${toolName} completed successfully\n`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
} else if (msg.type === "error") {
|
|
|
|
|
const errorMsg = msg.error?.message || JSON.stringify(msg.error);
|
|
|
|
|
console.error(`[SpecRegeneration] ERROR in query stream: ${errorMsg}`);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_error",
|
|
|
|
|
error: `Error during spec regeneration: ${errorMsg}`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (streamError) {
|
|
|
|
|
console.error("[SpecRegeneration] ERROR in query stream:", streamError);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_error",
|
|
|
|
|
error: `Stream error: ${streamError.message || String(streamError)}`,
|
|
|
|
|
});
|
|
|
|
|
throw streamError;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[SpecRegeneration] Query completed - ${messageCount} messages, ${toolCallCount} tool calls`);
|
|
|
|
|
|
|
|
|
|
execution.query = null;
|
|
|
|
|
execution.abortController = null;
|
|
|
|
|
|
|
|
|
|
const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
|
|
|
currentPhase = "complete";
|
|
|
|
|
console.log(`[SpecRegeneration] Phase: ${currentPhase} - Spec regeneration completed in ${elapsedTime}s`);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_progress",
|
|
|
|
|
content: `\n[Phase: ${currentPhase}] ✓ Spec regeneration complete! (${elapsedTime}s)\n`,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_complete",
|
|
|
|
|
message: "Spec regeneration complete!",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
console.log(`[SpecRegeneration] ===== Spec regeneration finished successfully =====`);
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
message: "Spec regeneration complete",
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
|
|
|
|
|
|
|
|
if (error instanceof AbortError || error?.name === "AbortError") {
|
|
|
|
|
console.log("[SpecRegeneration] Regeneration aborted");
|
|
|
|
|
console.log(`[SpecRegeneration] Regeneration aborted after ${elapsedTime}s`);
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_error",
|
|
|
|
|
error: "Spec regeneration was aborted by user",
|
|
|
|
|
});
|
|
|
|
|
if (execution) {
|
|
|
|
|
execution.abortController = null;
|
|
|
|
|
execution.query = null;
|
|
|
|
|
@@ -344,7 +836,17 @@ Begin by exploring the project structure.`;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.error("[SpecRegeneration] Error regenerating spec:", error);
|
|
|
|
|
const errorMessage = error.message || String(error);
|
|
|
|
|
const errorStack = error.stack || "";
|
|
|
|
|
console.error(`[SpecRegeneration] ERROR regenerating spec after ${elapsedTime}s:`);
|
|
|
|
|
console.error(`[SpecRegeneration] Error message: ${errorMessage}`);
|
|
|
|
|
console.error(`[SpecRegeneration] Error stack: ${errorStack}`);
|
|
|
|
|
|
|
|
|
|
sendToRenderer({
|
|
|
|
|
type: "spec_regeneration_error",
|
|
|
|
|
error: `Failed to regenerate spec: ${errorMessage}`,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (execution) {
|
|
|
|
|
execution.abortController = null;
|
|
|
|
|
execution.query = null;
|
|
|
|
|
|