+
) : (
-
+
)}
-
+
{uploadedImageData
? "Click to change"
: "Click to upload"}
diff --git a/app/src/components/views/feature-suggestions-dialog.tsx b/app/src/components/views/feature-suggestions-dialog.tsx
new file mode 100644
index 00000000..e1b5bf10
--- /dev/null
+++ b/app/src/components/views/feature-suggestions-dialog.tsx
@@ -0,0 +1,433 @@
+"use client";
+
+import { useEffect, useRef, useState, useCallback } from "react";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Button } from "@/components/ui/button";
+import { Checkbox } from "@/components/ui/checkbox";
+import { Label } from "@/components/ui/label";
+import {
+ Loader2,
+ Lightbulb,
+ Download,
+ StopCircle,
+ ChevronDown,
+ ChevronRight,
+} from "lucide-react";
+import { getElectronAPI, FeatureSuggestion, SuggestionsEvent } from "@/lib/electron";
+import { useAppStore, Feature } from "@/store/app-store";
+import { toast } from "sonner";
+
+interface FeatureSuggestionsDialogProps {
+ open: boolean;
+ onClose: () => void;
+ projectPath: string;
+}
+
+export function FeatureSuggestionsDialog({
+ open,
+ onClose,
+ projectPath,
+}: FeatureSuggestionsDialogProps) {
+ const [isGenerating, setIsGenerating] = useState(false);
+ const [progress, setProgress] = useState([]);
+ const [suggestions, setSuggestions] = useState([]);
+ const [selectedIds, setSelectedIds] = useState>(new Set());
+ const [expandedIds, setExpandedIds] = useState>(new Set());
+ const [isImporting, setIsImporting] = useState(false);
+ const scrollRef = useRef(null);
+ const autoScrollRef = useRef(true);
+
+ const { features, setFeatures } = useAppStore();
+
+ // Auto-scroll progress when new content arrives
+ useEffect(() => {
+ if (autoScrollRef.current && scrollRef.current && isGenerating) {
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
+ }
+ }, [progress, isGenerating]);
+
+ // Listen for suggestion events
+ useEffect(() => {
+ if (!open) return;
+
+ const api = getElectronAPI();
+ if (!api?.suggestions) return;
+
+ const unsubscribe = api.suggestions.onEvent((event: SuggestionsEvent) => {
+ if (event.type === "suggestions_progress") {
+ setProgress((prev) => [...prev, event.content || ""]);
+ } else if (event.type === "suggestions_tool") {
+ const toolName = event.tool || "Unknown Tool";
+ setProgress((prev) => [...prev, `Using tool: ${toolName}\n`]);
+ } else if (event.type === "suggestions_complete") {
+ setIsGenerating(false);
+ if (event.suggestions && event.suggestions.length > 0) {
+ setSuggestions(event.suggestions);
+ // Select all by default
+ setSelectedIds(new Set(event.suggestions.map((s) => s.id)));
+ toast.success(`Generated ${event.suggestions.length} feature suggestions!`);
+ } else {
+ toast.info("No suggestions generated. Try again.");
+ }
+ } else if (event.type === "suggestions_error") {
+ setIsGenerating(false);
+ toast.error(`Error: ${event.error}`);
+ }
+ });
+
+ return () => {
+ unsubscribe();
+ };
+ }, [open]);
+
+ // Start generating suggestions
+ const handleGenerate = useCallback(async () => {
+ const api = getElectronAPI();
+ if (!api?.suggestions) {
+ toast.error("Suggestions API not available");
+ return;
+ }
+
+ setIsGenerating(true);
+ setProgress([]);
+ setSuggestions([]);
+ setSelectedIds(new Set());
+
+ try {
+ const result = await api.suggestions.generate(projectPath);
+ if (!result.success) {
+ toast.error(result.error || "Failed to start generation");
+ setIsGenerating(false);
+ }
+ } catch (error) {
+ console.error("Failed to generate suggestions:", error);
+ toast.error("Failed to start generation");
+ setIsGenerating(false);
+ }
+ }, [projectPath]);
+
+ // Stop generating
+ const handleStop = useCallback(async () => {
+ const api = getElectronAPI();
+ if (!api?.suggestions) return;
+
+ try {
+ await api.suggestions.stop();
+ setIsGenerating(false);
+ toast.info("Generation stopped");
+ } catch (error) {
+ console.error("Failed to stop generation:", error);
+ }
+ }, []);
+
+ // Toggle suggestion selection
+ const toggleSelection = useCallback((id: string) => {
+ setSelectedIds((prev) => {
+ const next = new Set(prev);
+ if (next.has(id)) {
+ next.delete(id);
+ } else {
+ next.add(id);
+ }
+ return next;
+ });
+ }, []);
+
+ // Toggle expand/collapse for a suggestion
+ const toggleExpanded = useCallback((id: string) => {
+ setExpandedIds((prev) => {
+ const next = new Set(prev);
+ if (next.has(id)) {
+ next.delete(id);
+ } else {
+ next.add(id);
+ }
+ return next;
+ });
+ }, []);
+
+ // Select/deselect all
+ const toggleSelectAll = useCallback(() => {
+ if (selectedIds.size === suggestions.length) {
+ setSelectedIds(new Set());
+ } else {
+ setSelectedIds(new Set(suggestions.map((s) => s.id)));
+ }
+ }, [selectedIds.size, suggestions]);
+
+ // Import selected suggestions as features
+ const handleImport = useCallback(async () => {
+ if (selectedIds.size === 0) {
+ toast.warning("No suggestions selected");
+ return;
+ }
+
+ setIsImporting(true);
+
+ try {
+ const api = getElectronAPI();
+ const selectedSuggestions = suggestions.filter((s) =>
+ selectedIds.has(s.id)
+ );
+
+ // Create new features from selected suggestions
+ const newFeatures: Feature[] = selectedSuggestions.map((s) => ({
+ id: `feature-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
+ category: s.category,
+ description: s.description,
+ steps: s.steps,
+ status: "backlog" as const,
+ skipTests: true, // As specified, testing mode true
+ }));
+
+ // Merge with existing features
+ const updatedFeatures = [...features, ...newFeatures];
+
+ // Save to file
+ const featureListPath = `${projectPath}/.automaker/feature_list.json`;
+ await api.writeFile(featureListPath, JSON.stringify(updatedFeatures, null, 2));
+
+ // Update store
+ setFeatures(updatedFeatures);
+
+ toast.success(`Imported ${newFeatures.length} features to backlog!`);
+ onClose();
+ } catch (error) {
+ console.error("Failed to import features:", error);
+ toast.error("Failed to import features");
+ } finally {
+ setIsImporting(false);
+ }
+ }, [selectedIds, suggestions, features, setFeatures, projectPath, onClose]);
+
+ // Handle scroll to detect if user scrolled up
+ const handleScroll = () => {
+ if (!scrollRef.current) return;
+
+ const { scrollTop, scrollHeight, clientHeight } = scrollRef.current;
+ const isAtBottom = scrollHeight - scrollTop - clientHeight < 50;
+ autoScrollRef.current = isAtBottom;
+ };
+
+ // Reset state when dialog closes
+ useEffect(() => {
+ if (!open) {
+ // Don't reset immediately - allow re-open to see results
+ // Only reset if explicitly closed without importing
+ }
+ }, [open]);
+
+ const hasStarted = progress.length > 0 || suggestions.length > 0;
+ const hasSuggestions = suggestions.length > 0;
+
+ return (
+
+ );
+}
diff --git a/app/src/components/views/spec-view.tsx b/app/src/components/views/spec-view.tsx
index 211214ab..b54ceef1 100644
--- a/app/src/components/views/spec-view.tsx
+++ b/app/src/components/views/spec-view.tsx
@@ -5,13 +5,35 @@ import { useAppStore } from "@/store/app-store";
import { getElectronAPI } from "@/lib/electron";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
-import { Save, RefreshCw, FileText } from "lucide-react";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Save, RefreshCw, FileText, Sparkles, Loader2, FilePlus2 } from "lucide-react";
+import { Checkbox } from "@/components/ui/checkbox";
+import type { SpecRegenerationEvent } from "@/types/electron";
export function SpecView() {
const { currentProject, appSpec, setAppSpec } = useAppStore();
const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false);
const [hasChanges, setHasChanges] = useState(false);
+ const [specExists, setSpecExists] = useState(true);
+
+ // Regeneration state
+ const [showRegenerateDialog, setShowRegenerateDialog] = useState(false);
+ const [projectDefinition, setProjectDefinition] = useState("");
+ const [isRegenerating, setIsRegenerating] = useState(false);
+
+ // Create spec state
+ const [showCreateDialog, setShowCreateDialog] = useState(false);
+ const [projectOverview, setProjectOverview] = useState("");
+ const [isCreating, setIsCreating] = useState(false);
+ const [generateFeatures, setGenerateFeatures] = useState(true);
// Load spec from file
const loadSpec = useCallback(async () => {
@@ -26,10 +48,16 @@ export function SpecView() {
if (result.success && result.content) {
setAppSpec(result.content);
+ setSpecExists(true);
setHasChanges(false);
+ } else {
+ // File doesn't exist
+ setAppSpec("");
+ setSpecExists(false);
}
} catch (error) {
console.error("Failed to load spec:", error);
+ setSpecExists(false);
} finally {
setIsLoading(false);
}
@@ -39,6 +67,35 @@ export function SpecView() {
loadSpec();
}, [loadSpec]);
+ // Subscribe to spec regeneration events
+ useEffect(() => {
+ const api = getElectronAPI();
+ if (!api.specRegeneration) return;
+
+ const unsubscribe = api.specRegeneration.onEvent((event: SpecRegenerationEvent) => {
+ console.log("[SpecView] Regeneration event:", event.type);
+
+ if (event.type === "spec_regeneration_complete") {
+ setIsRegenerating(false);
+ setIsCreating(false);
+ setShowRegenerateDialog(false);
+ setShowCreateDialog(false);
+ setProjectDefinition("");
+ setProjectOverview("");
+ // Reload the spec to show the new content
+ loadSpec();
+ } else if (event.type === "spec_regeneration_error") {
+ setIsRegenerating(false);
+ setIsCreating(false);
+ console.error("[SpecView] Regeneration error:", event.error);
+ }
+ });
+
+ return () => {
+ unsubscribe();
+ };
+ }, [loadSpec]);
+
// Save spec to file
const saveSpec = async () => {
if (!currentProject) return;
@@ -63,6 +120,62 @@ export function SpecView() {
setHasChanges(true);
};
+ const handleRegenerate = async () => {
+ if (!currentProject || !projectDefinition.trim()) return;
+
+ setIsRegenerating(true);
+ try {
+ const api = getElectronAPI();
+ if (!api.specRegeneration) {
+ console.error("[SpecView] Spec regeneration not available");
+ setIsRegenerating(false);
+ return;
+ }
+ const result = await api.specRegeneration.generate(
+ currentProject.path,
+ projectDefinition.trim()
+ );
+
+ if (!result.success) {
+ console.error("[SpecView] Failed to start regeneration:", result.error);
+ setIsRegenerating(false);
+ }
+ // If successful, we'll wait for the events to update the state
+ } catch (error) {
+ console.error("[SpecView] Failed to regenerate spec:", error);
+ setIsRegenerating(false);
+ }
+ };
+
+ const handleCreateSpec = async () => {
+ if (!currentProject || !projectOverview.trim()) return;
+
+ setIsCreating(true);
+ setShowCreateDialog(false);
+ try {
+ const api = getElectronAPI();
+ if (!api.specRegeneration) {
+ console.error("[SpecView] Spec regeneration not available");
+ setIsCreating(false);
+ return;
+ }
+ const result = await api.specRegeneration.create(
+ currentProject.path,
+ projectOverview.trim(),
+ generateFeatures
+ );
+
+ if (!result.success) {
+ console.error("[SpecView] Failed to start spec creation:", result.error);
+ setIsCreating(false);
+ }
+ // If successful, we'll wait for the events to update the state
+ } catch (error) {
+ console.error("[SpecView] Failed to create spec:", error);
+ setIsCreating(false);
+ }
+ };
+
if (!currentProject) {
return (
+ {/* Header */}
+
+
+
+
+
App Specification
+
+ {currentProject.path}/.automaker/app_spec.txt
+
+
+
+
+
+ {/* Empty State */}
+
+
+
+
No App Specification Found
+
+ Create an app specification to help our system understand your project.
+ We'll analyze your codebase and generate a comprehensive spec based on your description.
+
+
+
+
+
+ {/* Create Dialog */}
+
+
+ );
+ }
+
return (
+
+
+ {/* Regenerate Dialog */}
+
);
}
diff --git a/app/src/hooks/use-auto-mode.ts b/app/src/hooks/use-auto-mode.ts
index 9f2def66..4301d406 100644
--- a/app/src/hooks/use-auto-mode.ts
+++ b/app/src/hooks/use-auto-mode.ts
@@ -1,17 +1,15 @@
-import { useEffect, useCallback } from "react";
+import { useEffect, useCallback, useMemo } from "react";
import { useShallow } from "zustand/react/shallow";
import { useAppStore } from "@/store/app-store";
-import { getElectronAPI } from "@/lib/electron";
-import type { AutoModeEvent } from "@/types/electron";
+import { getElectronAPI, type AutoModeEvent } from "@/lib/electron";
/**
- * Hook for managing auto mode
+ * Hook for managing auto mode (scoped per project)
*/
export function useAutoMode() {
const {
- isAutoModeRunning,
+ autoModeByProject,
setAutoModeRunning,
- runningAutoTasks,
addRunningTask,
removeRunningTask,
clearRunningTasks,
@@ -20,9 +18,8 @@ export function useAutoMode() {
maxConcurrency,
} = useAppStore(
useShallow((state) => ({
- isAutoModeRunning: state.isAutoModeRunning,
+ autoModeByProject: state.autoModeByProject,
setAutoModeRunning: state.setAutoModeRunning,
- runningAutoTasks: state.runningAutoTasks,
addRunningTask: state.addRunningTask,
removeRunningTask: state.removeRunningTask,
clearRunningTasks: state.clearRunningTasks,
@@ -32,56 +29,74 @@ export function useAutoMode() {
}))
);
+ // Get project-specific auto mode state
+ const projectId = currentProject?.id;
+ const projectAutoModeState = useMemo(() => {
+ if (!projectId) return { isRunning: false, runningTasks: [] };
+ return autoModeByProject[projectId] || { isRunning: false, runningTasks: [] };
+ }, [autoModeByProject, projectId]);
+
+ const isAutoModeRunning = projectAutoModeState.isRunning;
+ const runningAutoTasks = projectAutoModeState.runningTasks;
+
// Check if we can start a new task based on concurrency limit
const canStartNewTask = runningAutoTasks.length < maxConcurrency;
// Handle auto mode events
useEffect(() => {
const api = getElectronAPI();
- if (!api?.autoMode) return;
+ if (!api?.autoMode || !projectId) return;
const unsubscribe = api.autoMode.onEvent((event: AutoModeEvent) => {
console.log("[AutoMode Event]", event);
+ // Events include projectId from backend, use it to scope updates
+ // Fall back to current projectId if not provided in event
+ const eventProjectId = event.projectId ?? projectId;
+
switch (event.type) {
case "auto_mode_feature_start":
- addRunningTask(event.featureId);
- addAutoModeActivity({
- featureId: event.featureId,
- type: "start",
- message: `Started working on feature`,
- });
+ if (event.featureId) {
+ addRunningTask(eventProjectId, event.featureId);
+ addAutoModeActivity({
+ featureId: event.featureId,
+ type: "start",
+ message: `Started working on feature`,
+ });
+ }
break;
case "auto_mode_feature_complete":
// Feature completed - remove from running tasks and UI will reload features on its own
- console.log(
- "[AutoMode] Feature completed:",
- event.featureId,
- "passes:",
- event.passes
- );
- removeRunningTask(event.featureId);
- addAutoModeActivity({
- featureId: event.featureId,
- type: "complete",
- message: event.passes
- ? "Feature completed successfully"
- : "Feature completed with failures",
- passes: event.passes,
- });
+ if (event.featureId) {
+ console.log(
+ "[AutoMode] Feature completed:",
+ event.featureId,
+ "passes:",
+ event.passes
+ );
+ removeRunningTask(eventProjectId, event.featureId);
+ addAutoModeActivity({
+ featureId: event.featureId,
+ type: "complete",
+ message: event.passes
+ ? "Feature completed successfully"
+ : "Feature completed with failures",
+ passes: event.passes,
+ });
+ }
break;
case "auto_mode_complete":
- // All features completed
- setAutoModeRunning(false);
- clearRunningTasks();
+ // All features completed for this project
+ setAutoModeRunning(eventProjectId, false);
+ clearRunningTasks(eventProjectId);
console.log("[AutoMode] All features completed!");
break;
case "auto_mode_error":
console.error("[AutoMode Error]", event.error);
- if (event.featureId) {
+ if (event.featureId && event.error) {
addAutoModeActivity({
featureId: event.featureId,
type: "error",
@@ -92,7 +107,7 @@ export function useAutoMode() {
case "auto_mode_progress":
// Log progress updates (throttle to avoid spam)
- if (event.content && event.content.length > 10) {
+ if (event.featureId && event.content && event.content.length > 10) {
addAutoModeActivity({
featureId: event.featureId,
type: "progress",
@@ -103,31 +118,36 @@ export function useAutoMode() {
case "auto_mode_tool":
// Log tool usage
- addAutoModeActivity({
- featureId: event.featureId,
- type: "tool",
- message: `Using tool: ${event.tool}`,
- tool: event.tool,
- });
+ if (event.featureId && event.tool) {
+ addAutoModeActivity({
+ featureId: event.featureId,
+ type: "tool",
+ message: `Using tool: ${event.tool}`,
+ tool: event.tool,
+ });
+ }
break;
case "auto_mode_phase":
// Log phase transitions (Planning, Action, Verification)
- console.log(
- `[AutoMode] Phase: ${event.phase} for ${event.featureId}`
- );
- addAutoModeActivity({
- featureId: event.featureId,
- type: event.phase,
- message: event.message,
- phase: event.phase,
- });
+ if (event.featureId && event.phase && event.message) {
+ console.log(
+ `[AutoMode] Phase: ${event.phase} for ${event.featureId}`
+ );
+ addAutoModeActivity({
+ featureId: event.featureId,
+ type: event.phase,
+ message: event.message,
+ phase: event.phase,
+ });
+ }
break;
}
});
return unsubscribe;
}, [
+ projectId,
addRunningTask,
removeRunningTask,
clearRunningTasks,
@@ -151,7 +171,7 @@ export function useAutoMode() {
const result = await api.autoMode.start(currentProject.path, maxConcurrency);
if (result.success) {
- setAutoModeRunning(true);
+ setAutoModeRunning(currentProject.id, true);
console.log(`[AutoMode] Started successfully with maxConcurrency: ${maxConcurrency}`);
} else {
console.error("[AutoMode] Failed to start:", result.error);
@@ -159,13 +179,20 @@ export function useAutoMode() {
}
} catch (error) {
console.error("[AutoMode] Error starting:", error);
- setAutoModeRunning(false);
+ if (currentProject) {
+ setAutoModeRunning(currentProject.id, false);
+ }
throw error;
}
}, [currentProject, setAutoModeRunning, maxConcurrency]);
// Stop auto mode
const stop = useCallback(async () => {
+ if (!currentProject) {
+ console.error("No project selected");
+ return;
+ }
+
try {
const api = getElectronAPI();
if (!api?.autoMode) {
@@ -175,8 +202,8 @@ export function useAutoMode() {
const result = await api.autoMode.stop();
if (result.success) {
- setAutoModeRunning(false);
- clearRunningTasks();
+ setAutoModeRunning(currentProject.id, false);
+ clearRunningTasks(currentProject.id);
console.log("[AutoMode] Stopped successfully");
} else {
console.error("[AutoMode] Failed to stop:", result.error);
@@ -186,11 +213,16 @@ export function useAutoMode() {
console.error("[AutoMode] Error stopping:", error);
throw error;
}
- }, [setAutoModeRunning, clearRunningTasks]);
+ }, [currentProject, setAutoModeRunning, clearRunningTasks]);
// Stop a specific feature
const stopFeature = useCallback(
async (featureId: string) => {
+ if (!currentProject) {
+ console.error("No project selected");
+ return;
+ }
+
try {
const api = getElectronAPI();
if (!api?.autoMode?.stopFeature) {
@@ -200,7 +232,7 @@ export function useAutoMode() {
const result = await api.autoMode.stopFeature(featureId);
if (result.success) {
- removeRunningTask(featureId);
+ removeRunningTask(currentProject.id, featureId);
console.log("[AutoMode] Feature stopped successfully:", featureId);
addAutoModeActivity({
featureId,
@@ -217,7 +249,7 @@ export function useAutoMode() {
throw error;
}
},
- [removeRunningTask, addAutoModeActivity]
+ [currentProject, removeRunningTask, addAutoModeActivity]
);
return {
diff --git a/app/src/hooks/use-keyboard-shortcuts.ts b/app/src/hooks/use-keyboard-shortcuts.ts
index 11fab3c1..34ebe104 100644
--- a/app/src/hooks/use-keyboard-shortcuts.ts
+++ b/app/src/hooks/use-keyboard-shortcuts.ts
@@ -102,7 +102,7 @@ export function useKeyboardShortcuts(shortcuts: KeyboardShortcut[]) {
export const NAV_SHORTCUTS: Record
= {
board: "K", // K for Kanban
agent: "A", // A for Agent
- spec: "E", // E for Editor (Spec)
+ spec: "D", // D for Document (Spec)
context: "C", // C for Context
tools: "T", // T for Tools
settings: "S", // S for Settings
@@ -121,8 +121,10 @@ export const UI_SHORTCUTS: Record = {
export const ACTION_SHORTCUTS: Record = {
addFeature: "N", // N for New feature
addContextFile: "F", // F for File (add context file)
- startNext: "Q", // Q for Queue (start next features from backlog)
+ startNext: "G", // G for Grab (start next features from backlog)
newSession: "W", // W for new session (in agent view)
openProject: "O", // O for Open project (navigate to welcome view)
projectPicker: "P", // P for Project picker
+ cyclePrevProject: "Q", // Q for previous project (cycle back through MRU)
+ cycleNextProject: "E", // E for next project (cycle forward through MRU)
};
diff --git a/app/src/lib/electron.ts b/app/src/lib/electron.ts
index ccadd497..d3162eb0 100644
--- a/app/src/lib/electron.ts
+++ b/app/src/lib/electron.ts
@@ -47,6 +47,7 @@ export type AutoModePhase = "planning" | "action" | "verification";
export interface AutoModeEvent {
type: "auto_mode_feature_start" | "auto_mode_progress" | "auto_mode_tool" | "auto_mode_feature_complete" | "auto_mode_error" | "auto_mode_complete" | "auto_mode_phase";
featureId?: string;
+ projectId?: string;
feature?: object;
content?: string;
tool?: string;
@@ -57,6 +58,47 @@ export interface AutoModeEvent {
phase?: AutoModePhase;
}
+// Feature Suggestions types
+export interface FeatureSuggestion {
+ id: string;
+ category: string;
+ description: string;
+ steps: string[];
+ priority: number;
+ reasoning: string;
+}
+
+export interface SuggestionsEvent {
+ type: "suggestions_progress" | "suggestions_tool" | "suggestions_complete" | "suggestions_error";
+ content?: string;
+ tool?: string;
+ input?: unknown;
+ suggestions?: FeatureSuggestion[];
+ error?: string;
+}
+
+export interface SuggestionsAPI {
+ generate: (projectPath: string) => Promise<{ success: boolean; error?: string }>;
+ stop: () => Promise<{ success: boolean; error?: string }>;
+ status: () => Promise<{ success: boolean; isRunning?: boolean; error?: string }>;
+ onEvent: (callback: (event: SuggestionsEvent) => void) => () => void;
+}
+
+// Spec Regeneration types
+export type SpecRegenerationEvent =
+ | { type: "spec_regeneration_progress"; content: string }
+ | { type: "spec_regeneration_tool"; tool: string; input: unknown }
+ | { type: "spec_regeneration_complete"; message: string }
+ | { type: "spec_regeneration_error"; error: string };
+
+export interface SpecRegenerationAPI {
+ create: (projectPath: string, projectOverview: string, generateFeatures?: boolean) => Promise<{ success: boolean; error?: string }>;
+ generate: (projectPath: string, projectDefinition: string) => Promise<{ success: boolean; error?: string }>;
+ stop: () => Promise<{ success: boolean; error?: string }>;
+ status: () => Promise<{ success: boolean; isRunning?: boolean; error?: string }>;
+ onEvent: (callback: (event: SpecRegenerationEvent) => void) => () => void;
+}
+
export interface AutoModeAPI {
start: (projectPath: string, maxConcurrency?: number) => Promise<{ success: boolean; error?: string }>;
stop: () => Promise<{ success: boolean; error?: string }>;
@@ -93,12 +135,15 @@ export interface ElectronAPI {
getPath: (name: string) => Promise;
saveImageToTemp?: (data: string, filename: string, mimeType: string) => Promise;
autoMode?: AutoModeAPI;
+ suggestions?: SuggestionsAPI;
+ specRegeneration?: SpecRegenerationAPI;
}
+// Augment global Window interface
declare global {
interface Window {
- electronAPI?: ElectronAPI;
- isElectron?: boolean;
+ electronAPI: ElectronAPI | undefined;
+ isElectron: boolean | undefined;
}
}
@@ -365,6 +410,12 @@ export const getElectronAPI = (): ElectronAPI => {
// Mock Auto Mode API
autoMode: createMockAutoModeAPI(),
+
+ // Mock Suggestions API
+ suggestions: createMockSuggestionsAPI(),
+
+ // Mock Spec Regeneration API
+ specRegeneration: createMockSpecRegenerationAPI(),
};
};
@@ -768,6 +819,381 @@ function delay(ms: number, featureId: string): Promise {
});
}
+// Mock Suggestions state and implementation
+let mockSuggestionsRunning = false;
+let mockSuggestionsCallbacks: ((event: SuggestionsEvent) => void)[] = [];
+let mockSuggestionsTimeout: NodeJS.Timeout | null = null;
+
+function createMockSuggestionsAPI(): SuggestionsAPI {
+ return {
+ generate: async (projectPath: string) => {
+ if (mockSuggestionsRunning) {
+ return { success: false, error: "Suggestions generation is already running" };
+ }
+
+ mockSuggestionsRunning = true;
+ console.log(`[Mock] Generating suggestions for: ${projectPath}`);
+
+ // Simulate async suggestion generation
+ simulateSuggestionsGeneration();
+
+ return { success: true };
+ },
+
+ stop: async () => {
+ mockSuggestionsRunning = false;
+ if (mockSuggestionsTimeout) {
+ clearTimeout(mockSuggestionsTimeout);
+ mockSuggestionsTimeout = null;
+ }
+ return { success: true };
+ },
+
+ status: async () => {
+ return {
+ success: true,
+ isRunning: mockSuggestionsRunning,
+ };
+ },
+
+ onEvent: (callback: (event: SuggestionsEvent) => void) => {
+ mockSuggestionsCallbacks.push(callback);
+ return () => {
+ mockSuggestionsCallbacks = mockSuggestionsCallbacks.filter(cb => cb !== callback);
+ };
+ },
+ };
+}
+
+function emitSuggestionsEvent(event: SuggestionsEvent) {
+ mockSuggestionsCallbacks.forEach(cb => cb(event));
+}
+
+async function simulateSuggestionsGeneration() {
+ // Emit progress events
+ emitSuggestionsEvent({
+ type: "suggestions_progress",
+ content: "Starting project analysis...\n",
+ });
+
+ await new Promise(resolve => {
+ mockSuggestionsTimeout = setTimeout(resolve, 500);
+ });
+ if (!mockSuggestionsRunning) return;
+
+ emitSuggestionsEvent({
+ type: "suggestions_tool",
+ tool: "Glob",
+ input: { pattern: "**/*.{ts,tsx,js,jsx}" },
+ });
+
+ await new Promise(resolve => {
+ mockSuggestionsTimeout = setTimeout(resolve, 500);
+ });
+ if (!mockSuggestionsRunning) return;
+
+ emitSuggestionsEvent({
+ type: "suggestions_progress",
+ content: "Analyzing codebase structure...\n",
+ });
+
+ await new Promise(resolve => {
+ mockSuggestionsTimeout = setTimeout(resolve, 500);
+ });
+ if (!mockSuggestionsRunning) return;
+
+ emitSuggestionsEvent({
+ type: "suggestions_progress",
+ content: "Identifying missing features...\n",
+ });
+
+ await new Promise(resolve => {
+ mockSuggestionsTimeout = setTimeout(resolve, 500);
+ });
+ if (!mockSuggestionsRunning) return;
+
+ // Generate mock suggestions
+ const mockSuggestions: FeatureSuggestion[] = [
+ {
+ id: `suggestion-${Date.now()}-0`,
+ category: "User Experience",
+ description: "Add dark mode toggle with system preference detection",
+ steps: [
+ "Create a ThemeProvider context to manage theme state",
+ "Add a toggle component in the settings or header",
+ "Implement CSS variables for theme colors",
+ "Add localStorage persistence for user preference"
+ ],
+ priority: 1,
+ reasoning: "Dark mode is a standard feature that improves accessibility and user comfort"
+ },
+ {
+ id: `suggestion-${Date.now()}-1`,
+ category: "Performance",
+ description: "Implement lazy loading for heavy components",
+ steps: [
+ "Identify components that are heavy or rarely used",
+ "Use React.lazy() and Suspense for code splitting",
+ "Add loading states for lazy-loaded components"
+ ],
+ priority: 2,
+ reasoning: "Improves initial load time and reduces bundle size"
+ },
+ {
+ id: `suggestion-${Date.now()}-2`,
+ category: "Accessibility",
+ description: "Add keyboard navigation support throughout the app",
+ steps: [
+ "Implement focus management for modals and dialogs",
+ "Add keyboard shortcuts for common actions",
+ "Ensure all interactive elements are focusable",
+ "Add ARIA labels and roles where needed"
+ ],
+ priority: 3,
+ reasoning: "Improves accessibility for users who rely on keyboard navigation"
+ },
+ {
+ id: `suggestion-${Date.now()}-3`,
+ category: "Testing",
+ description: "Add comprehensive unit test coverage",
+ steps: [
+ "Set up Jest and React Testing Library",
+ "Create tests for all utility functions",
+ "Add component tests for critical UI elements",
+ "Set up CI pipeline for automated testing"
+ ],
+ priority: 4,
+ reasoning: "Ensures code quality and prevents regressions"
+ },
+ {
+ id: `suggestion-${Date.now()}-4`,
+ category: "Developer Experience",
+ description: "Add Storybook for component documentation",
+ steps: [
+ "Install and configure Storybook",
+ "Create stories for all UI components",
+ "Add interaction tests using play functions",
+ "Set up Chromatic for visual regression testing"
+ ],
+ priority: 5,
+ reasoning: "Improves component development workflow and documentation"
+ }
+ ];
+
+ emitSuggestionsEvent({
+ type: "suggestions_complete",
+ suggestions: mockSuggestions,
+ });
+
+ mockSuggestionsRunning = false;
+ mockSuggestionsTimeout = null;
+}
+
+// Mock Spec Regeneration state and implementation
+let mockSpecRegenerationRunning = false;
+let mockSpecRegenerationCallbacks: ((event: SpecRegenerationEvent) => void)[] = [];
+let mockSpecRegenerationTimeout: NodeJS.Timeout | null = null;
+
+function createMockSpecRegenerationAPI(): SpecRegenerationAPI {
+ return {
+ create: async (projectPath: string, projectOverview: string, generateFeatures = true) => {
+ if (mockSpecRegenerationRunning) {
+ return { success: false, error: "Spec creation is already running" };
+ }
+
+ mockSpecRegenerationRunning = true;
+ console.log(`[Mock] Creating initial spec for: ${projectPath}, generateFeatures: ${generateFeatures}`);
+
+ // Simulate async spec creation
+ simulateSpecCreation(projectPath, projectOverview, generateFeatures);
+
+ return { success: true };
+ },
+
+ generate: async (projectPath: string, projectDefinition: string) => {
+ if (mockSpecRegenerationRunning) {
+ return { success: false, error: "Spec regeneration is already running" };
+ }
+
+ mockSpecRegenerationRunning = true;
+ console.log(`[Mock] Regenerating spec for: ${projectPath}`);
+
+ // Simulate async spec regeneration
+ simulateSpecRegeneration(projectPath, projectDefinition);
+
+ return { success: true };
+ },
+
+ stop: async () => {
+ mockSpecRegenerationRunning = false;
+ if (mockSpecRegenerationTimeout) {
+ clearTimeout(mockSpecRegenerationTimeout);
+ mockSpecRegenerationTimeout = null;
+ }
+ return { success: true };
+ },
+
+ status: async () => {
+ return {
+ success: true,
+ isRunning: mockSpecRegenerationRunning,
+ };
+ },
+
+ onEvent: (callback: (event: SpecRegenerationEvent) => void) => {
+ mockSpecRegenerationCallbacks.push(callback);
+ return () => {
+ mockSpecRegenerationCallbacks = mockSpecRegenerationCallbacks.filter(cb => cb !== callback);
+ };
+ },
+ };
+}
+
+function emitSpecRegenerationEvent(event: SpecRegenerationEvent) {
+ mockSpecRegenerationCallbacks.forEach(cb => cb(event));
+}
+
+async function simulateSpecCreation(projectPath: string, projectOverview: string, generateFeatures = true) {
+ emitSpecRegenerationEvent({
+ type: "spec_regeneration_progress",
+ content: "Starting project analysis...\n",
+ });
+
+ await new Promise(resolve => {
+ mockSpecRegenerationTimeout = setTimeout(resolve, 500);
+ });
+ if (!mockSpecRegenerationRunning) return;
+
+ emitSpecRegenerationEvent({
+ type: "spec_regeneration_tool",
+ tool: "Glob",
+ input: { pattern: "**/*.{json,ts,tsx}" },
+ });
+
+ await new Promise(resolve => {
+ mockSpecRegenerationTimeout = setTimeout(resolve, 500);
+ });
+ if (!mockSpecRegenerationRunning) return;
+
+ emitSpecRegenerationEvent({
+ type: "spec_regeneration_progress",
+ content: "Detecting tech stack...\n",
+ });
+
+ await new Promise(resolve => {
+ mockSpecRegenerationTimeout = setTimeout(resolve, 500);
+ });
+ if (!mockSpecRegenerationRunning) return;
+
+ // Write mock app_spec.txt
+ mockFileSystem[`${projectPath}/.automaker/app_spec.txt`] = `
+ Demo Project
+
+
+ ${projectOverview}
+
+
+
+
+ Next.js
+ React
+ Tailwind CSS
+
+
+
+
+ Core functionality based on overview
+
+
+
+ Setup and basic structure
+ Core features implementation
+
+`;
+
+ // If generateFeatures is true, also write feature_list.json
+ if (generateFeatures) {
+ const timestamp = Date.now();
+ mockFileSystem[`${projectPath}/.automaker/feature_list.json`] = JSON.stringify([
+ {
+ id: `feature-${timestamp}-0`,
+ category: "Phase 1: Foundation",
+ description: "Setup and basic structure",
+ status: "backlog",
+ steps: ["Initialize project", "Configure dependencies"],
+ skipTests: true,
+ },
+ {
+ id: `feature-${timestamp}-1`,
+ category: "Phase 2: Core Logic",
+ description: "Core features implementation",
+ status: "backlog",
+ steps: ["Implement core functionality", "Add business logic"],
+ skipTests: true,
+ },
+ ], null, 2);
+ }
+
+ emitSpecRegenerationEvent({
+ type: "spec_regeneration_complete",
+ message: "Initial spec creation complete!",
+ });
+
+ mockSpecRegenerationRunning = false;
+ mockSpecRegenerationTimeout = null;
+}
+
+async function simulateSpecRegeneration(projectPath: string, projectDefinition: string) {
+ emitSpecRegenerationEvent({
+ type: "spec_regeneration_progress",
+ content: "Starting spec regeneration...\n",
+ });
+
+ await new Promise(resolve => {
+ mockSpecRegenerationTimeout = setTimeout(resolve, 500);
+ });
+ if (!mockSpecRegenerationRunning) return;
+
+ emitSpecRegenerationEvent({
+ type: "spec_regeneration_progress",
+ content: "Analyzing codebase...\n",
+ });
+
+ await new Promise(resolve => {
+ mockSpecRegenerationTimeout = setTimeout(resolve, 500);
+ });
+ if (!mockSpecRegenerationRunning) return;
+
+ // Write regenerated spec
+ mockFileSystem[`${projectPath}/.automaker/app_spec.txt`] = `
+ Regenerated Project
+
+
+ ${projectDefinition}
+
+
+
+
+ Next.js
+ React
+ Tailwind CSS
+
+
+
+
+ Regenerated features based on definition
+
+`;
+
+ emitSpecRegenerationEvent({
+ type: "spec_regeneration_complete",
+ message: "Spec regeneration complete!",
+ });
+
+ mockSpecRegenerationRunning = false;
+ mockSpecRegenerationTimeout = null;
+}
+
// Utility functions for project management
export interface Project {
diff --git a/app/src/lib/project-init.ts b/app/src/lib/project-init.ts
index 7a6563af..ec7b0c85 100644
--- a/app/src/lib/project-init.ts
+++ b/app/src/lib/project-init.ts
@@ -15,31 +15,6 @@ export interface ProjectInitResult {
existingFiles?: string[];
}
-/**
- * Default app_spec.txt template for new projects
- */
-const DEFAULT_APP_SPEC = `
- Untitled Project
-
-
- Describe your project here. This file will be analyzed by an AI agent
- to understand your project structure and tech stack.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`;
-
/**
* Default feature_list.json template for new projects
*/
@@ -47,15 +22,16 @@ const DEFAULT_FEATURE_LIST = JSON.stringify([], null, 2);
/**
* Required files and directories in the .automaker directory
+ * Note: app_spec.txt is NOT created automatically - user must set it up via the spec editor
*/
const REQUIRED_STRUCTURE = {
directories: [
".automaker",
".automaker/context",
".automaker/agents-context",
+ ".automaker/images",
],
files: {
- ".automaker/app_spec.txt": DEFAULT_APP_SPEC,
".automaker/feature_list.json": DEFAULT_FEATURE_LIST,
},
};
@@ -186,3 +162,37 @@ export async function getProjectInitStatus(projectPath: string): Promise<{
};
}
}
+
+/**
+ * Checks if the app_spec.txt file exists for a project
+ *
+ * @param projectPath - The root path of the project
+ * @returns true if app_spec.txt exists
+ */
+export async function hasAppSpec(projectPath: string): Promise {
+ const api = getElectronAPI();
+ try {
+ const fullPath = `${projectPath}/.automaker/app_spec.txt`;
+ return await api.exists(fullPath);
+ } catch (error) {
+ console.error("[project-init] Error checking app_spec.txt:", error);
+ return false;
+ }
+}
+
+/**
+ * Checks if the .automaker directory exists for a project
+ *
+ * @param projectPath - The root path of the project
+ * @returns true if .automaker directory exists
+ */
+export async function hasAutomakerDir(projectPath: string): Promise {
+ const api = getElectronAPI();
+ try {
+ const fullPath = `${projectPath}/.automaker`;
+ return await api.exists(fullPath);
+ } catch (error) {
+ console.error("[project-init] Error checking .automaker dir:", error);
+ return false;
+ }
+}
diff --git a/app/src/store/app-store.ts b/app/src/store/app-store.ts
index 28f90038..47c0c3dc 100644
--- a/app/src/store/app-store.ts
+++ b/app/src/store/app-store.ts
@@ -94,6 +94,8 @@ export interface AppState {
projects: Project[];
currentProject: Project | null;
trashedProjects: TrashedProject[];
+ projectHistory: string[]; // Array of project IDs in MRU order (most recent first)
+ projectHistoryIndex: number; // Current position in project history for cycling
// View state
currentView: ViewMode;
@@ -119,9 +121,11 @@ export interface AppState {
currentChatSession: ChatSession | null;
chatHistoryOpen: boolean;
- // Auto Mode
- isAutoModeRunning: boolean;
- runningAutoTasks: string[]; // Feature IDs being worked on (supports concurrent tasks)
+ // Auto Mode (per-project state, keyed by project ID)
+ autoModeByProject: Record;
autoModeActivityLog: AutoModeActivity[];
maxConcurrency: number; // Maximum number of concurrent agent tasks
@@ -162,6 +166,8 @@ export interface AppActions {
emptyTrash: () => void;
setCurrentProject: (project: Project | null) => void;
reorderProjects: (oldIndex: number, newIndex: number) => void;
+ cyclePrevProject: () => void; // Cycle back through project history (Q)
+ cycleNextProject: () => void; // Cycle forward through project history (E)
// View actions
setCurrentView: (view: ViewMode) => void;
@@ -198,11 +204,12 @@ export interface AppActions {
setChatHistoryOpen: (open: boolean) => void;
toggleChatHistory: () => void;
- // Auto Mode actions
- setAutoModeRunning: (running: boolean) => void;
- addRunningTask: (taskId: string) => void;
- removeRunningTask: (taskId: string) => void;
- clearRunningTasks: () => void;
+ // Auto Mode actions (per-project)
+ setAutoModeRunning: (projectId: string, running: boolean) => void;
+ addRunningTask: (projectId: string, taskId: string) => void;
+ removeRunningTask: (projectId: string, taskId: string) => void;
+ clearRunningTasks: (projectId: string) => void;
+ getAutoModeState: (projectId: string) => { isRunning: boolean; runningTasks: string[] };
addAutoModeActivity: (
activity: Omit
) => void;
@@ -223,6 +230,8 @@ const initialState: AppState = {
projects: [],
currentProject: null,
trashedProjects: [],
+ projectHistory: [],
+ projectHistoryIndex: -1,
currentView: "welcome",
sidebarOpen: true,
theme: "dark",
@@ -236,8 +245,7 @@ const initialState: AppState = {
chatSessions: [],
currentChatSession: null,
chatHistoryOpen: false,
- isAutoModeRunning: false,
- runningAutoTasks: [],
+ autoModeByProject: {},
autoModeActivityLog: [],
maxConcurrency: 3, // Default to 3 concurrent agents
kanbanCardDetailLevel: "standard", // Default to standard detail level
@@ -363,11 +371,59 @@ export const useAppStore = create()(
set({ currentProject: project });
if (project) {
set({ currentView: "board" });
+ // Add to project history (MRU order)
+ const currentHistory = get().projectHistory;
+ // Remove this project if it's already in history
+ const filteredHistory = currentHistory.filter((id) => id !== project.id);
+ // Add to the front (most recent)
+ const newHistory = [project.id, ...filteredHistory];
+ // Reset history index to 0 (current project)
+ set({ projectHistory: newHistory, projectHistoryIndex: 0 });
} else {
set({ currentView: "welcome" });
}
},
+ cyclePrevProject: () => {
+ const { projectHistory, projectHistoryIndex, projects } = get();
+ if (projectHistory.length <= 1) return; // Need at least 2 projects to cycle
+
+ // Move to the next index (going back in history = higher index)
+ const newIndex = (projectHistoryIndex + 1) % projectHistory.length;
+ const targetProjectId = projectHistory[newIndex];
+ const targetProject = projects.find((p) => p.id === targetProjectId);
+
+ if (targetProject) {
+ // Update the index but don't modify history order when cycling
+ set({
+ currentProject: targetProject,
+ projectHistoryIndex: newIndex,
+ currentView: "board"
+ });
+ }
+ },
+
+ cycleNextProject: () => {
+ const { projectHistory, projectHistoryIndex, projects } = get();
+ if (projectHistory.length <= 1) return; // Need at least 2 projects to cycle
+
+ // Move to the previous index (going forward = lower index, wrapping around)
+ const newIndex = projectHistoryIndex <= 0
+ ? projectHistory.length - 1
+ : projectHistoryIndex - 1;
+ const targetProjectId = projectHistory[newIndex];
+ const targetProject = projects.find((p) => p.id === targetProjectId);
+
+ if (targetProject) {
+ // Update the index but don't modify history order when cycling
+ set({
+ currentProject: targetProject,
+ projectHistoryIndex: newIndex,
+ currentView: "board"
+ });
+ }
+ },
+
// View actions
setCurrentView: (view) => set({ currentView: view }),
toggleSidebar: () => set({ sidebarOpen: !get().sidebarOpen }),
@@ -522,25 +578,63 @@ export const useAppStore = create()(
toggleChatHistory: () => set({ chatHistoryOpen: !get().chatHistoryOpen }),
- // Auto Mode actions
- setAutoModeRunning: (running) => set({ isAutoModeRunning: running }),
-
- addRunningTask: (taskId) => {
- const current = get().runningAutoTasks;
- if (!current.includes(taskId)) {
- set({ runningAutoTasks: [...current, taskId] });
- }
- },
-
- removeRunningTask: (taskId) => {
+ // Auto Mode actions (per-project)
+ setAutoModeRunning: (projectId, running) => {
+ const current = get().autoModeByProject;
+ const projectState = current[projectId] || { isRunning: false, runningTasks: [] };
set({
- runningAutoTasks: get().runningAutoTasks.filter(
- (id) => id !== taskId
- ),
+ autoModeByProject: {
+ ...current,
+ [projectId]: { ...projectState, isRunning: running },
+ },
});
},
- clearRunningTasks: () => set({ runningAutoTasks: [] }),
+ addRunningTask: (projectId, taskId) => {
+ const current = get().autoModeByProject;
+ const projectState = current[projectId] || { isRunning: false, runningTasks: [] };
+ if (!projectState.runningTasks.includes(taskId)) {
+ set({
+ autoModeByProject: {
+ ...current,
+ [projectId]: {
+ ...projectState,
+ runningTasks: [...projectState.runningTasks, taskId],
+ },
+ },
+ });
+ }
+ },
+
+ removeRunningTask: (projectId, taskId) => {
+ const current = get().autoModeByProject;
+ const projectState = current[projectId] || { isRunning: false, runningTasks: [] };
+ set({
+ autoModeByProject: {
+ ...current,
+ [projectId]: {
+ ...projectState,
+ runningTasks: projectState.runningTasks.filter((id) => id !== taskId),
+ },
+ },
+ });
+ },
+
+ clearRunningTasks: (projectId) => {
+ const current = get().autoModeByProject;
+ const projectState = current[projectId] || { isRunning: false, runningTasks: [] };
+ set({
+ autoModeByProject: {
+ ...current,
+ [projectId]: { ...projectState, runningTasks: [] },
+ },
+ });
+ },
+
+ getAutoModeState: (projectId) => {
+ const projectState = get().autoModeByProject[projectId];
+ return projectState || { isRunning: false, runningTasks: [] };
+ },
addAutoModeActivity: (activity) => {
const id = `activity-${Date.now()}-${Math.random()
@@ -579,6 +673,8 @@ export const useAppStore = create()(
projects: state.projects,
currentProject: state.currentProject,
trashedProjects: state.trashedProjects,
+ projectHistory: state.projectHistory,
+ projectHistoryIndex: state.projectHistoryIndex,
currentView: state.currentView,
theme: state.theme,
sidebarOpen: state.sidebarOpen,
diff --git a/app/src/types/electron.d.ts b/app/src/types/electron.d.ts
index 7935c869..5d008c9e 100644
--- a/app/src/types/electron.d.ts
+++ b/app/src/types/electron.d.ts
@@ -162,22 +162,26 @@ export type AutoModeEvent =
| {
type: "auto_mode_feature_start";
featureId: string;
+ projectId?: string;
feature: unknown;
}
| {
type: "auto_mode_progress";
featureId: string;
+ projectId?: string;
content: string;
}
| {
type: "auto_mode_tool";
featureId: string;
+ projectId?: string;
tool: string;
input: unknown;
}
| {
type: "auto_mode_feature_complete";
featureId: string;
+ projectId?: string;
passes: boolean;
message: string;
}
@@ -185,18 +189,72 @@ export type AutoModeEvent =
type: "auto_mode_error";
error: string;
featureId?: string;
+ projectId?: string;
}
| {
type: "auto_mode_complete";
message: string;
+ projectId?: string;
}
| {
type: "auto_mode_phase";
featureId: string;
+ projectId?: string;
phase: "planning" | "action" | "verification";
message: string;
};
+export type SpecRegenerationEvent =
+ | {
+ type: "spec_regeneration_progress";
+ content: string;
+ }
+ | {
+ type: "spec_regeneration_tool";
+ tool: string;
+ input: unknown;
+ }
+ | {
+ type: "spec_regeneration_complete";
+ message: string;
+ }
+ | {
+ type: "spec_regeneration_error";
+ error: string;
+ };
+
+export interface SpecRegenerationAPI {
+ create: (
+ projectPath: string,
+ projectOverview: string,
+ generateFeatures?: boolean
+ ) => Promise<{
+ success: boolean;
+ error?: string;
+ }>;
+
+ generate: (
+ projectPath: string,
+ projectDefinition: string
+ ) => Promise<{
+ success: boolean;
+ error?: string;
+ }>;
+
+ stop: () => Promise<{
+ success: boolean;
+ error?: string;
+ }>;
+
+ status: () => Promise<{
+ success: boolean;
+ isRunning?: boolean;
+ error?: string;
+ }>;
+
+ onEvent: (callback: (event: SpecRegenerationEvent) => void) => () => void;
+}
+
export interface AutoModeAPI {
start: (projectPath: string) => Promise<{
success: boolean;
@@ -316,6 +374,9 @@ export interface ElectronAPI {
// Auto Mode APIs
autoMode: AutoModeAPI;
+
+ // Spec Regeneration APIs
+ specRegeneration: SpecRegenerationAPI;
}
declare global {