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

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