@evolve.labs/devflow 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/.claude/commands/agents/architect.md +1162 -0
  2. package/.claude/commands/agents/architect.meta.yaml +124 -0
  3. package/.claude/commands/agents/builder.md +1432 -0
  4. package/.claude/commands/agents/builder.meta.yaml +117 -0
  5. package/.claude/commands/agents/chronicler.md +633 -0
  6. package/.claude/commands/agents/chronicler.meta.yaml +217 -0
  7. package/.claude/commands/agents/guardian.md +456 -0
  8. package/.claude/commands/agents/guardian.meta.yaml +127 -0
  9. package/.claude/commands/agents/strategist.md +483 -0
  10. package/.claude/commands/agents/strategist.meta.yaml +158 -0
  11. package/.claude/commands/agents/system-designer.md +1137 -0
  12. package/.claude/commands/agents/system-designer.meta.yaml +156 -0
  13. package/.claude/commands/devflow-help.md +93 -0
  14. package/.claude/commands/devflow-status.md +60 -0
  15. package/.claude/commands/quick/create-adr.md +82 -0
  16. package/.claude/commands/quick/new-feature.md +57 -0
  17. package/.claude/commands/quick/security-check.md +54 -0
  18. package/.claude/commands/quick/system-design.md +58 -0
  19. package/.claude_project +52 -0
  20. package/.devflow/agents/architect.meta.yaml +122 -0
  21. package/.devflow/agents/builder.meta.yaml +116 -0
  22. package/.devflow/agents/chronicler.meta.yaml +222 -0
  23. package/.devflow/agents/guardian.meta.yaml +127 -0
  24. package/.devflow/agents/strategist.meta.yaml +158 -0
  25. package/.devflow/agents/system-designer.meta.yaml +265 -0
  26. package/.devflow/project.yaml +242 -0
  27. package/.gitignore-template +84 -0
  28. package/LICENSE +21 -0
  29. package/README.md +249 -0
  30. package/bin/devflow.js +54 -0
  31. package/lib/autopilot.js +235 -0
  32. package/lib/autopilotConstants.js +213 -0
  33. package/lib/constants.js +95 -0
  34. package/lib/init.js +200 -0
  35. package/lib/update.js +181 -0
  36. package/lib/utils.js +157 -0
  37. package/lib/web.js +119 -0
  38. package/package.json +57 -0
  39. package/web/CHANGELOG.md +192 -0
  40. package/web/README.md +156 -0
  41. package/web/app/api/autopilot/execute/route.ts +102 -0
  42. package/web/app/api/autopilot/terminal-execute/route.ts +124 -0
  43. package/web/app/api/files/route.ts +280 -0
  44. package/web/app/api/files/tree/route.ts +160 -0
  45. package/web/app/api/git/route.ts +201 -0
  46. package/web/app/api/health/route.ts +94 -0
  47. package/web/app/api/project/open/route.ts +134 -0
  48. package/web/app/api/search/route.ts +247 -0
  49. package/web/app/api/specs/route.ts +405 -0
  50. package/web/app/api/terminal/route.ts +222 -0
  51. package/web/app/globals.css +160 -0
  52. package/web/app/ide/layout.tsx +43 -0
  53. package/web/app/ide/page.tsx +216 -0
  54. package/web/app/layout.tsx +34 -0
  55. package/web/app/page.tsx +303 -0
  56. package/web/components/agents/AgentIcons.tsx +281 -0
  57. package/web/components/autopilot/AutopilotConfigModal.tsx +245 -0
  58. package/web/components/autopilot/AutopilotPanel.tsx +299 -0
  59. package/web/components/dashboard/DashboardPanel.tsx +393 -0
  60. package/web/components/editor/Breadcrumbs.tsx +134 -0
  61. package/web/components/editor/EditorPanel.tsx +120 -0
  62. package/web/components/editor/EditorTabs.tsx +229 -0
  63. package/web/components/editor/MarkdownPreview.tsx +154 -0
  64. package/web/components/editor/MermaidDiagram.tsx +113 -0
  65. package/web/components/editor/MonacoEditor.tsx +177 -0
  66. package/web/components/editor/TabContextMenu.tsx +207 -0
  67. package/web/components/git/GitPanel.tsx +534 -0
  68. package/web/components/layout/Shell.tsx +15 -0
  69. package/web/components/layout/StatusBar.tsx +100 -0
  70. package/web/components/modals/CommandPalette.tsx +393 -0
  71. package/web/components/modals/GlobalSearch.tsx +348 -0
  72. package/web/components/modals/QuickOpen.tsx +241 -0
  73. package/web/components/modals/RecentFiles.tsx +208 -0
  74. package/web/components/projects/ProjectSelector.tsx +147 -0
  75. package/web/components/settings/SettingItem.tsx +150 -0
  76. package/web/components/settings/SettingsPanel.tsx +323 -0
  77. package/web/components/specs/SpecsPanel.tsx +1091 -0
  78. package/web/components/terminal/TerminalPanel.tsx +683 -0
  79. package/web/components/ui/ContextMenu.tsx +182 -0
  80. package/web/components/ui/LoadingSpinner.tsx +66 -0
  81. package/web/components/ui/ResizeHandle.tsx +110 -0
  82. package/web/components/ui/Skeleton.tsx +108 -0
  83. package/web/components/ui/SkipLinks.tsx +37 -0
  84. package/web/components/ui/Toaster.tsx +57 -0
  85. package/web/hooks/useFocusTrap.ts +141 -0
  86. package/web/hooks/useKeyboardShortcuts.ts +169 -0
  87. package/web/hooks/useListNavigation.ts +237 -0
  88. package/web/lib/autopilotConstants.ts +213 -0
  89. package/web/lib/constants/agents.ts +67 -0
  90. package/web/lib/git.ts +339 -0
  91. package/web/lib/ptyManager.ts +191 -0
  92. package/web/lib/specsParser.ts +299 -0
  93. package/web/lib/stores/autopilotStore.ts +288 -0
  94. package/web/lib/stores/fileStore.ts +550 -0
  95. package/web/lib/stores/gitStore.ts +386 -0
  96. package/web/lib/stores/projectStore.ts +196 -0
  97. package/web/lib/stores/settingsStore.ts +126 -0
  98. package/web/lib/stores/specsStore.ts +297 -0
  99. package/web/lib/stores/uiStore.ts +175 -0
  100. package/web/lib/types/index.ts +177 -0
  101. package/web/lib/utils.ts +98 -0
  102. package/web/next.config.js +50 -0
  103. package/web/package.json +54 -0
  104. package/web/postcss.config.js +6 -0
  105. package/web/tailwind.config.ts +68 -0
  106. package/web/tsconfig.json +41 -0
@@ -0,0 +1,297 @@
1
+ import { create } from 'zustand';
2
+ import type { Spec, Requirement, DesignDecision, Task, SpecPhase } from '@/lib/types';
3
+
4
+ // Progress info for a spec
5
+ export interface SpecProgress {
6
+ total: number;
7
+ completed: number;
8
+ inProgress: number;
9
+ percentage: number;
10
+ status: 'not_started' | 'in_progress' | 'completed';
11
+ }
12
+
13
+ interface SpecsState {
14
+ // State
15
+ specs: Spec[];
16
+ requirements: Requirement[];
17
+ decisions: DesignDecision[];
18
+ tasks: Task[];
19
+ isLoading: boolean;
20
+ error: string | null;
21
+ selectedSpecId: string | null;
22
+ activePhase: SpecPhase;
23
+ filterProject: string | null; // null = show all
24
+
25
+ // Actions
26
+ loadSpecs: (projectPaths: string[]) => Promise<void>;
27
+ createSpec: (projectPath: string, data: CreateSpecData) => Promise<string | null>;
28
+ setSelectedSpec: (id: string | null) => void;
29
+ setActivePhase: (phase: SpecPhase) => void;
30
+ setFilterProject: (projectPath: string | null) => void;
31
+ updateTaskStatus: (taskId: string, status: Task['status']) => Promise<void>;
32
+ getSpecsByPhase: (phase: SpecPhase) => Spec[];
33
+ getRequirementsBySpec: (specId: string) => Requirement[];
34
+ getTasksBySpec: (specId: string) => Task[];
35
+ getDecisionsBySpec: (specId: string) => DesignDecision[];
36
+ getSpecProgress: (specId: string) => SpecProgress;
37
+ getFilteredRequirements: () => Requirement[];
38
+ getFilteredDecisions: () => DesignDecision[];
39
+ getFilteredTasks: () => Task[];
40
+ }
41
+
42
+ interface CreateSpecData {
43
+ type: 'story' | 'adr' | 'spec';
44
+ title: string;
45
+ description?: string;
46
+ priority?: string;
47
+ phase?: SpecPhase;
48
+ }
49
+
50
+ function basename(p: string): string {
51
+ return p.split('/').pop() || p;
52
+ }
53
+
54
+ export const useSpecsStore = create<SpecsState>((set, get) => ({
55
+ specs: [],
56
+ requirements: [],
57
+ decisions: [],
58
+ tasks: [],
59
+ isLoading: false,
60
+ error: null,
61
+ selectedSpecId: null,
62
+ activePhase: 'requirements',
63
+ filterProject: null,
64
+
65
+ loadSpecs: async (projectPaths: string[]) => {
66
+ if (projectPaths.length === 0) return;
67
+
68
+ set({ isLoading: true, error: null });
69
+
70
+ try {
71
+ // Fetch specs from all projects in parallel
72
+ const results = await Promise.all(
73
+ projectPaths.map(async (projectPath) => {
74
+ const response = await fetch(
75
+ `/api/specs?projectPath=${encodeURIComponent(projectPath)}`
76
+ );
77
+
78
+ if (!response.ok) {
79
+ console.error(`Failed to load specs from ${basename(projectPath)}`);
80
+ return null;
81
+ }
82
+
83
+ const data = await response.json();
84
+ const projectName = basename(projectPath);
85
+
86
+ // Tag each item with sourceProject
87
+ return {
88
+ specs: (data.specs || []).map((s: Spec) => ({ ...s, sourceProject: projectPath })),
89
+ requirements: (data.requirements || []).map((r: Requirement) => ({ ...r, sourceProject: projectPath })),
90
+ decisions: (data.decisions || []).map((d: DesignDecision) => ({ ...d, sourceProject: projectPath })),
91
+ tasks: (data.tasks || []).map((t: Task) => ({ ...t, sourceProject: projectPath })),
92
+ };
93
+ })
94
+ );
95
+
96
+ // Merge results from all projects
97
+ const allSpecs: Spec[] = [];
98
+ const allRequirements: Requirement[] = [];
99
+ const allDecisions: DesignDecision[] = [];
100
+ const allTasks: Task[] = [];
101
+
102
+ for (const result of results) {
103
+ if (!result) continue;
104
+ allSpecs.push(...result.specs);
105
+ allRequirements.push(...result.requirements);
106
+ allDecisions.push(...result.decisions);
107
+ allTasks.push(...result.tasks);
108
+ }
109
+
110
+ // Deduplicate by composite key (sourceProject + id)
111
+ const dedup = <T extends { id: string; sourceProject?: string }>(items: T[]) =>
112
+ [...new Map(items.map(i => [`${i.sourceProject || ''}:${i.id}`, i])).values()];
113
+
114
+ set({
115
+ specs: dedup(allSpecs),
116
+ requirements: dedup(allRequirements),
117
+ decisions: dedup(allDecisions),
118
+ tasks: dedup(allTasks),
119
+ isLoading: false,
120
+ });
121
+ } catch (error) {
122
+ console.error('Error loading specs:', error);
123
+ set({
124
+ error: error instanceof Error ? error.message : 'Failed to load specs',
125
+ isLoading: false,
126
+ });
127
+ }
128
+ },
129
+
130
+ createSpec: async (projectPath: string, data: CreateSpecData) => {
131
+ try {
132
+ const response = await fetch('/api/specs', {
133
+ method: 'POST',
134
+ headers: { 'Content-Type': 'application/json' },
135
+ body: JSON.stringify({
136
+ projectPath,
137
+ ...data,
138
+ }),
139
+ });
140
+
141
+ if (!response.ok) {
142
+ throw new Error('Failed to create spec');
143
+ }
144
+
145
+ const result = await response.json();
146
+
147
+ // Reload specs from all projects that were previously loaded
148
+ const currentPaths = [...new Set(get().specs.map(s => s.sourceProject).filter(Boolean))] as string[];
149
+ if (!currentPaths.includes(projectPath)) {
150
+ currentPaths.push(projectPath);
151
+ }
152
+ await get().loadSpecs(currentPaths);
153
+
154
+ return result.id;
155
+ } catch (error) {
156
+ console.error('Error creating spec:', error);
157
+ set({
158
+ error: error instanceof Error ? error.message : 'Failed to create spec',
159
+ });
160
+ return null;
161
+ }
162
+ },
163
+
164
+ setSelectedSpec: (id: string | null) => {
165
+ set({ selectedSpecId: id });
166
+ },
167
+
168
+ setActivePhase: (phase: SpecPhase) => {
169
+ set({ activePhase: phase });
170
+ },
171
+
172
+ setFilterProject: (projectPath: string | null) => {
173
+ set({ filterProject: projectPath });
174
+ },
175
+
176
+ updateTaskStatus: async (taskId: string, status: Task['status']) => {
177
+ const { tasks } = get();
178
+ const task = tasks.find((t) => t.id === taskId);
179
+
180
+ if (!task) return;
181
+
182
+ // Update local state immediately for responsive UI
183
+ set((state) => ({
184
+ tasks: state.tasks.map((t) =>
185
+ t.id === taskId
186
+ ? {
187
+ ...t,
188
+ status,
189
+ completedAt: status === 'completed' ? new Date() : undefined,
190
+ }
191
+ : t
192
+ ),
193
+ }));
194
+
195
+ // Persist to file if we have filePath
196
+ if (task.filePath) {
197
+ try {
198
+ const response = await fetch('/api/specs', {
199
+ method: 'PATCH',
200
+ headers: { 'Content-Type': 'application/json' },
201
+ body: JSON.stringify({
202
+ filePath: task.filePath,
203
+ taskTitle: task.title,
204
+ completed: status === 'completed',
205
+ }),
206
+ });
207
+
208
+ if (!response.ok) {
209
+ console.error('Failed to persist task status');
210
+ // Revert on failure
211
+ set((state) => ({
212
+ tasks: state.tasks.map((t) =>
213
+ t.id === taskId
214
+ ? {
215
+ ...t,
216
+ status: task.status,
217
+ completedAt: task.completedAt,
218
+ }
219
+ : t
220
+ ),
221
+ }));
222
+ }
223
+ } catch (error) {
224
+ console.error('Error persisting task status:', error);
225
+ }
226
+ }
227
+ },
228
+
229
+ getSpecsByPhase: (phase: SpecPhase) => {
230
+ const { specs, filterProject } = get();
231
+ return specs.filter((spec) =>
232
+ spec.phase === phase && (!filterProject || spec.sourceProject === filterProject)
233
+ );
234
+ },
235
+
236
+ getRequirementsBySpec: (specId: string) => {
237
+ const { requirements } = get();
238
+ return requirements.filter((req) => req.specId === specId);
239
+ },
240
+
241
+ getTasksBySpec: (specId: string) => {
242
+ const { tasks } = get();
243
+ return tasks.filter((task) => task.specId === specId);
244
+ },
245
+
246
+ getDecisionsBySpec: (specId: string) => {
247
+ const { decisions } = get();
248
+ return decisions.filter((dec) => dec.specId === specId);
249
+ },
250
+
251
+ getSpecProgress: (specId: string) => {
252
+ const { tasks } = get();
253
+ const specTasks = tasks.filter((task) => task.specId === specId);
254
+
255
+ if (specTasks.length === 0) {
256
+ return {
257
+ total: 0,
258
+ completed: 0,
259
+ inProgress: 0,
260
+ percentage: 0,
261
+ status: 'not_started' as const,
262
+ };
263
+ }
264
+
265
+ const completed = specTasks.filter((t) => t.status === 'completed').length;
266
+ const inProgress = specTasks.filter((t) => t.status === 'in_progress').length;
267
+ const total = specTasks.length;
268
+ const percentage = Math.round((completed / total) * 100);
269
+
270
+ let status: 'not_started' | 'in_progress' | 'completed' = 'not_started';
271
+ if (completed === total) {
272
+ status = 'completed';
273
+ } else if (completed > 0 || inProgress > 0) {
274
+ status = 'in_progress';
275
+ }
276
+
277
+ return { total, completed, inProgress, percentage, status };
278
+ },
279
+
280
+ getFilteredRequirements: () => {
281
+ const { requirements, filterProject } = get();
282
+ if (!filterProject) return requirements;
283
+ return requirements.filter(r => r.sourceProject === filterProject);
284
+ },
285
+
286
+ getFilteredDecisions: () => {
287
+ const { decisions, filterProject } = get();
288
+ if (!filterProject) return decisions;
289
+ return decisions.filter(d => d.sourceProject === filterProject);
290
+ },
291
+
292
+ getFilteredTasks: () => {
293
+ const { tasks, filterProject } = get();
294
+ if (!filterProject) return tasks;
295
+ return tasks.filter(t => t.sourceProject === filterProject);
296
+ },
297
+ }));
@@ -0,0 +1,175 @@
1
+ import { create } from 'zustand';
2
+ import { persist } from 'zustand/middleware';
3
+ import type { ModelId, AutopilotConfig } from '@/lib/types';
4
+
5
+ type Theme = 'light' | 'dark' | 'system';
6
+ type SidebarPanel = 'specs' | 'git' | 'dashboard';
7
+ type RightPanel = 'chat' | 'tasks';
8
+ type ModalType = 'quickOpen' | 'globalSearch' | 'commandPalette' | 'recentFiles' | null;
9
+
10
+ interface UIState {
11
+ // State
12
+ theme: Theme;
13
+ sidebarVisible: boolean;
14
+ sidebarWidth: number;
15
+ chatPanelVisible: boolean;
16
+ chatPanelWidth: number;
17
+ activePanel: SidebarPanel;
18
+ activeRightPanel: RightPanel;
19
+ terminalVisible: boolean;
20
+ terminalHeight: number;
21
+ terminalMaximized: boolean;
22
+ previewVisible: boolean;
23
+ specsPanelVisible: boolean;
24
+ specsPanelWidth: number;
25
+
26
+ // Modal State
27
+ activeModal: ModalType;
28
+
29
+ // Model & Autopilot
30
+ selectedModel: ModelId;
31
+ autopilot: AutopilotConfig;
32
+
33
+ // Actions
34
+ setTheme: (theme: Theme) => void;
35
+ toggleSidebar: () => void;
36
+ setSidebarWidth: (width: number) => void;
37
+ toggleChatPanel: () => void;
38
+ setChatPanelWidth: (width: number) => void;
39
+ setActivePanel: (panel: SidebarPanel) => void;
40
+ setActiveRightPanel: (panel: RightPanel) => void;
41
+ toggleTerminal: () => void;
42
+ setTerminalHeight: (height: number) => void;
43
+ toggleTerminalMaximized: () => void;
44
+ togglePreview: () => void;
45
+ toggleSpecsPanel: () => void;
46
+ setSpecsPanelWidth: (width: number) => void;
47
+ setSelectedModel: (model: ModelId) => void;
48
+ setAutopilot: (config: Partial<AutopilotConfig>) => void;
49
+ toggleAutopilot: () => void;
50
+ openModal: (modal: ModalType) => void;
51
+ closeModal: () => void;
52
+ }
53
+
54
+ export const useUIStore = create<UIState>()(
55
+ persist(
56
+ (set) => ({
57
+ theme: 'dark',
58
+ sidebarVisible: true,
59
+ sidebarWidth: 280,
60
+ chatPanelVisible: true,
61
+ chatPanelWidth: 420,
62
+ activePanel: 'specs',
63
+ activeRightPanel: 'chat',
64
+ terminalVisible: false,
65
+ terminalHeight: 200,
66
+ terminalMaximized: false,
67
+ previewVisible: false,
68
+ specsPanelVisible: true,
69
+ specsPanelWidth: 300,
70
+
71
+ // Modal state
72
+ activeModal: null,
73
+
74
+ // Default model and autopilot config
75
+ selectedModel: 'claude-sonnet-4',
76
+ autopilot: {
77
+ enabled: false,
78
+ maxIterations: 10,
79
+ pauseOnError: true,
80
+ requireApproval: 'files',
81
+ },
82
+
83
+ setTheme: (theme) => {
84
+ set({ theme });
85
+ if (typeof document !== 'undefined') {
86
+ document.documentElement.classList.remove('light', 'dark');
87
+ if (theme === 'system') {
88
+ const systemTheme = window.matchMedia('(prefers-color-scheme: dark)')
89
+ .matches
90
+ ? 'dark'
91
+ : 'light';
92
+ document.documentElement.classList.add(systemTheme);
93
+ } else {
94
+ document.documentElement.classList.add(theme);
95
+ }
96
+ }
97
+ },
98
+
99
+ toggleSidebar: () => {
100
+ set((state) => ({ sidebarVisible: !state.sidebarVisible }));
101
+ },
102
+
103
+ setSidebarWidth: (width) => {
104
+ set({ sidebarWidth: Math.max(200, Math.min(500, width)) });
105
+ },
106
+
107
+ toggleChatPanel: () => {
108
+ set((state) => ({ chatPanelVisible: !state.chatPanelVisible }));
109
+ },
110
+
111
+ setChatPanelWidth: (width) => {
112
+ set({ chatPanelWidth: Math.max(320, Math.min(600, width)) });
113
+ },
114
+
115
+ setActivePanel: (panel) => {
116
+ set({ activePanel: panel });
117
+ },
118
+
119
+ setActiveRightPanel: (panel) => {
120
+ set({ activeRightPanel: panel });
121
+ },
122
+
123
+ toggleTerminal: () => {
124
+ set((state) => ({ terminalVisible: !state.terminalVisible }));
125
+ },
126
+
127
+ setTerminalHeight: (height) => {
128
+ set({ terminalHeight: Math.max(150, Math.min(600, height)) });
129
+ },
130
+
131
+ toggleTerminalMaximized: () => {
132
+ set((state) => ({ terminalMaximized: !state.terminalMaximized }));
133
+ },
134
+
135
+ togglePreview: () => {
136
+ set((state) => ({ previewVisible: !state.previewVisible }));
137
+ },
138
+
139
+ toggleSpecsPanel: () => {
140
+ set((state) => ({ specsPanelVisible: !state.specsPanelVisible }));
141
+ },
142
+
143
+ setSpecsPanelWidth: (width) => {
144
+ set({ specsPanelWidth: Math.max(250, Math.min(450, width)) });
145
+ },
146
+
147
+ setSelectedModel: (model) => {
148
+ set({ selectedModel: model });
149
+ },
150
+
151
+ setAutopilot: (config) => {
152
+ set((state) => ({
153
+ autopilot: { ...state.autopilot, ...config }
154
+ }));
155
+ },
156
+
157
+ toggleAutopilot: () => {
158
+ set((state) => ({
159
+ autopilot: { ...state.autopilot, enabled: !state.autopilot.enabled }
160
+ }));
161
+ },
162
+
163
+ openModal: (modal) => {
164
+ set({ activeModal: modal });
165
+ },
166
+
167
+ closeModal: () => {
168
+ set({ activeModal: null });
169
+ },
170
+ }),
171
+ {
172
+ name: 'devflow-ui-store',
173
+ }
174
+ )
175
+ );
@@ -0,0 +1,177 @@
1
+ // File types
2
+ export interface FileNode {
3
+ name: string;
4
+ path: string;
5
+ type: 'file' | 'directory';
6
+ extension?: string;
7
+ children?: FileNode[];
8
+ size?: number;
9
+ modifiedAt?: string;
10
+ }
11
+
12
+ export interface OpenFile {
13
+ path: string;
14
+ name: string;
15
+ content: string;
16
+ originalContent: string;
17
+ isDirty: boolean;
18
+ language: string;
19
+ }
20
+
21
+ // Chat types
22
+ export interface ChatImage {
23
+ id: string;
24
+ data: string; // base64 data
25
+ mimeType: string;
26
+ name?: string;
27
+ }
28
+
29
+ export interface ChatMessage {
30
+ id: string;
31
+ role: 'user' | 'assistant';
32
+ content: string;
33
+ images?: ChatImage[];
34
+ agent?: string;
35
+ timestamp: Date;
36
+ isStreaming?: boolean;
37
+ }
38
+
39
+ export interface ChatSession {
40
+ id: string;
41
+ name: string;
42
+ createdAt: Date;
43
+ messageCount: number;
44
+ }
45
+
46
+ // Agent types
47
+ export interface Agent {
48
+ id: string;
49
+ name: string;
50
+ displayName: string;
51
+ icon: string;
52
+ color: string;
53
+ description: string;
54
+ shortDescription: string;
55
+ }
56
+
57
+ // Project types
58
+ export interface ProjectInfo {
59
+ path: string;
60
+ name: string;
61
+ isValid: boolean;
62
+ hasDevflow: boolean;
63
+ hasClaudeProject: boolean;
64
+ stats: ProjectStats;
65
+ }
66
+
67
+ export interface ProjectStats {
68
+ specs: number;
69
+ stories: number;
70
+ adrs: number;
71
+ agents: number;
72
+ }
73
+
74
+ // Health types
75
+ export interface HealthStatus {
76
+ claudeCli: {
77
+ installed: boolean;
78
+ authenticated: boolean;
79
+ version?: string;
80
+ error?: string;
81
+ };
82
+ project: {
83
+ valid: boolean;
84
+ hasDevflow: boolean;
85
+ hasClaudeProject: boolean;
86
+ };
87
+ system: {
88
+ platform: string;
89
+ nodeVersion: string;
90
+ };
91
+ }
92
+
93
+ // API Response types
94
+ export interface ApiResponse<T> {
95
+ success: boolean;
96
+ data?: T;
97
+ error?: string;
98
+ }
99
+
100
+ // Spec types (Kiro-style workflow)
101
+ export type SpecPhase = 'requirements' | 'design' | 'tasks';
102
+
103
+ export interface Spec {
104
+ id: string;
105
+ name: string;
106
+ description: string;
107
+ phase: SpecPhase;
108
+ status: 'draft' | 'approved' | 'implemented';
109
+ createdAt: Date;
110
+ updatedAt: Date;
111
+ filePath?: string;
112
+ sourceProject?: string;
113
+ }
114
+
115
+ export interface Requirement {
116
+ id: string;
117
+ specId: string;
118
+ title: string;
119
+ description: string;
120
+ type: 'functional' | 'non-functional' | 'constraint';
121
+ priority: 'must' | 'should' | 'could' | 'wont';
122
+ acceptanceCriteria: string[];
123
+ status: 'draft' | 'approved' | 'implemented';
124
+ filePath?: string;
125
+ sourceProject?: string;
126
+ }
127
+
128
+ export interface DesignDecision {
129
+ id: string;
130
+ specId: string;
131
+ title: string;
132
+ context: string;
133
+ decision: string;
134
+ consequences: string[];
135
+ alternatives?: string[];
136
+ status: 'proposed' | 'accepted' | 'deprecated';
137
+ filePath?: string;
138
+ sourceProject?: string;
139
+ }
140
+
141
+ // Task types
142
+ export type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'blocked';
143
+ export type TaskPriority = 'low' | 'medium' | 'high' | 'critical';
144
+
145
+ export interface Task {
146
+ id: string;
147
+ specId?: string;
148
+ filePath?: string;
149
+ title: string;
150
+ description: string;
151
+ status: TaskStatus;
152
+ priority: TaskPriority;
153
+ dependencies: string[];
154
+ assignedAgent?: string;
155
+ estimatedTokens?: number;
156
+ createdAt: Date;
157
+ completedAt?: Date;
158
+ sourceProject?: string;
159
+ }
160
+
161
+ // Model types
162
+ export type ModelId = 'claude-sonnet-4' | 'claude-opus-4' | 'auto';
163
+
164
+ export interface ModelConfig {
165
+ id: ModelId;
166
+ name: string;
167
+ description: string;
168
+ icon: string;
169
+ }
170
+
171
+ // Autopilot types
172
+ export interface AutopilotConfig {
173
+ enabled: boolean;
174
+ maxIterations: number;
175
+ pauseOnError: boolean;
176
+ requireApproval: 'none' | 'files' | 'all';
177
+ }