feat(feature-list-protection): implement multi-layered protection strategy for feature list

- Removed the `FEATURE_LIST_PROTECTION.md` documentation file as the protection strategy has been fully implemented.
- Updated the `.automaker/feature_list.json` to an empty array to prevent accidental data loss.
- Enhanced the context management and prompt generation to include critical warnings and memory content for agents.
- Added validation, auto-backup, and access control mechanisms to safeguard the feature list from direct modifications.

These changes ensure robust protection against data loss and improve the overall safety of feature management within the application.
This commit is contained in:
Cody Seibert
2025-12-10 11:24:07 -05:00
parent a28d2f5cfe
commit 5e418984ad
9 changed files with 533 additions and 477 deletions

View File

@@ -40,7 +40,12 @@ class ContextManager {
*/
async readContextFile(projectPath, featureId) {
try {
const contextPath = path.join(projectPath, ".automaker", "agents-context", `${featureId}.md`);
const contextPath = path.join(
projectPath,
".automaker",
"agents-context",
`${featureId}.md`
);
const content = await fs.readFile(contextPath, "utf-8");
return content;
} catch (error) {
@@ -56,16 +61,145 @@ class ContextManager {
if (!projectPath) return;
try {
const contextPath = path.join(projectPath, ".automaker", "agents-context", `${featureId}.md`);
const contextPath = path.join(
projectPath,
".automaker",
"agents-context",
`${featureId}.md`
);
await fs.unlink(contextPath);
console.log(`[ContextManager] Deleted agent context for feature ${featureId}`);
console.log(
`[ContextManager] Deleted agent context for feature ${featureId}`
);
} catch (error) {
// File might not exist, which is fine
if (error.code !== 'ENOENT') {
if (error.code !== "ENOENT") {
console.error("[ContextManager] Failed to delete context file:", error);
}
}
}
/**
* Read the memory.md file containing lessons learned and common issues
* Returns formatted string to inject into prompts
*/
async getMemoryContent(projectPath) {
if (!projectPath) return "";
try {
const memoryPath = path.join(projectPath, ".automaker", "memory.md");
// Check if file exists
try {
await fs.access(memoryPath);
} catch {
// File doesn't exist, return empty string
return "";
}
const content = await fs.readFile(memoryPath, "utf-8");
if (!content.trim()) {
return "";
}
return `
**🧠 Agent Memory - Previous Lessons Learned:**
The following memory file contains lessons learned from previous agent runs, including common issues and their solutions. Review this carefully to avoid repeating past mistakes.
<agent-memory>
${content}
</agent-memory>
**IMPORTANT:** If you encounter a new issue that took significant debugging effort to resolve, add it to the memory file at \`.automaker/memory.md\` in a concise format:
- Issue title
- Problem description (1-2 sentences)
- Solution/fix (with code example if helpful)
This helps future agent runs avoid the same pitfalls.
`;
} catch (error) {
console.error("[ContextManager] Failed to read memory file:", error);
return "";
}
}
/**
* List context files from .automaker/context/ directory and get previews
* Returns a formatted string with file names and first 50 lines of each file
*/
async getContextFilesPreview(projectPath) {
if (!projectPath) return "";
try {
const contextDir = path.join(projectPath, ".automaker", "context");
// Check if directory exists
try {
await fs.access(contextDir);
} catch {
// Directory doesn't exist, return empty string
return "";
}
// Read directory contents
const entries = await fs.readdir(contextDir, { withFileTypes: true });
const files = entries
.filter((entry) => entry.isFile())
.map((entry) => entry.name)
.sort();
if (files.length === 0) {
return "";
}
// Build preview string
const previews = [];
previews.push(`\n**📁 Context Files Available:**\n`);
previews.push(
`The following context files are available in \`.automaker/context/\` directory.`
);
previews.push(
`These files contain additional context that may be relevant to your work.`
);
previews.push(
`You can read them in full using the Read tool if needed.\n`
);
for (const fileName of files) {
try {
const filePath = path.join(contextDir, fileName);
const content = await fs.readFile(filePath, "utf-8");
const lines = content.split("\n");
const previewLines = lines.slice(0, 50);
const preview = previewLines.join("\n");
const hasMore = lines.length > 50;
previews.push(`\n**File: ${fileName}**`);
if (hasMore) {
previews.push(
`(Showing first 50 of ${lines.length} lines - use Read tool to see full content)`
);
}
previews.push(`\`\`\``);
previews.push(preview);
previews.push(`\`\`\`\n`);
} catch (error) {
console.error(
`[ContextManager] Failed to read context file ${fileName}:`,
error
);
previews.push(`\n**File: ${fileName}** (Error reading file)\n`);
}
}
return previews.join("\n");
} catch (error) {
console.error("[ContextManager] Failed to list context files:", error);
return "";
}
}
}
module.exports = new ContextManager();

View File

@@ -27,7 +27,11 @@ class FeatureExecutor {
// PHASE 1: PLANNING
// ========================================
const planningMessage = `📋 Planning implementation for: ${feature.description}\n`;
await contextManager.writeToContextFile(projectPath, feature.id, planningMessage);
await contextManager.writeToContextFile(
projectPath,
feature.id,
planningMessage
);
sendToRenderer({
type: "auto_mode_phase",
@@ -35,7 +39,9 @@ class FeatureExecutor {
phase: "planning",
message: `Planning implementation for: ${feature.description}`,
});
console.log(`[FeatureExecutor] Phase: PLANNING for ${feature.description}`);
console.log(
`[FeatureExecutor] Phase: PLANNING for ${feature.description}`
);
const abortController = new AbortController();
execution.abortController = abortController;
@@ -46,14 +52,17 @@ class FeatureExecutor {
projectPath
);
// Determine if we're in TDD mode (skipTests=false means TDD mode)
const isTDD = !feature.skipTests;
// Configure options for the SDK query
const options = {
model: "claude-opus-4-5-20251101",
systemPrompt: promptBuilder.getCodingPrompt(),
systemPrompt: await promptBuilder.getCodingPrompt(projectPath, isTDD),
maxTurns: 1000,
cwd: projectPath,
mcpServers: {
"automaker-tools": featureToolsServer
"automaker-tools": featureToolsServer,
},
allowedTools: [
"Read",
@@ -75,7 +84,7 @@ class FeatureExecutor {
};
// Build the prompt for this specific feature
let prompt = promptBuilder.buildFeaturePrompt(feature);
let prompt = await promptBuilder.buildFeaturePrompt(feature, projectPath);
// Add images to prompt if feature has imagePaths
if (feature.imagePaths && feature.imagePaths.length > 0) {
@@ -103,7 +112,8 @@ class FeatureExecutor {
".gif": "image/gif",
".webp": "image/webp",
};
const mediaType = mimeTypeMap[ext] || imagePathObj.mimeType || "image/png";
const mediaType =
mimeTypeMap[ext] || imagePathObj.mimeType || "image/png";
contentBlocks.push({
type: "image",
@@ -114,7 +124,9 @@ class FeatureExecutor {
},
});
console.log(`[FeatureExecutor] Added image to prompt: ${imagePath}`);
console.log(
`[FeatureExecutor] Added image to prompt: ${imagePath}`
);
} catch (error) {
console.error(
`[FeatureExecutor] Failed to load image ${imagePathObj.path}:`,
@@ -142,7 +154,11 @@ class FeatureExecutor {
// PHASE 2: ACTION
// ========================================
const actionMessage = `⚡ Executing implementation for: ${feature.description}\n`;
await contextManager.writeToContextFile(projectPath, feature.id, actionMessage);
await contextManager.writeToContextFile(
projectPath,
feature.id,
actionMessage
);
sendToRenderer({
type: "auto_mode_phase",
@@ -169,7 +185,11 @@ class FeatureExecutor {
responseText += block.text;
// Write to context file
await contextManager.writeToContextFile(projectPath, feature.id, block.text);
await contextManager.writeToContextFile(
projectPath,
feature.id,
block.text
);
// Stream progress to renderer
sendToRenderer({
@@ -182,7 +202,11 @@ class FeatureExecutor {
if (!hasStartedToolUse) {
hasStartedToolUse = true;
const startMsg = "Starting code implementation...\n";
await contextManager.writeToContextFile(projectPath, feature.id, startMsg);
await contextManager.writeToContextFile(
projectPath,
feature.id,
startMsg
);
sendToRenderer({
type: "auto_mode_progress",
featureId: feature.id,
@@ -192,7 +216,11 @@ class FeatureExecutor {
// Write tool use to context file
const toolMsg = `\n🔧 Tool: ${block.name}\n`;
await contextManager.writeToContextFile(projectPath, feature.id, toolMsg);
await contextManager.writeToContextFile(
projectPath,
feature.id,
toolMsg
);
// Notify about tool use
sendToRenderer({
@@ -213,7 +241,11 @@ class FeatureExecutor {
// PHASE 3: VERIFICATION
// ========================================
const verificationMessage = `✅ Verifying implementation for: ${feature.description}\n`;
await contextManager.writeToContextFile(projectPath, feature.id, verificationMessage);
await contextManager.writeToContextFile(
projectPath,
feature.id,
verificationMessage
);
sendToRenderer({
type: "auto_mode_phase",
@@ -221,11 +253,17 @@ class FeatureExecutor {
phase: "verification",
message: `Verifying implementation for: ${feature.description}`,
});
console.log(`[FeatureExecutor] Phase: VERIFICATION for ${feature.description}`);
console.log(
`[FeatureExecutor] Phase: VERIFICATION for ${feature.description}`
);
const checkingMsg =
"Verifying implementation and checking test results...\n";
await contextManager.writeToContextFile(projectPath, feature.id, checkingMsg);
await contextManager.writeToContextFile(
projectPath,
feature.id,
checkingMsg
);
sendToRenderer({
type: "auto_mode_progress",
featureId: feature.id,
@@ -236,15 +274,21 @@ class FeatureExecutor {
const updatedFeatures = await featureLoader.loadFeatures(projectPath);
const updatedFeature = updatedFeatures.find((f) => f.id === feature.id);
// For skipTests features, waiting_approval is also considered a success
const passes = updatedFeature?.status === "verified" ||
(updatedFeature?.skipTests && updatedFeature?.status === "waiting_approval");
const passes =
updatedFeature?.status === "verified" ||
(updatedFeature?.skipTests &&
updatedFeature?.status === "waiting_approval");
// Send verification result
const resultMsg = passes
? "✓ Verification successful: All tests passed\n"
: "✗ Verification: Tests need attention\n";
await contextManager.writeToContextFile(projectPath, feature.id, resultMsg);
await contextManager.writeToContextFile(
projectPath,
feature.id,
resultMsg
);
sendToRenderer({
type: "auto_mode_progress",
featureId: feature.id,
@@ -283,12 +327,24 @@ class FeatureExecutor {
/**
* Resume feature implementation with previous context
*/
async resumeFeatureWithContext(feature, projectPath, sendToRenderer, previousContext, execution) {
console.log(`[FeatureExecutor] Resuming with context for: ${feature.description}`);
async resumeFeatureWithContext(
feature,
projectPath,
sendToRenderer,
previousContext,
execution
) {
console.log(
`[FeatureExecutor] Resuming with context for: ${feature.description}`
);
try {
const resumeMessage = `\n🔄 Resuming implementation for: ${feature.description}\n`;
await contextManager.writeToContextFile(projectPath, feature.id, resumeMessage);
await contextManager.writeToContextFile(
projectPath,
feature.id,
resumeMessage
);
sendToRenderer({
type: "auto_mode_phase",
@@ -300,6 +356,9 @@ class FeatureExecutor {
const abortController = new AbortController();
execution.abortController = abortController;
// Determine if we're in TDD mode (skipTests=false means TDD mode)
const isTDD = !feature.skipTests;
// Create custom MCP server with UpdateFeatureStatus tool
const featureToolsServer = mcpServerFactory.createFeatureToolsServer(
featureLoader.updateFeatureStatus.bind(featureLoader),
@@ -308,13 +367,23 @@ class FeatureExecutor {
const options = {
model: "claude-opus-4-5-20251101",
systemPrompt: promptBuilder.getVerificationPrompt(),
systemPrompt: await promptBuilder.getVerificationPrompt(projectPath, isTDD),
maxTurns: 1000,
cwd: projectPath,
mcpServers: {
"automaker-tools": featureToolsServer
"automaker-tools": featureToolsServer,
},
allowedTools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash", "WebSearch", "WebFetch", "mcp__automaker-tools__UpdateFeatureStatus"],
allowedTools: [
"Read",
"Write",
"Edit",
"Glob",
"Grep",
"Bash",
"WebSearch",
"WebFetch",
"mcp__automaker-tools__UpdateFeatureStatus",
],
permissionMode: "acceptEdits",
sandbox: {
enabled: true,
@@ -324,7 +393,11 @@ class FeatureExecutor {
};
// Build prompt with previous context
let prompt = promptBuilder.buildResumePrompt(feature, previousContext);
let prompt = await promptBuilder.buildResumePrompt(
feature,
previousContext,
projectPath
);
// Add images to prompt if feature has imagePaths or followUpImages
const imagePaths = feature.followUpImages || feature.imagePaths;
@@ -343,7 +416,10 @@ class FeatureExecutor {
for (const imagePathObj of imagePaths) {
try {
// Handle both string paths and FeatureImagePath objects
const imagePath = typeof imagePathObj === 'string' ? imagePathObj : imagePathObj.path;
const imagePath =
typeof imagePathObj === "string"
? imagePathObj
: imagePathObj.path;
const imageBuffer = fs.readFileSync(imagePath);
const base64Data = imageBuffer.toString("base64");
const ext = path.extname(imagePath).toLowerCase();
@@ -354,9 +430,10 @@ class FeatureExecutor {
".gif": "image/gif",
".webp": "image/webp",
};
const mediaType = typeof imagePathObj === 'string'
? (mimeTypeMap[ext] || "image/png")
: (mimeTypeMap[ext] || imagePathObj.mimeType || "image/png");
const mediaType =
typeof imagePathObj === "string"
? mimeTypeMap[ext] || "image/png"
: mimeTypeMap[ext] || imagePathObj.mimeType || "image/png";
contentBlocks.push({
type: "image",
@@ -367,9 +444,14 @@ class FeatureExecutor {
},
});
console.log(`[FeatureExecutor] Added image to resume prompt: ${imagePath}`);
console.log(
`[FeatureExecutor] Added image to resume prompt: ${imagePath}`
);
} catch (error) {
const errorPath = typeof imagePathObj === 'string' ? imagePathObj : imagePathObj.path;
const errorPath =
typeof imagePathObj === "string"
? imagePathObj
: imagePathObj.path;
console.error(
`[FeatureExecutor] Failed to load image ${errorPath}:`,
error
@@ -394,7 +476,11 @@ class FeatureExecutor {
if (block.type === "text") {
responseText += block.text;
await contextManager.writeToContextFile(projectPath, feature.id, block.text);
await contextManager.writeToContextFile(
projectPath,
feature.id,
block.text
);
sendToRenderer({
type: "auto_mode_progress",
@@ -403,7 +489,11 @@ class FeatureExecutor {
});
} else if (block.type === "tool_use") {
const toolMsg = `\n🔧 Tool: ${block.name}\n`;
await contextManager.writeToContextFile(projectPath, feature.id, toolMsg);
await contextManager.writeToContextFile(
projectPath,
feature.id,
toolMsg
);
sendToRenderer({
type: "auto_mode_tool",
@@ -423,14 +513,20 @@ class FeatureExecutor {
const updatedFeatures = await featureLoader.loadFeatures(projectPath);
const updatedFeature = updatedFeatures.find((f) => f.id === feature.id);
// For skipTests features, waiting_approval is also considered a success
const passes = updatedFeature?.status === "verified" ||
(updatedFeature?.skipTests && updatedFeature?.status === "waiting_approval");
const passes =
updatedFeature?.status === "verified" ||
(updatedFeature?.skipTests &&
updatedFeature?.status === "waiting_approval");
const finalMsg = passes
? "✓ Feature successfully verified and completed\n"
: "⚠ Feature still in progress - may need additional work\n";
await contextManager.writeToContextFile(projectPath, feature.id, finalMsg);
await contextManager.writeToContextFile(
projectPath,
feature.id,
finalMsg
);
sendToRenderer({
type: "auto_mode_progress",
@@ -469,11 +565,17 @@ class FeatureExecutor {
* Analyzes changes and creates a proper conventional commit message
*/
async commitChangesOnly(feature, projectPath, sendToRenderer, execution) {
console.log(`[FeatureExecutor] Committing changes for: ${feature.description}`);
console.log(
`[FeatureExecutor] Committing changes for: ${feature.description}`
);
try {
const commitMessage = `\n📝 Committing changes for: ${feature.description}\n`;
await contextManager.writeToContextFile(projectPath, feature.id, commitMessage);
await contextManager.writeToContextFile(
projectPath,
feature.id,
commitMessage
);
sendToRenderer({
type: "auto_mode_progress",
@@ -503,7 +605,7 @@ IMPORTANT RULES:
maxTurns: 15, // Allow some turns to analyze and commit
cwd: projectPath,
mcpServers: {
"automaker-tools": featureToolsServer
"automaker-tools": featureToolsServer,
},
allowedTools: ["Bash", "mcp__automaker-tools__UpdateFeatureStatus"],
permissionMode: "acceptEdits",
@@ -569,7 +671,11 @@ EOF
if (block.type === "text") {
responseText += block.text;
await contextManager.writeToContextFile(projectPath, feature.id, block.text);
await contextManager.writeToContextFile(
projectPath,
feature.id,
block.text
);
sendToRenderer({
type: "auto_mode_progress",
@@ -578,7 +684,11 @@ EOF
});
} else if (block.type === "tool_use") {
const toolMsg = `\n🔧 Tool: ${block.name}\n`;
await contextManager.writeToContextFile(projectPath, feature.id, toolMsg);
await contextManager.writeToContextFile(
projectPath,
feature.id,
toolMsg
);
sendToRenderer({
type: "auto_mode_tool",
@@ -595,7 +705,11 @@ EOF
execution.abortController = null;
const finalMsg = "✓ Changes committed successfully\n";
await contextManager.writeToContextFile(projectPath, feature.id, finalMsg);
await contextManager.writeToContextFile(
projectPath,
feature.id,
finalMsg
);
sendToRenderer({
type: "auto_mode_progress",

View File

@@ -12,11 +12,17 @@ class FeatureVerifier {
* Verify feature tests (runs tests and checks if they pass)
*/
async verifyFeatureTests(feature, projectPath, sendToRenderer, execution) {
console.log(`[FeatureVerifier] Verifying tests for: ${feature.description}`);
console.log(
`[FeatureVerifier] Verifying tests for: ${feature.description}`
);
try {
const verifyMsg = `\n✅ Verifying tests for: ${feature.description}\n`;
await contextManager.writeToContextFile(projectPath, feature.id, verifyMsg);
await contextManager.writeToContextFile(
projectPath,
feature.id,
verifyMsg
);
sendToRenderer({
type: "auto_mode_phase",
@@ -36,13 +42,21 @@ class FeatureVerifier {
const options = {
model: "claude-opus-4-5-20251101",
systemPrompt: promptBuilder.getVerificationPrompt(),
systemPrompt: await promptBuilder.getVerificationPrompt(projectPath),
maxTurns: 1000,
cwd: projectPath,
mcpServers: {
"automaker-tools": featureToolsServer
"automaker-tools": featureToolsServer,
},
allowedTools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash", "mcp__automaker-tools__UpdateFeatureStatus"],
allowedTools: [
"Read",
"Write",
"Edit",
"Glob",
"Grep",
"Bash",
"mcp__automaker-tools__UpdateFeatureStatus",
],
permissionMode: "acceptEdits",
sandbox: {
enabled: true,
@@ -51,11 +65,18 @@ class FeatureVerifier {
abortController: abortController,
};
const prompt = promptBuilder.buildVerificationPrompt(feature);
const prompt = await promptBuilder.buildVerificationPrompt(
feature,
projectPath
);
const runningTestsMsg =
"Running Playwright tests to verify feature implementation...\n";
await contextManager.writeToContextFile(projectPath, feature.id, runningTestsMsg);
await contextManager.writeToContextFile(
projectPath,
feature.id,
runningTestsMsg
);
sendToRenderer({
type: "auto_mode_progress",
@@ -76,7 +97,11 @@ class FeatureVerifier {
if (block.type === "text") {
responseText += block.text;
await contextManager.writeToContextFile(projectPath, feature.id, block.text);
await contextManager.writeToContextFile(
projectPath,
feature.id,
block.text
);
sendToRenderer({
type: "auto_mode_progress",
@@ -85,7 +110,11 @@ class FeatureVerifier {
});
} else if (block.type === "tool_use") {
const toolMsg = `\n🔧 Tool: ${block.name}\n`;
await contextManager.writeToContextFile(projectPath, feature.id, toolMsg);
await contextManager.writeToContextFile(
projectPath,
feature.id,
toolMsg
);
sendToRenderer({
type: "auto_mode_tool",
@@ -105,14 +134,20 @@ class FeatureVerifier {
const updatedFeatures = await featureLoader.loadFeatures(projectPath);
const updatedFeature = updatedFeatures.find((f) => f.id === feature.id);
// For skipTests features, waiting_approval is also considered a success
const passes = updatedFeature?.status === "verified" ||
(updatedFeature?.skipTests && updatedFeature?.status === "waiting_approval");
const passes =
updatedFeature?.status === "verified" ||
(updatedFeature?.skipTests &&
updatedFeature?.status === "waiting_approval");
const finalMsg = passes
? "✓ Verification successful: All tests passed\n"
: "✗ Tests failed or not all passing - feature remains in progress\n";
await contextManager.writeToContextFile(projectPath, feature.id, finalMsg);
await contextManager.writeToContextFile(
projectPath,
feature.id,
finalMsg
);
sendToRenderer({
type: "auto_mode_progress",

View File

@@ -1,3 +1,5 @@
const contextManager = require("./context-manager");
/**
* Prompt Builder - Generates prompts for different agent tasks
*/
@@ -5,16 +7,21 @@ class PromptBuilder {
/**
* Build the prompt for implementing a specific feature
*/
buildFeaturePrompt(feature) {
async buildFeaturePrompt(feature, projectPath) {
const skipTestsNote = feature.skipTests
? `\n**⚠️ IMPORTANT - Manual Testing Mode:**\nThis feature has skipTests=true, which means:\n- DO NOT commit changes automatically\n- DO NOT mark as verified - it will automatically go to "waiting_approval" status\n- The user will manually review and commit the changes\n- Just implement the feature and mark it as verified (it will be converted to waiting_approval)\n`
: "";
let imagesNote = "";
if (feature.imagePaths && feature.imagePaths.length > 0) {
const imagesList = feature.imagePaths.map((img, idx) =>
` ${idx + 1}. ${img.filename} (${img.mimeType})\n Path: ${img.path}`
).join("\n");
const imagesList = feature.imagePaths
.map(
(img, idx) =>
` ${idx + 1}. ${img.filename} (${img.mimeType})\n Path: ${
img.path
}`
)
.join("\n");
imagesNote = `\n**📎 Context Images Attached:**\nThe user has attached ${feature.imagePaths.length} image(s) for context. These images are provided both visually (in the initial message) and as files you can read:
@@ -23,14 +30,31 @@ ${imagesList}
You can use the Read tool to view these images at any time during implementation. Review them carefully before implementing.\n`;
}
// Get context files preview
const contextFilesPreview = await contextManager.getContextFilesPreview(
projectPath
);
// Get memory content (lessons learned from previous runs)
const memoryContent = await contextManager.getMemoryContent(projectPath);
// Build mode header for this feature
const modeHeader = feature.skipTests
? `**🔨 MODE: Manual Review (No Automated Tests)**
This feature is set for manual review - focus on clean implementation without automated tests.`
: `**🧪 MODE: Test-Driven Development (TDD)**
This feature requires automated Playwright tests to verify the implementation.`;
return `You are working on a feature implementation task.
${modeHeader}
${memoryContent}
**Current Feature to Implement:**
ID: ${feature.id}
Category: ${feature.category}
Description: ${feature.description}
${skipTestsNote}${imagesNote}
${skipTestsNote}${imagesNote}${contextFilesPreview}
**Steps to Complete:**
${feature.steps.map((step, i) => `${i + 1}. ${step}`).join("\n")}
@@ -125,16 +149,21 @@ Begin by reading the project structure and then implementing the feature.`;
/**
* Build the prompt for verifying a specific feature
*/
buildVerificationPrompt(feature) {
async buildVerificationPrompt(feature, projectPath) {
const skipTestsNote = feature.skipTests
? `\n**⚠️ IMPORTANT - Manual Testing Mode:**\nThis feature has skipTests=true, which means:\n- DO NOT commit changes automatically\n- DO NOT mark as verified - it will automatically go to "waiting_approval" status\n- The user will manually review and commit the changes\n- Just implement the feature and mark it as verified (it will be converted to waiting_approval)\n`
: "";
let imagesNote = "";
if (feature.imagePaths && feature.imagePaths.length > 0) {
const imagesList = feature.imagePaths.map((img, idx) =>
` ${idx + 1}. ${img.filename} (${img.mimeType})\n Path: ${img.path}`
).join("\n");
const imagesList = feature.imagePaths
.map(
(img, idx) =>
` ${idx + 1}. ${img.filename} (${img.mimeType})\n Path: ${
img.path
}`
)
.join("\n");
imagesNote = `\n**📎 Context Images Attached:**\nThe user has attached ${feature.imagePaths.length} image(s) for context. These images are provided both visually (in the initial message) and as files you can read:
@@ -143,15 +172,33 @@ ${imagesList}
You can use the Read tool to view these images at any time during implementation. Review them carefully before implementing.\n`;
}
// Get context files preview
const contextFilesPreview = await contextManager.getContextFilesPreview(
projectPath
);
// Get memory content (lessons learned from previous runs)
const memoryContent = await contextManager.getMemoryContent(projectPath);
// Build mode header for this feature
const modeHeader = feature.skipTests
? `**🔨 MODE: Manual Review (No Automated Tests)**
This feature is set for manual review - focus on completing implementation without automated tests.`
: `**🧪 MODE: Test-Driven Development (TDD)**
This feature requires automated Playwright tests to verify the implementation.`;
return `You are implementing and verifying a feature until it is complete and working correctly.
${modeHeader}
${memoryContent}
**Feature to Implement/Verify:**
ID: ${feature.id}
Category: ${feature.category}
Description: ${feature.description}
Current Status: ${feature.status}
${skipTestsNote}${imagesNote}
${skipTestsNote}${imagesNote}${contextFilesPreview}
**Steps that should be implemented:**
${feature.steps.map((step, i) => `${i + 1}. ${step}`).join("\n")}
@@ -237,7 +284,7 @@ Begin by reading the project structure and understanding what needs to be implem
/**
* Build prompt for resuming feature with previous context
*/
buildResumePrompt(feature, previousContext) {
async buildResumePrompt(feature, previousContext, projectPath) {
const skipTestsNote = feature.skipTests
? `\n**⚠️ IMPORTANT - Manual Testing Mode:**\nThis feature has skipTests=true, which means:\n- DO NOT commit changes automatically\n- DO NOT mark as verified - it will automatically go to "waiting_approval" status\n- The user will manually review and commit the changes\n- Just implement the feature and mark it as verified (it will be converted to waiting_approval)\n`
: "";
@@ -246,13 +293,18 @@ Begin by reading the project structure and understanding what needs to be implem
const imagePaths = feature.followUpImages || feature.imagePaths;
let imagesNote = "";
if (imagePaths && imagePaths.length > 0) {
const imagesList = imagePaths.map((img, idx) => {
// Handle both FeatureImagePath objects and simple path strings
const path = typeof img === 'string' ? img : img.path;
const filename = typeof img === 'string' ? path.split('/').pop() : img.filename;
const mimeType = typeof img === 'string' ? 'image/*' : img.mimeType;
return ` ${idx + 1}. ${filename} (${mimeType})\n Path: ${path}`;
}).join("\n");
const imagesList = imagePaths
.map((img, idx) => {
// Handle both FeatureImagePath objects and simple path strings
const path = typeof img === "string" ? img : img.path;
const filename =
typeof img === "string" ? path.split("/").pop() : img.filename;
const mimeType = typeof img === "string" ? "image/*" : img.mimeType;
return ` ${
idx + 1
}. ${filename} (${mimeType})\n Path: ${path}`;
})
.join("\n");
imagesNote = `\n**📎 Context Images Attached:**\nThe user has attached ${imagePaths.length} image(s) for context. These images are provided both visually (in the initial message) and as files you can read:
@@ -261,14 +313,31 @@ ${imagesList}
You can use the Read tool to view these images at any time. Review them carefully.\n`;
}
// Get context files preview
const contextFilesPreview = await contextManager.getContextFilesPreview(
projectPath
);
// Get memory content (lessons learned from previous runs)
const memoryContent = await contextManager.getMemoryContent(projectPath);
// Build mode header for this feature
const modeHeader = feature.skipTests
? `**🔨 MODE: Manual Review (No Automated Tests)**
This feature is set for manual review - focus on clean implementation without automated tests.`
: `**🧪 MODE: Test-Driven Development (TDD)**
This feature requires automated Playwright tests to verify the implementation.`;
return `You are resuming work on a feature implementation that was previously started.
${modeHeader}
${memoryContent}
**Current Feature:**
ID: ${feature.id}
Category: ${feature.category}
Description: ${feature.description}
${skipTestsNote}${imagesNote}
${skipTestsNote}${imagesNote}${contextFilesPreview}
**Steps to Complete:**
${feature.steps.map((step, i) => `${i + 1}. ${step}`).join("\n")}
@@ -460,10 +529,40 @@ Begin by exploring the project structure.`;
/**
* Get the system prompt for coding agent
* @param {string} projectPath - Path to the project
* @param {boolean} isTDD - Whether this is Test-Driven Development mode (skipTests=false)
*/
getCodingPrompt() {
async getCodingPrompt(projectPath, isTDD = true) {
// Get context files preview
const contextFilesPreview = projectPath
? await contextManager.getContextFilesPreview(projectPath)
: "";
// Get memory content (lessons learned from previous runs)
const memoryContent = projectPath
? await contextManager.getMemoryContent(projectPath)
: "";
// Build mode-specific instructions
const modeHeader = isTDD
? `**🧪 MODE: Test-Driven Development (TDD)**
You are implementing features using TDD methodology. This means:
- Write Playwright tests BEFORE or alongside implementation
- Run tests frequently to verify your work
- Tests are your validation mechanism
- Delete tests after they pass (they're for immediate verification only)`
: `**🔨 MODE: Manual Review (No Automated Tests)**
You are implementing features for manual user review. This means:
- Focus on clean, working implementation
- NO automated test writing required
- User will manually verify the implementation
- DO NOT commit changes - user will review and commit`;
return `You are an AI coding agent working autonomously to implement features.
${modeHeader}
${memoryContent}
**🚨 CRITICAL FILE PROTECTION - READ THIS FIRST 🚨**
THE FOLLOWING FILE IS ABSOLUTELY FORBIDDEN FROM DIRECT MODIFICATION:
@@ -486,6 +585,8 @@ Directly modifying feature_list.json can:
**THE ONLY WAY to update features:**
Use the mcp__automaker-tools__UpdateFeatureStatus tool with featureId, status, and summary parameters.
${contextFilesPreview}
Your role is to:
- Implement features exactly as specified
- Write production-quality code
@@ -547,15 +648,62 @@ You have full access to:
- Search and analyze the codebase
- **UpdateFeatureStatus tool** (mcp__automaker-tools__UpdateFeatureStatus) - Use this to update feature status
**🧠 Learning from Errors - Memory System:**
If you encounter an error or issue that:
- Took multiple attempts to debug
- Was caused by a non-obvious codebase quirk
- Required understanding something specific about this project
- Could trip up future agent runs
**ADD IT TO MEMORY** by appending to \`.automaker/memory.md\`:
\`\`\`markdown
### Issue: [Brief Title]
**Problem:** [1-2 sentence description of the issue]
**Fix:** [Concise explanation of the solution]
\`\`\`
Keep entries concise - focus on the essential information needed to avoid the issue in the future. This helps both you and other agents learn from mistakes.
Focus on one feature at a time and complete it fully before finishing. Always delete tests after they pass and use the UpdateFeatureStatus tool.`;
}
/**
* Get the system prompt for verification agent
* @param {string} projectPath - Path to the project
* @param {boolean} isTDD - Whether this is Test-Driven Development mode (skipTests=false)
*/
getVerificationPrompt() {
async getVerificationPrompt(projectPath, isTDD = true) {
// Get context files preview
const contextFilesPreview = projectPath
? await contextManager.getContextFilesPreview(projectPath)
: "";
// Get memory content (lessons learned from previous runs)
const memoryContent = projectPath
? await contextManager.getMemoryContent(projectPath)
: "";
// Build mode-specific instructions
const modeHeader = isTDD
? `**🧪 MODE: Test-Driven Development (TDD)**
You are verifying/completing features using TDD methodology. This means:
- Run Playwright tests to verify implementation
- Fix failing tests by updating code
- Tests are your validation mechanism
- Delete tests after they pass (they're for immediate verification only)`
: `**🔨 MODE: Manual Review (No Automated Tests)**
You are completing features for manual user review. This means:
- Focus on clean, working implementation
- NO automated test writing required
- User will manually verify the implementation
- DO NOT commit changes - user will review and commit`;
return `You are an AI implementation and verification agent focused on completing features and ensuring they work.
${modeHeader}
${memoryContent}
**🚨 CRITICAL FILE PROTECTION - READ THIS FIRST 🚨**
THE FOLLOWING FILE IS ABSOLUTELY FORBIDDEN FROM DIRECT MODIFICATION:
@@ -578,6 +726,8 @@ Directly modifying feature_list.json can:
**THE ONLY WAY to update features:**
Use the mcp__automaker-tools__UpdateFeatureStatus tool with featureId, status, and summary parameters.
${contextFilesPreview}
Your role is to:
- **Continue implementing features until they are complete** - don't stop at the first failure
- Check if feature.skipTests is true - if so, skip automated testing and don't commit
@@ -638,6 +788,24 @@ You have access to:
- Make git commits
- **UpdateFeatureStatus tool** (mcp__automaker-tools__UpdateFeatureStatus) - Use this to update feature status
**🧠 Learning from Errors - Memory System:**
If you encounter an error or issue that:
- Took multiple attempts to debug
- Was caused by a non-obvious codebase quirk
- Required understanding something specific about this project
- Could trip up future agent runs
**ADD IT TO MEMORY** by appending to \`.automaker/memory.md\`:
\`\`\`markdown
### Issue: [Brief Title]
**Problem:** [1-2 sentence description of the issue]
**Fix:** [Concise explanation of the solution]
\`\`\`
Keep entries concise - focus on the essential information needed to avoid the issue in the future. This helps both you and other agents learn from mistakes.
**CRITICAL:** Be persistent and thorough - keep iterating on the implementation until all tests pass. Don't give up after the first failure. Always delete tests after they pass, use the UpdateFeatureStatus tool with a summary, and commit your work.`;
}

View File

@@ -29,7 +29,7 @@ export default function RootLayout({
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
<Toaster richColors position="top-right" />
<Toaster richColors position="bottom-right" />
</body>
</html>
);

View File

@@ -699,7 +699,7 @@ export function Sidebar() {
</button>
<button
onClick={() => setShowTrashDialog(true)}
className="group flex items-center justify-center px-3 py-2.5 rounded-lg relative overflow-hidden transition-all text-muted-foreground hover:text-primary hover:bg-destructive/10 border border-sidebar-border"
className="group flex items-center justify-center px-3 h-[42px] rounded-lg relative overflow-hidden transition-all text-muted-foreground hover:text-primary hover:bg-destructive/10 border border-sidebar-border"
title="Trash"
data-testid="trash-button"
>
@@ -778,7 +778,7 @@ export function Sidebar() {
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
className="hidden lg:flex items-center justify-center w-8 h-8 rounded-lg text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/50 border border-sidebar-border transition-all titlebar-no-drag"
className="hidden lg:flex items-center justify-center w-8 h-[42px] rounded-lg text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/50 border border-sidebar-border transition-all titlebar-no-drag"
title="Project history"
data-testid="project-history-menu"
>