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:
Cody Seibert
2025-12-10 14:29:05 -05:00
parent d83eb86f22
commit c502fbc57a
26 changed files with 2497 additions and 298 deletions

View File

@@ -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 };
}
/**

View File

@@ -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: {

View File

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

View File

@@ -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;