mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
feat(auto-mode): enhance error handling and feature status updates
- Improved error handling in AutoModeService to log errors and update feature status to "waiting_approval" when an error occurs during feature execution. - Added error message writing to the context file for better debugging. - Updated FeatureLoader to include an optional error message parameter when updating feature status. - Enhanced prompt generation to include detailed context about attached images for better user guidance during feature implementation. - Modified KanbanCard component to visually indicate features with errors, improving user awareness of issues. These changes significantly enhance the robustness of the auto mode feature and improve the user experience by providing clearer feedback on feature execution status.
This commit is contained in:
@@ -181,6 +181,31 @@ class AutoModeService {
|
||||
return { success: true, passes: result.passes };
|
||||
} catch (error) {
|
||||
console.error("[AutoMode] Error running feature:", error);
|
||||
|
||||
// Write error to context file
|
||||
try {
|
||||
await contextManager.writeToContextFile(
|
||||
projectPath,
|
||||
featureId,
|
||||
`\n\n❌ ERROR: ${error.message}\n\n${error.stack || ''}\n`
|
||||
);
|
||||
} catch (contextError) {
|
||||
console.error("[AutoMode] Failed to write error to context:", contextError);
|
||||
}
|
||||
|
||||
// Update feature status to waiting_approval so user can review the error
|
||||
try {
|
||||
await featureLoader.updateFeatureStatus(
|
||||
featureId,
|
||||
"waiting_approval",
|
||||
projectPath,
|
||||
null, // no summary
|
||||
error.message // pass error message
|
||||
);
|
||||
} catch (statusError) {
|
||||
console.error("[AutoMode] Failed to update feature status after error:", statusError);
|
||||
}
|
||||
|
||||
sendToRenderer({
|
||||
type: "auto_mode_error",
|
||||
error: error.message,
|
||||
@@ -260,6 +285,31 @@ class AutoModeService {
|
||||
return { success: true, passes: result.passes };
|
||||
} catch (error) {
|
||||
console.error("[AutoMode] Error verifying feature:", error);
|
||||
|
||||
// Write error to context file
|
||||
try {
|
||||
await contextManager.writeToContextFile(
|
||||
projectPath,
|
||||
featureId,
|
||||
`\n\n❌ ERROR: ${error.message}\n\n${error.stack || ''}\n`
|
||||
);
|
||||
} catch (contextError) {
|
||||
console.error("[AutoMode] Failed to write error to context:", contextError);
|
||||
}
|
||||
|
||||
// Update feature status to waiting_approval so user can review the error
|
||||
try {
|
||||
await featureLoader.updateFeatureStatus(
|
||||
featureId,
|
||||
"waiting_approval",
|
||||
projectPath,
|
||||
null, // no summary
|
||||
error.message // pass error message
|
||||
);
|
||||
} catch (statusError) {
|
||||
console.error("[AutoMode] Failed to update feature status after error:", statusError);
|
||||
}
|
||||
|
||||
sendToRenderer({
|
||||
type: "auto_mode_error",
|
||||
error: error.message,
|
||||
@@ -400,6 +450,31 @@ class AutoModeService {
|
||||
return { success: true, passes: finalResult.passes };
|
||||
} catch (error) {
|
||||
console.error("[AutoMode] Error resuming feature:", error);
|
||||
|
||||
// Write error to context file
|
||||
try {
|
||||
await contextManager.writeToContextFile(
|
||||
projectPath,
|
||||
featureId,
|
||||
`\n\n❌ ERROR: ${error.message}\n\n${error.stack || ''}\n`
|
||||
);
|
||||
} catch (contextError) {
|
||||
console.error("[AutoMode] Failed to write error to context:", contextError);
|
||||
}
|
||||
|
||||
// Update feature status to waiting_approval so user can review the error
|
||||
try {
|
||||
await featureLoader.updateFeatureStatus(
|
||||
featureId,
|
||||
"waiting_approval",
|
||||
projectPath,
|
||||
null, // no summary
|
||||
error.message // pass error message
|
||||
);
|
||||
} catch (statusError) {
|
||||
console.error("[AutoMode] Failed to update feature status after error:", statusError);
|
||||
}
|
||||
|
||||
sendToRenderer({
|
||||
type: "auto_mode_error",
|
||||
error: error.message,
|
||||
@@ -544,6 +619,31 @@ class AutoModeService {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`[AutoMode] Error running feature ${featureId}:`, error);
|
||||
|
||||
// Write error to context file
|
||||
try {
|
||||
await contextManager.writeToContextFile(
|
||||
projectPath,
|
||||
featureId,
|
||||
`\n\n❌ ERROR: ${error.message}\n\n${error.stack || ''}\n`
|
||||
);
|
||||
} catch (contextError) {
|
||||
console.error("[AutoMode] Failed to write error to context:", contextError);
|
||||
}
|
||||
|
||||
// Update feature status to waiting_approval so user can review the error
|
||||
try {
|
||||
await featureLoader.updateFeatureStatus(
|
||||
featureId,
|
||||
"waiting_approval",
|
||||
projectPath,
|
||||
null, // no summary
|
||||
error.message // pass error message
|
||||
);
|
||||
} catch (statusError) {
|
||||
console.error("[AutoMode] Failed to update feature status after error:", statusError);
|
||||
}
|
||||
|
||||
sendToRenderer({
|
||||
type: "auto_mode_error",
|
||||
error: error.message,
|
||||
@@ -761,6 +861,31 @@ class AutoModeService {
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[AutoMode] Error in follow-up:", error);
|
||||
|
||||
// Write error to context file
|
||||
try {
|
||||
await contextManager.writeToContextFile(
|
||||
projectPath,
|
||||
featureId,
|
||||
`\n\n❌ ERROR: ${error.message}\n\n${error.stack || ''}\n`
|
||||
);
|
||||
} catch (contextError) {
|
||||
console.error("[AutoMode] Failed to write error to context:", contextError);
|
||||
}
|
||||
|
||||
// Update feature status to waiting_approval so user can review the error
|
||||
try {
|
||||
await featureLoader.updateFeatureStatus(
|
||||
featureId,
|
||||
"waiting_approval",
|
||||
projectPath,
|
||||
null, // no summary
|
||||
error.message // pass error message
|
||||
);
|
||||
} catch (statusError) {
|
||||
console.error("[AutoMode] Failed to update feature status after error:", statusError);
|
||||
}
|
||||
|
||||
sendToRenderer({
|
||||
type: "auto_mode_error",
|
||||
error: error.message,
|
||||
|
||||
@@ -342,7 +342,8 @@ class FeatureExecutor {
|
||||
const path = require("path");
|
||||
for (const imagePathObj of imagePaths) {
|
||||
try {
|
||||
const imagePath = imagePathObj.path;
|
||||
// Handle both string paths and FeatureImagePath objects
|
||||
const imagePath = typeof imagePathObj === 'string' ? imagePathObj : imagePathObj.path;
|
||||
const imageBuffer = fs.readFileSync(imagePath);
|
||||
const base64Data = imageBuffer.toString("base64");
|
||||
const ext = path.extname(imagePath).toLowerCase();
|
||||
@@ -353,7 +354,9 @@ class FeatureExecutor {
|
||||
".gif": "image/gif",
|
||||
".webp": "image/webp",
|
||||
};
|
||||
const mediaType = 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",
|
||||
@@ -366,8 +369,9 @@ class FeatureExecutor {
|
||||
|
||||
console.log(`[FeatureExecutor] Added image to resume prompt: ${imagePath}`);
|
||||
} catch (error) {
|
||||
const errorPath = typeof imagePathObj === 'string' ? imagePathObj : imagePathObj.path;
|
||||
console.error(
|
||||
`[FeatureExecutor] Failed to load image ${imagePathObj.path}:`,
|
||||
`[FeatureExecutor] Failed to load image ${errorPath}:`,
|
||||
error
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,8 +36,9 @@ class FeatureLoader {
|
||||
* @param {string} status - The new status
|
||||
* @param {string} projectPath - Path to the project
|
||||
* @param {string} [summary] - Optional summary of what was done
|
||||
* @param {string} [error] - Optional error message if feature errored
|
||||
*/
|
||||
async updateFeatureStatus(featureId, status, projectPath, summary) {
|
||||
async updateFeatureStatus(featureId, status, projectPath, summary, error) {
|
||||
const featuresPath = path.join(
|
||||
projectPath,
|
||||
".automaker",
|
||||
@@ -98,6 +99,14 @@ class FeatureLoader {
|
||||
feature.summary = summary;
|
||||
}
|
||||
|
||||
// Update the error field (set or clear)
|
||||
if (error) {
|
||||
feature.error = error;
|
||||
} else {
|
||||
// Clear any previous error when status changes without error
|
||||
delete feature.error;
|
||||
}
|
||||
|
||||
// Save back to file
|
||||
const toSave = features.map((f) => {
|
||||
const featureData = {
|
||||
@@ -123,6 +132,9 @@ class FeatureLoader {
|
||||
if (f.summary !== undefined) {
|
||||
featureData.summary = f.summary;
|
||||
}
|
||||
if (f.error !== undefined) {
|
||||
featureData.error = f.error;
|
||||
}
|
||||
return featureData;
|
||||
});
|
||||
|
||||
|
||||
@@ -10,9 +10,18 @@ class PromptBuilder {
|
||||
? `\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`
|
||||
: "";
|
||||
|
||||
const imagesNote = feature.imagePaths && feature.imagePaths.length > 0
|
||||
? `\n**📎 Context Images Attached:**\nThe user has attached ${feature.imagePaths.length} image(s) for context. These images will be provided to you visually to help understand the requirements. Review them carefully before implementing.\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");
|
||||
|
||||
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:
|
||||
|
||||
${imagesList}
|
||||
|
||||
You can use the Read tool to view these images at any time during implementation. Review them carefully before implementing.\n`;
|
||||
}
|
||||
|
||||
return `You are working on a feature implementation task.
|
||||
|
||||
@@ -121,9 +130,18 @@ Begin by reading the project structure and then implementing the feature.`;
|
||||
? `\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`
|
||||
: "";
|
||||
|
||||
const imagesNote = feature.imagePaths && feature.imagePaths.length > 0
|
||||
? `\n**📎 Context Images Attached:**\nThe user has attached ${feature.imagePaths.length} image(s) for context. These images will be provided to you visually to help understand the requirements. Review them carefully before implementing.\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");
|
||||
|
||||
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:
|
||||
|
||||
${imagesList}
|
||||
|
||||
You can use the Read tool to view these images at any time during implementation. Review them carefully before implementing.\n`;
|
||||
}
|
||||
|
||||
return `You are implementing and verifying a feature until it is complete and working correctly.
|
||||
|
||||
@@ -224,9 +242,24 @@ Begin by reading the project structure and understanding what needs to be implem
|
||||
? `\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`
|
||||
: "";
|
||||
|
||||
const imagesNote = feature.imagePaths && feature.imagePaths.length > 0
|
||||
? `\n**📎 Context Images Attached:**\nThe user has attached ${feature.imagePaths.length} image(s) for context. These images will be provided to you visually to help understand the requirements. Review them carefully.\n`
|
||||
: "";
|
||||
// For resume, check both followUpImages and imagePaths
|
||||
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");
|
||||
|
||||
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:
|
||||
|
||||
${imagesList}
|
||||
|
||||
You can use the Read tool to view these images at any time. Review them carefully.\n`;
|
||||
}
|
||||
|
||||
return `You are resuming work on a feature implementation that was previously started.
|
||||
|
||||
|
||||
@@ -581,7 +581,7 @@ export function Sidebar() {
|
||||
isActive
|
||||
? "bg-sidebar-accent/50 text-foreground border border-sidebar-border"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/50",
|
||||
!sidebarOpen && "justify-center"
|
||||
sidebarOpen ? "justify-start" : "justify-center"
|
||||
)}
|
||||
title={!sidebarOpen ? item.label : undefined}
|
||||
data-testid={`nav-${item.id}`}
|
||||
@@ -599,7 +599,7 @@ export function Sidebar() {
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
"ml-2.5 font-medium text-sm flex-1",
|
||||
"ml-2.5 font-medium text-sm flex-1 text-left",
|
||||
sidebarOpen ? "hidden lg:block" : "hidden"
|
||||
)}
|
||||
>
|
||||
@@ -665,7 +665,7 @@ export function Sidebar() {
|
||||
/>
|
||||
<span
|
||||
className={cn(
|
||||
"ml-2.5 font-medium text-sm flex-1",
|
||||
"ml-2.5 font-medium text-sm flex-1 text-left",
|
||||
sidebarOpen ? "hidden lg:block" : "hidden"
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -202,7 +202,7 @@ export function AgentOutputModal({
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onClose}>
|
||||
<DialogContent
|
||||
className="w-[90vw] max-w-[90vw] max-h-[80vh] flex flex-col"
|
||||
className="w-[60vw] max-w-[60vw] max-h-[80vh] flex flex-col"
|
||||
data-testid="agent-output-modal"
|
||||
>
|
||||
<DialogHeader className="flex-shrink-0">
|
||||
|
||||
@@ -338,16 +338,32 @@ export function BoardView() {
|
||||
}, [showAddDialog, defaultSkipTests]);
|
||||
|
||||
|
||||
// Listen for auto mode feature completion and reload features
|
||||
// Listen for auto mode feature completion and errors to reload features
|
||||
useEffect(() => {
|
||||
const api = getElectronAPI();
|
||||
if (!api?.autoMode) return;
|
||||
|
||||
const { removeRunningTask } = useAppStore.getState();
|
||||
|
||||
const unsubscribe = api.autoMode.onEvent((event) => {
|
||||
if (event.type === "auto_mode_feature_complete") {
|
||||
// Reload features when a feature is completed
|
||||
console.log("[Board] Feature completed, reloading features...");
|
||||
loadFeatures();
|
||||
} else if (event.type === "auto_mode_error") {
|
||||
// Reload features when an error occurs (feature moved to waiting_approval)
|
||||
console.log("[Board] Feature error, reloading features...", event.error);
|
||||
|
||||
// Remove from running tasks so it moves to the correct column
|
||||
if (event.featureId) {
|
||||
removeRunningTask(event.featureId);
|
||||
}
|
||||
|
||||
loadFeatures();
|
||||
// Show error toast
|
||||
toast.error("Agent encountered an error", {
|
||||
description: event.error || "Check the logs for details",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -439,6 +455,7 @@ export function BoardView() {
|
||||
imagePaths: f.imagePaths,
|
||||
skipTests: f.skipTests,
|
||||
summary: f.summary,
|
||||
error: f.error,
|
||||
}));
|
||||
await api.writeFile(
|
||||
`${currentProject.path}/.automaker/feature_list.json`,
|
||||
|
||||
@@ -39,7 +39,6 @@ import {
|
||||
RotateCcw,
|
||||
StopCircle,
|
||||
Hand,
|
||||
ArrowLeft,
|
||||
MessageSquare,
|
||||
GitCommit,
|
||||
Cpu,
|
||||
@@ -49,6 +48,7 @@ import {
|
||||
Expand,
|
||||
FileText,
|
||||
MoreVertical,
|
||||
AlertCircle,
|
||||
} from "lucide-react";
|
||||
import { CountUpTimer } from "@/components/ui/count-up-timer";
|
||||
import { getElectronAPI } from "@/lib/electron";
|
||||
@@ -199,7 +199,10 @@ export function KanbanCard({
|
||||
"cursor-grab active:cursor-grabbing transition-all backdrop-blur-sm border-border relative",
|
||||
isDragging && "opacity-50 scale-105 shadow-lg",
|
||||
isCurrentAutoTask &&
|
||||
"border-running-indicator border-2 shadow-running-indicator/50 shadow-lg animate-pulse"
|
||||
"border-running-indicator border-2 shadow-running-indicator/50 shadow-lg animate-pulse",
|
||||
feature.error &&
|
||||
!isCurrentAutoTask &&
|
||||
"border-red-500 border-2 shadow-red-500/30 shadow-lg"
|
||||
)}
|
||||
data-testid={`kanban-card-${feature.id}`}
|
||||
{...attributes}
|
||||
@@ -214,7 +217,7 @@ export function KanbanCard({
|
||||
</div>
|
||||
)}
|
||||
{/* Skip Tests indicator badge */}
|
||||
{feature.skipTests && (
|
||||
{feature.skipTests && !feature.error && (
|
||||
<div
|
||||
className={cn(
|
||||
"absolute px-1.5 py-0.5 text-[10px] font-medium rounded flex items-center gap-1 z-10",
|
||||
@@ -228,6 +231,21 @@ export function KanbanCard({
|
||||
<span>Manual</span>
|
||||
</div>
|
||||
)}
|
||||
{/* Error indicator badge */}
|
||||
{feature.error && (
|
||||
<div
|
||||
className={cn(
|
||||
"absolute px-1.5 py-0.5 text-[10px] font-medium rounded flex items-center gap-1 z-10",
|
||||
shortcutKey ? "top-2 left-10" : "top-2 left-2",
|
||||
"bg-red-500/20 border border-red-500/50 text-red-400"
|
||||
)}
|
||||
data-testid={`error-badge-${feature.id}`}
|
||||
title={feature.error}
|
||||
>
|
||||
<AlertCircle className="w-3 h-3" />
|
||||
<span>Errored</span>
|
||||
</div>
|
||||
)}
|
||||
<CardHeader className="p-3 pb-2">
|
||||
{isCurrentAutoTask && (
|
||||
<div className="absolute top-2 right-2 flex items-center justify-center gap-2 bg-running-indicator/20 border border-running-indicator rounded px-2 py-0.5">
|
||||
@@ -255,6 +273,28 @@ export function KanbanCard({
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEdit();
|
||||
}}
|
||||
data-testid={`edit-feature-${feature.id}`}
|
||||
>
|
||||
<Edit className="w-3 h-3 mr-2" />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
{onViewOutput && (
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onViewOutput();
|
||||
}}
|
||||
data-testid={`view-logs-${feature.id}`}
|
||||
>
|
||||
<FileText className="w-3 h-3 mr-2" />
|
||||
Logs
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
className="text-destructive focus:text-destructive"
|
||||
onClick={(e) => {
|
||||
@@ -565,55 +605,10 @@ export function KanbanCard({
|
||||
Logs
|
||||
</Button>
|
||||
)}
|
||||
{/* Move back button for skipTests verified features */}
|
||||
{feature.skipTests && onMoveBackToInProgress && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 text-xs text-yellow-500 hover:text-yellow-500 hover:bg-yellow-500/10"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onMoveBackToInProgress();
|
||||
}}
|
||||
data-testid={`move-back-${feature.id}`}
|
||||
>
|
||||
<ArrowLeft className="w-3 h-3 mr-1" />
|
||||
Back
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="flex-1 h-7 text-xs"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEdit();
|
||||
}}
|
||||
data-testid={`edit-feature-${feature.id}`}
|
||||
>
|
||||
<Edit className="w-3 h-3 mr-1" />
|
||||
Edit
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{!isCurrentAutoTask && feature.status === "waiting_approval" && (
|
||||
<>
|
||||
{/* Logs button if context exists */}
|
||||
{hasContext && onViewOutput && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-7 text-xs"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onViewOutput();
|
||||
}}
|
||||
data-testid={`view-output-waiting-${feature.id}`}
|
||||
>
|
||||
<FileText className="w-3 h-3 mr-1" />
|
||||
Logs
|
||||
</Button>
|
||||
)}
|
||||
{/* Follow-up prompt button */}
|
||||
{onFollowUp && (
|
||||
<Button
|
||||
@@ -665,19 +660,6 @@ export function KanbanCard({
|
||||
Logs
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="flex-1 h-7 text-xs"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onEdit();
|
||||
}}
|
||||
data-testid={`edit-feature-${feature.id}`}
|
||||
>
|
||||
<Edit className="w-3 h-3 mr-1" />
|
||||
Edit
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -86,6 +86,7 @@ export interface Feature {
|
||||
startedAt?: string; // ISO timestamp for when the card moved to in_progress
|
||||
skipTests?: boolean; // When true, skip TDD approach and require manual verification
|
||||
summary?: string; // Summary of what was done/modified by the agent
|
||||
error?: string; // Error message if the agent errored during processing
|
||||
}
|
||||
|
||||
export interface AppState {
|
||||
|
||||
Reference in New Issue
Block a user