mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 20:43:36 +00:00
feat(backup): add backup.json for feature tracking and status updates
- Introduced a new `backup.json` file to track feature statuses, descriptions, and summaries for better project management. - Updated `.automaker/feature_list.json` to reflect verified statuses for several features, ensuring accurate representation of progress. - Enhanced `memory.md` with details on drag-and-drop functionality for features in `waiting_approval` status. - Improved auto mode service to allow running tasks to complete when auto mode is stopped, enhancing user experience.
This commit is contained in:
@@ -128,10 +128,12 @@ class AutoModeService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop auto mode - stops the auto loop and all running features
|
||||
* Stop auto mode - stops the auto loop but lets running features complete
|
||||
* This only turns off the auto toggle to prevent picking up new features.
|
||||
* Running tasks will continue until they complete naturally.
|
||||
*/
|
||||
async stop() {
|
||||
console.log("[AutoMode] Stopping auto mode");
|
||||
console.log("[AutoMode] Stopping auto mode (letting running features complete)");
|
||||
|
||||
this.autoLoopRunning = false;
|
||||
|
||||
@@ -147,18 +149,15 @@ class AutoModeService {
|
||||
this.autoLoopAbortController = null;
|
||||
}
|
||||
|
||||
// Abort all running features
|
||||
for (const [featureId, execution] of this.runningFeatures.entries()) {
|
||||
console.log(`[AutoMode] Aborting feature: ${featureId}`);
|
||||
if (execution.abortController) {
|
||||
execution.abortController.abort();
|
||||
}
|
||||
}
|
||||
// NOTE: We intentionally do NOT abort running features here.
|
||||
// Stopping auto mode should only turn off the toggle to prevent new features
|
||||
// from being picked up. Running features will complete naturally.
|
||||
// Use stopFeature() to cancel a specific running feature if needed.
|
||||
|
||||
// Clear all running features
|
||||
this.runningFeatures.clear();
|
||||
const runningCount = this.runningFeatures.size;
|
||||
console.log(`[AutoMode] Auto loop stopped. ${runningCount} feature(s) still running and will complete.`);
|
||||
|
||||
return { success: true };
|
||||
return { success: true, runningFeatures: runningCount };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,8 +23,8 @@ contextBridge.exposeInMainWorld("electronAPI", {
|
||||
|
||||
// App APIs
|
||||
getPath: (name) => ipcRenderer.invoke("app:getPath", name),
|
||||
saveImageToTemp: (data, filename, mimeType) =>
|
||||
ipcRenderer.invoke("app:saveImageToTemp", { data, filename, mimeType }),
|
||||
saveImageToTemp: (data, filename, mimeType, projectPath) =>
|
||||
ipcRenderer.invoke("app:saveImageToTemp", { data, filename, mimeType, projectPath }),
|
||||
|
||||
// Agent APIs
|
||||
agent: {
|
||||
|
||||
@@ -200,6 +200,201 @@ This helps future agent runs avoid the same pitfalls.
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the initial git state before a feature starts executing
|
||||
* This captures all files that were already modified before the AI agent started
|
||||
* @param {string} projectPath - Path to the project
|
||||
* @param {string} featureId - Feature ID
|
||||
* @returns {Promise<{modifiedFiles: string[], untrackedFiles: string[]}>}
|
||||
*/
|
||||
async saveInitialGitState(projectPath, featureId) {
|
||||
if (!projectPath) return { modifiedFiles: [], untrackedFiles: [] };
|
||||
|
||||
try {
|
||||
const { execSync } = require("child_process");
|
||||
const contextDir = path.join(projectPath, ".automaker", "agents-context");
|
||||
|
||||
// Ensure directory exists
|
||||
try {
|
||||
await fs.access(contextDir);
|
||||
} catch {
|
||||
await fs.mkdir(contextDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Get list of modified files (both staged and unstaged)
|
||||
let modifiedFiles = [];
|
||||
try {
|
||||
const modifiedOutput = execSync("git diff --name-only HEAD", {
|
||||
cwd: projectPath,
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
if (modifiedOutput) {
|
||||
modifiedFiles = modifiedOutput.split("\n").filter(Boolean);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ContextManager] No modified files or git error:", error.message);
|
||||
}
|
||||
|
||||
// Get list of untracked files
|
||||
let untrackedFiles = [];
|
||||
try {
|
||||
const untrackedOutput = execSync("git ls-files --others --exclude-standard", {
|
||||
cwd: projectPath,
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
if (untrackedOutput) {
|
||||
untrackedFiles = untrackedOutput.split("\n").filter(Boolean);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ContextManager] Error getting untracked files:", error.message);
|
||||
}
|
||||
|
||||
// Save the initial state to a JSON file
|
||||
const stateFile = path.join(contextDir, `${featureId}-git-state.json`);
|
||||
const state = {
|
||||
timestamp: new Date().toISOString(),
|
||||
modifiedFiles,
|
||||
untrackedFiles,
|
||||
};
|
||||
|
||||
await fs.writeFile(stateFile, JSON.stringify(state, null, 2), "utf-8");
|
||||
console.log(`[ContextManager] Saved initial git state for ${featureId}:`, {
|
||||
modifiedCount: modifiedFiles.length,
|
||||
untrackedCount: untrackedFiles.length,
|
||||
});
|
||||
|
||||
return state;
|
||||
} catch (error) {
|
||||
console.error("[ContextManager] Failed to save initial git state:", error);
|
||||
return { modifiedFiles: [], untrackedFiles: [] };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the initial git state saved before a feature started executing
|
||||
* @param {string} projectPath - Path to the project
|
||||
* @param {string} featureId - Feature ID
|
||||
* @returns {Promise<{modifiedFiles: string[], untrackedFiles: string[], timestamp: string} | null>}
|
||||
*/
|
||||
async getInitialGitState(projectPath, featureId) {
|
||||
if (!projectPath) return null;
|
||||
|
||||
try {
|
||||
const stateFile = path.join(
|
||||
projectPath,
|
||||
".automaker",
|
||||
"agents-context",
|
||||
`${featureId}-git-state.json`
|
||||
);
|
||||
const content = await fs.readFile(stateFile, "utf-8");
|
||||
return JSON.parse(content);
|
||||
} catch (error) {
|
||||
console.log(`[ContextManager] No initial git state found for ${featureId}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the git state file for a feature
|
||||
* @param {string} projectPath - Path to the project
|
||||
* @param {string} featureId - Feature ID
|
||||
*/
|
||||
async deleteGitStateFile(projectPath, featureId) {
|
||||
if (!projectPath) return;
|
||||
|
||||
try {
|
||||
const stateFile = path.join(
|
||||
projectPath,
|
||||
".automaker",
|
||||
"agents-context",
|
||||
`${featureId}-git-state.json`
|
||||
);
|
||||
await fs.unlink(stateFile);
|
||||
console.log(`[ContextManager] Deleted git state file for ${featureId}`);
|
||||
} catch (error) {
|
||||
// File might not exist, which is fine
|
||||
if (error.code !== "ENOENT") {
|
||||
console.error("[ContextManager] Failed to delete git state file:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate which files were changed during the AI session
|
||||
* by comparing current git state with the saved initial state
|
||||
* @param {string} projectPath - Path to the project
|
||||
* @param {string} featureId - Feature ID
|
||||
* @returns {Promise<{newFiles: string[], modifiedFiles: string[]}>}
|
||||
*/
|
||||
async getFilesChangedDuringSession(projectPath, featureId) {
|
||||
if (!projectPath) return { newFiles: [], modifiedFiles: [] };
|
||||
|
||||
try {
|
||||
const { execSync } = require("child_process");
|
||||
|
||||
// Get initial state
|
||||
const initialState = await this.getInitialGitState(projectPath, featureId);
|
||||
|
||||
// Get current state
|
||||
let currentModified = [];
|
||||
try {
|
||||
const modifiedOutput = execSync("git diff --name-only HEAD", {
|
||||
cwd: projectPath,
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
if (modifiedOutput) {
|
||||
currentModified = modifiedOutput.split("\n").filter(Boolean);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ContextManager] No modified files or git error");
|
||||
}
|
||||
|
||||
let currentUntracked = [];
|
||||
try {
|
||||
const untrackedOutput = execSync("git ls-files --others --exclude-standard", {
|
||||
cwd: projectPath,
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
if (untrackedOutput) {
|
||||
currentUntracked = untrackedOutput.split("\n").filter(Boolean);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("[ContextManager] Error getting untracked files");
|
||||
}
|
||||
|
||||
if (!initialState) {
|
||||
// No initial state - all current changes are considered from this session
|
||||
console.log("[ContextManager] No initial state found, returning all current changes");
|
||||
return {
|
||||
newFiles: currentUntracked,
|
||||
modifiedFiles: currentModified,
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate files that are new since the session started
|
||||
const initialModifiedSet = new Set(initialState.modifiedFiles || []);
|
||||
const initialUntrackedSet = new Set(initialState.untrackedFiles || []);
|
||||
|
||||
// New files = current untracked - initial untracked
|
||||
const newFiles = currentUntracked.filter(f => !initialUntrackedSet.has(f));
|
||||
|
||||
// Modified files = current modified - initial modified
|
||||
const modifiedFiles = currentModified.filter(f => !initialModifiedSet.has(f));
|
||||
|
||||
console.log(`[ContextManager] Files changed during session for ${featureId}:`, {
|
||||
newFilesCount: newFiles.length,
|
||||
modifiedFilesCount: modifiedFiles.length,
|
||||
newFiles,
|
||||
modifiedFiles,
|
||||
});
|
||||
|
||||
return { newFiles, modifiedFiles };
|
||||
} catch (error) {
|
||||
console.error("[ContextManager] Failed to calculate changed files:", error);
|
||||
return { newFiles: [], modifiedFiles: [] };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ContextManager();
|
||||
|
||||
@@ -193,6 +193,10 @@ class FeatureExecutor {
|
||||
let isCodex;
|
||||
|
||||
try {
|
||||
// Save the initial git state before starting implementation
|
||||
// This allows us to track only files changed during this session when committing
|
||||
await contextManager.saveInitialGitState(projectPath, feature.id);
|
||||
|
||||
// ========================================
|
||||
// PHASE 1: PLANNING
|
||||
// ========================================
|
||||
@@ -1062,6 +1066,23 @@ class FeatureExecutor {
|
||||
content: "Analyzing changes and creating commit...",
|
||||
});
|
||||
|
||||
// Get the files that were changed during this AI session
|
||||
const changedFiles = await contextManager.getFilesChangedDuringSession(
|
||||
projectPath,
|
||||
feature.id
|
||||
);
|
||||
|
||||
// Combine new files and modified files into a single list of files to commit
|
||||
const sessionFiles = [
|
||||
...changedFiles.newFiles,
|
||||
...changedFiles.modifiedFiles,
|
||||
];
|
||||
|
||||
console.log(
|
||||
`[FeatureExecutor] Files changed during session: ${sessionFiles.length}`,
|
||||
sessionFiles
|
||||
);
|
||||
|
||||
const abortController = new AbortController();
|
||||
execution.abortController = abortController;
|
||||
|
||||
@@ -1080,7 +1101,9 @@ IMPORTANT RULES:
|
||||
- DO NOT write tests
|
||||
- DO NOT do anything except analyzing changes and committing them
|
||||
- Use the git command line tools via Bash
|
||||
- Create proper conventional commit messages based on what was actually changed`,
|
||||
- Create proper conventional commit messages based on what was actually changed
|
||||
- ONLY commit the specific files that were changed during the AI session (provided in the prompt)
|
||||
- DO NOT use 'git add .' - only add the specific files listed`,
|
||||
maxTurns: 15, // Allow some turns to analyze and commit
|
||||
cwd: projectPath,
|
||||
mcpServers: {
|
||||
@@ -1094,25 +1117,44 @@ IMPORTANT RULES:
|
||||
abortController: abortController,
|
||||
};
|
||||
|
||||
// Build the file list section for the prompt
|
||||
let fileListSection = "";
|
||||
if (sessionFiles.length > 0) {
|
||||
fileListSection = `
|
||||
**Files Changed During This AI Session:**
|
||||
The following files were modified or created during this feature implementation:
|
||||
${sessionFiles.map((f) => `- ${f}`).join("\n")}
|
||||
|
||||
**CRITICAL:** Only commit these specific files listed above. Do NOT use \`git add .\` or \`git add -A\`.
|
||||
Instead, add each file individually or use: \`git add ${sessionFiles.map((f) => `"${f}"`).join(" ")}\`
|
||||
`;
|
||||
} else {
|
||||
fileListSection = `
|
||||
**Note:** No specific files were tracked for this session. Please run \`git status\` to see what files have been modified, and only stage files that appear to be related to this feature implementation. Be conservative - if a file doesn't seem related to this feature, don't include it.
|
||||
`;
|
||||
}
|
||||
|
||||
// Prompt that guides the agent to create a proper conventional commit
|
||||
const prompt = `Please commit the current changes with a proper conventional commit message.
|
||||
const prompt = `Please commit the changes for this feature with a proper conventional commit message.
|
||||
|
||||
**Feature Context:**
|
||||
Category: ${feature.category}
|
||||
Description: ${feature.description}
|
||||
|
||||
${fileListSection}
|
||||
**Your Task:**
|
||||
|
||||
1. First, run \`git status\` to see all untracked and modified files
|
||||
2. Run \`git diff\` to see the actual changes (both staged and unstaged)
|
||||
1. First, run \`git status\` to see the current state of the repository
|
||||
2. Run \`git diff\` on the specific files listed above to see the actual changes
|
||||
3. Run \`git log --oneline -5\` to see recent commit message styles in this repo
|
||||
4. Analyze all the changes and draft a proper conventional commit message:
|
||||
4. Analyze the changes in the files and draft a proper conventional commit message:
|
||||
- Use conventional commit format: \`type(scope): description\`
|
||||
- Types: feat, fix, refactor, style, docs, test, chore
|
||||
- The description should be concise (under 72 chars) and focus on "what" was done
|
||||
- Summarize the nature of the changes (new feature, enhancement, bug fix, etc.)
|
||||
- Make sure the commit message accurately reflects the actual code changes
|
||||
5. Run \`git add .\` to stage all changes
|
||||
5. Stage ONLY the specific files that were changed during this session (listed above)
|
||||
- DO NOT use \`git add .\` or \`git add -A\`
|
||||
- Add files individually: \`git add "path/to/file1" "path/to/file2"\`
|
||||
6. Create the commit with a message ending with:
|
||||
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||
|
||||
@@ -1136,7 +1178,8 @@ EOF
|
||||
- DO NOT use the feature description verbatim as the commit message
|
||||
- Analyze the actual code changes to determine the appropriate commit message
|
||||
- The commit message should be professional and follow conventional commit standards
|
||||
- DO NOT modify any code or run tests - ONLY commit the existing changes`;
|
||||
- DO NOT modify any code or run tests - ONLY commit the existing changes
|
||||
- ONLY stage the specific files listed above - do not commit unrelated changes`;
|
||||
|
||||
const currentQuery = query({ prompt, options });
|
||||
execution.query = currentQuery;
|
||||
|
||||
Reference in New Issue
Block a user