mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 08:53: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:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user