mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
Merge branch 'main' of github.com:webdevcody/automaker
This commit is contained in:
@@ -32,7 +32,7 @@ class AutoModeService {
|
||||
query: null,
|
||||
projectPath: null,
|
||||
sendToRenderer: null,
|
||||
isActive: () => this.runningFeatures.has(featureId)
|
||||
isActive: () => this.runningFeatures.has(featureId),
|
||||
};
|
||||
return context;
|
||||
}
|
||||
@@ -126,7 +126,11 @@ class AutoModeService {
|
||||
console.log(`[AutoMode] Running feature: ${feature.description}`);
|
||||
|
||||
// Update feature status to in_progress
|
||||
await featureLoader.updateFeatureStatus(featureId, "in_progress", projectPath);
|
||||
await featureLoader.updateFeatureStatus(
|
||||
featureId,
|
||||
"in_progress",
|
||||
projectPath
|
||||
);
|
||||
|
||||
sendToRenderer({
|
||||
type: "auto_mode_feature_start",
|
||||
@@ -135,13 +139,28 @@ class AutoModeService {
|
||||
});
|
||||
|
||||
// Implement the feature
|
||||
const result = await featureExecutor.implementFeature(feature, projectPath, sendToRenderer, execution);
|
||||
const result = await featureExecutor.implementFeature(
|
||||
feature,
|
||||
projectPath,
|
||||
sendToRenderer,
|
||||
execution
|
||||
);
|
||||
|
||||
// Update feature status based on result
|
||||
const newStatus = result.passes ? "verified" : "backlog";
|
||||
await featureLoader.updateFeatureStatus(feature.id, newStatus, projectPath);
|
||||
// For skipTests features, go to waiting_approval on success instead of verified
|
||||
let newStatus;
|
||||
if (result.passes) {
|
||||
newStatus = feature.skipTests ? "waiting_approval" : "verified";
|
||||
} else {
|
||||
newStatus = "backlog";
|
||||
}
|
||||
await featureLoader.updateFeatureStatus(
|
||||
feature.id,
|
||||
newStatus,
|
||||
projectPath
|
||||
);
|
||||
|
||||
// Delete context file if verified
|
||||
// Delete context file only if verified (not for waiting_approval)
|
||||
if (newStatus === "verified") {
|
||||
await contextManager.deleteContextFile(projectPath, feature.id);
|
||||
}
|
||||
@@ -208,11 +227,20 @@ class AutoModeService {
|
||||
});
|
||||
|
||||
// Verify the feature by running tests
|
||||
const result = await featureVerifier.verifyFeatureTests(feature, projectPath, sendToRenderer, execution);
|
||||
const result = await featureVerifier.verifyFeatureTests(
|
||||
feature,
|
||||
projectPath,
|
||||
sendToRenderer,
|
||||
execution
|
||||
);
|
||||
|
||||
// Update feature status based on result
|
||||
const newStatus = result.passes ? "verified" : "in_progress";
|
||||
await featureLoader.updateFeatureStatus(featureId, newStatus, projectPath);
|
||||
await featureLoader.updateFeatureStatus(
|
||||
featureId,
|
||||
newStatus,
|
||||
projectPath
|
||||
);
|
||||
|
||||
// Delete context file if verified
|
||||
if (newStatus === "verified") {
|
||||
@@ -281,10 +309,19 @@ class AutoModeService {
|
||||
});
|
||||
|
||||
// Read existing context
|
||||
const previousContext = await contextManager.readContextFile(projectPath, featureId);
|
||||
const previousContext = await contextManager.readContextFile(
|
||||
projectPath,
|
||||
featureId
|
||||
);
|
||||
|
||||
// Resume implementation with context
|
||||
const result = await featureExecutor.resumeFeatureWithContext(feature, projectPath, sendToRenderer, previousContext, execution);
|
||||
const result = await featureExecutor.resumeFeatureWithContext(
|
||||
feature,
|
||||
projectPath,
|
||||
sendToRenderer,
|
||||
previousContext,
|
||||
execution
|
||||
);
|
||||
|
||||
// If the agent ends early without finishing, automatically re-run
|
||||
let attempts = 0;
|
||||
@@ -298,11 +335,16 @@ class AutoModeService {
|
||||
|
||||
if (updatedFeature && updatedFeature.status === "in_progress") {
|
||||
attempts++;
|
||||
console.log(`[AutoMode] Feature ended early, auto-retrying (attempt ${attempts}/${maxAttempts})...`);
|
||||
console.log(
|
||||
`[AutoMode] Feature ended early, auto-retrying (attempt ${attempts}/${maxAttempts})...`
|
||||
);
|
||||
|
||||
// Update context file with retry message
|
||||
await contextManager.writeToContextFile(projectPath, featureId,
|
||||
`\n\n🔄 Auto-retry #${attempts} - Continuing implementation...\n\n`);
|
||||
await contextManager.writeToContextFile(
|
||||
projectPath,
|
||||
featureId,
|
||||
`\n\n🔄 Auto-retry #${attempts} - Continuing implementation...\n\n`
|
||||
);
|
||||
|
||||
sendToRenderer({
|
||||
type: "auto_mode_progress",
|
||||
@@ -311,20 +353,39 @@ class AutoModeService {
|
||||
});
|
||||
|
||||
// Read updated context
|
||||
const retryContext = await contextManager.readContextFile(projectPath, featureId);
|
||||
const retryContext = await contextManager.readContextFile(
|
||||
projectPath,
|
||||
featureId
|
||||
);
|
||||
|
||||
// Resume again with full context
|
||||
finalResult = await featureExecutor.resumeFeatureWithContext(feature, projectPath, sendToRenderer, retryContext, execution);
|
||||
finalResult = await featureExecutor.resumeFeatureWithContext(
|
||||
feature,
|
||||
projectPath,
|
||||
sendToRenderer,
|
||||
retryContext,
|
||||
execution
|
||||
);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update feature status based on final result
|
||||
const newStatus = finalResult.passes ? "verified" : "in_progress";
|
||||
await featureLoader.updateFeatureStatus(featureId, newStatus, projectPath);
|
||||
// For skipTests features, go to waiting_approval on success instead of verified
|
||||
let newStatus;
|
||||
if (finalResult.passes) {
|
||||
newStatus = feature.skipTests ? "waiting_approval" : "verified";
|
||||
} else {
|
||||
newStatus = "in_progress";
|
||||
}
|
||||
await featureLoader.updateFeatureStatus(
|
||||
featureId,
|
||||
newStatus,
|
||||
projectPath
|
||||
);
|
||||
|
||||
// Delete context file if verified
|
||||
// Delete context file only if verified (not for waiting_approval)
|
||||
if (newStatus === "verified") {
|
||||
await contextManager.deleteContextFile(projectPath, featureId);
|
||||
}
|
||||
@@ -377,7 +438,9 @@ class AutoModeService {
|
||||
|
||||
// Skip if this feature is already running (via manual trigger)
|
||||
if (this.runningFeatures.has(currentFeatureId)) {
|
||||
console.log(`[AutoMode] Skipping ${currentFeatureId} - already running`);
|
||||
console.log(
|
||||
`[AutoMode] Skipping ${currentFeatureId} - already running`
|
||||
);
|
||||
await this.sleep(3000);
|
||||
continue;
|
||||
}
|
||||
@@ -397,13 +460,28 @@ class AutoModeService {
|
||||
this.runningFeatures.set(currentFeatureId, execution);
|
||||
|
||||
// Implement the feature
|
||||
const result = await featureExecutor.implementFeature(nextFeature, projectPath, sendToRenderer, execution);
|
||||
const result = await featureExecutor.implementFeature(
|
||||
nextFeature,
|
||||
projectPath,
|
||||
sendToRenderer,
|
||||
execution
|
||||
);
|
||||
|
||||
// Update feature status based on result
|
||||
const newStatus = result.passes ? "verified" : "backlog";
|
||||
await featureLoader.updateFeatureStatus(nextFeature.id, newStatus, projectPath);
|
||||
// For skipTests features, go to waiting_approval on success instead of verified
|
||||
let newStatus;
|
||||
if (result.passes) {
|
||||
newStatus = nextFeature.skipTests ? "waiting_approval" : "verified";
|
||||
} else {
|
||||
newStatus = "backlog";
|
||||
}
|
||||
await featureLoader.updateFeatureStatus(
|
||||
nextFeature.id,
|
||||
newStatus,
|
||||
projectPath
|
||||
);
|
||||
|
||||
// Delete context file if verified
|
||||
// Delete context file only if verified (not for waiting_approval)
|
||||
if (newStatus === "verified") {
|
||||
await contextManager.deleteContextFile(projectPath, nextFeature.id);
|
||||
}
|
||||
@@ -477,7 +555,12 @@ class AutoModeService {
|
||||
});
|
||||
|
||||
// Perform the analysis
|
||||
const result = await projectAnalyzer.runProjectAnalysis(projectPath, analysisId, sendToRenderer, execution);
|
||||
const result = await projectAnalyzer.runProjectAnalysis(
|
||||
projectPath,
|
||||
analysisId,
|
||||
sendToRenderer,
|
||||
execution
|
||||
);
|
||||
|
||||
sendToRenderer({
|
||||
type: "auto_mode_feature_complete",
|
||||
@@ -500,6 +583,239 @@ class AutoModeService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop a specific feature by ID
|
||||
*/
|
||||
async stopFeature({ featureId }) {
|
||||
if (!this.runningFeatures.has(featureId)) {
|
||||
return { success: false, error: `Feature ${featureId} is not running` };
|
||||
}
|
||||
|
||||
console.log(`[AutoMode] Stopping feature: ${featureId}`);
|
||||
|
||||
const execution = this.runningFeatures.get(featureId);
|
||||
if (execution && execution.abortController) {
|
||||
execution.abortController.abort();
|
||||
}
|
||||
|
||||
// Clean up
|
||||
this.runningFeatures.delete(featureId);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow-up on a feature with additional prompt
|
||||
* This continues work on a feature that's in waiting_approval status
|
||||
*/
|
||||
async followUpFeature({
|
||||
projectPath,
|
||||
featureId,
|
||||
prompt,
|
||||
imagePaths,
|
||||
sendToRenderer,
|
||||
}) {
|
||||
// Check if this feature is already running
|
||||
if (this.runningFeatures.has(featureId)) {
|
||||
throw new Error(`Feature ${featureId} is already running`);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[AutoMode] Follow-up on feature: ${featureId} with prompt: ${prompt}`
|
||||
);
|
||||
|
||||
// Register this feature as running
|
||||
const execution = this.createExecutionContext(featureId);
|
||||
execution.projectPath = projectPath;
|
||||
execution.sendToRenderer = sendToRenderer;
|
||||
this.runningFeatures.set(featureId, execution);
|
||||
|
||||
// Start the async work in the background (don't await)
|
||||
// This allows the API to return immediately so the modal can close
|
||||
this.runFollowUpWork({
|
||||
projectPath,
|
||||
featureId,
|
||||
prompt,
|
||||
imagePaths,
|
||||
sendToRenderer,
|
||||
execution,
|
||||
}).catch((error) => {
|
||||
console.error("[AutoMode] Follow-up work error:", error);
|
||||
this.runningFeatures.delete(featureId);
|
||||
});
|
||||
|
||||
// Return immediately so the frontend can close the modal
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to run follow-up work asynchronously
|
||||
*/
|
||||
async runFollowUpWork({
|
||||
projectPath,
|
||||
featureId,
|
||||
prompt,
|
||||
imagePaths,
|
||||
sendToRenderer,
|
||||
execution,
|
||||
}) {
|
||||
try {
|
||||
// Load features
|
||||
const features = await featureLoader.loadFeatures(projectPath);
|
||||
const feature = features.find((f) => f.id === featureId);
|
||||
|
||||
if (!feature) {
|
||||
throw new Error(`Feature ${featureId} not found`);
|
||||
}
|
||||
|
||||
console.log(`[AutoMode] Following up on feature: ${feature.description}`);
|
||||
|
||||
// Update status to in_progress
|
||||
await featureLoader.updateFeatureStatus(
|
||||
featureId,
|
||||
"in_progress",
|
||||
projectPath
|
||||
);
|
||||
|
||||
sendToRenderer({
|
||||
type: "auto_mode_feature_start",
|
||||
featureId: feature.id,
|
||||
feature: feature,
|
||||
});
|
||||
|
||||
// Read existing context and append follow-up prompt
|
||||
const previousContext = await contextManager.readContextFile(
|
||||
projectPath,
|
||||
featureId
|
||||
);
|
||||
|
||||
// Append follow-up prompt to context
|
||||
const followUpContext = `${previousContext}\n\n## Follow-up Instructions\n\n${prompt}`;
|
||||
await contextManager.writeToContextFile(
|
||||
projectPath,
|
||||
featureId,
|
||||
`\n\n## Follow-up Instructions\n\n${prompt}`
|
||||
);
|
||||
|
||||
// Resume implementation with follow-up context and optional images
|
||||
const result = await featureExecutor.resumeFeatureWithContext(
|
||||
{ ...feature, followUpPrompt: prompt, followUpImages: imagePaths },
|
||||
projectPath,
|
||||
sendToRenderer,
|
||||
followUpContext,
|
||||
execution
|
||||
);
|
||||
|
||||
// For skipTests features, go to waiting_approval on success instead of verified
|
||||
const newStatus = result.passes
|
||||
? feature.skipTests
|
||||
? "waiting_approval"
|
||||
: "verified"
|
||||
: "in_progress";
|
||||
|
||||
await featureLoader.updateFeatureStatus(
|
||||
feature.id,
|
||||
newStatus,
|
||||
projectPath
|
||||
);
|
||||
|
||||
// Delete context file if verified (only for non-skipTests)
|
||||
if (newStatus === "verified") {
|
||||
await contextManager.deleteContextFile(projectPath, feature.id);
|
||||
}
|
||||
|
||||
sendToRenderer({
|
||||
type: "auto_mode_feature_complete",
|
||||
featureId: feature.id,
|
||||
passes: result.passes,
|
||||
message: result.message,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[AutoMode] Error in follow-up:", error);
|
||||
sendToRenderer({
|
||||
type: "auto_mode_error",
|
||||
error: error.message,
|
||||
featureId: featureId,
|
||||
});
|
||||
} finally {
|
||||
this.runningFeatures.delete(featureId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit changes for a feature without doing additional work
|
||||
* This marks the feature as verified and commits the changes
|
||||
*/
|
||||
async commitFeature({ projectPath, featureId, sendToRenderer }) {
|
||||
console.log(`[AutoMode] Committing feature: ${featureId}`);
|
||||
|
||||
// Register briefly as running for the commit operation
|
||||
const execution = this.createExecutionContext(featureId);
|
||||
execution.projectPath = projectPath;
|
||||
execution.sendToRenderer = sendToRenderer;
|
||||
this.runningFeatures.set(featureId, execution);
|
||||
|
||||
try {
|
||||
// Load feature to get description for commit message
|
||||
const features = await featureLoader.loadFeatures(projectPath);
|
||||
const feature = features.find((f) => f.id === featureId);
|
||||
|
||||
if (!feature) {
|
||||
throw new Error(`Feature ${featureId} not found`);
|
||||
}
|
||||
|
||||
sendToRenderer({
|
||||
type: "auto_mode_feature_start",
|
||||
featureId: feature.id,
|
||||
feature: { ...feature, description: "Committing changes..." },
|
||||
});
|
||||
|
||||
sendToRenderer({
|
||||
type: "auto_mode_phase",
|
||||
featureId,
|
||||
phase: "action",
|
||||
message: "Committing changes to git...",
|
||||
});
|
||||
|
||||
// Run git commit via the agent
|
||||
const commitResult = await featureExecutor.commitChangesOnly(
|
||||
feature,
|
||||
projectPath,
|
||||
sendToRenderer,
|
||||
execution
|
||||
);
|
||||
|
||||
// Update status to verified
|
||||
await featureLoader.updateFeatureStatus(
|
||||
featureId,
|
||||
"verified",
|
||||
projectPath
|
||||
);
|
||||
|
||||
// Delete context file
|
||||
await contextManager.deleteContextFile(projectPath, featureId);
|
||||
|
||||
sendToRenderer({
|
||||
type: "auto_mode_feature_complete",
|
||||
featureId: feature.id,
|
||||
passes: true,
|
||||
message: "Changes committed successfully",
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error("[AutoMode] Error committing feature:", error);
|
||||
sendToRenderer({
|
||||
type: "auto_mode_error",
|
||||
error: error.message,
|
||||
featureId: featureId,
|
||||
});
|
||||
throw error;
|
||||
} finally {
|
||||
this.runningFeatures.delete(featureId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep helper
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user