@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,393 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useRef, useMemo } from 'react';
4
+ import { useUIStore } from '@/lib/stores/uiStore';
5
+ import { useProjectStore } from '@/lib/stores/projectStore';
6
+ import {
7
+ Command,
8
+ Search,
9
+ FileText,
10
+ GitBranch,
11
+ Terminal,
12
+ Moon,
13
+ Sun,
14
+ Save,
15
+ X,
16
+ Eye,
17
+ EyeOff,
18
+ LayoutPanelLeft,
19
+ Maximize2,
20
+ Minimize2,
21
+ } from 'lucide-react';
22
+ import { cn } from '@/lib/utils';
23
+ import { useFileStore } from '@/lib/stores/fileStore';
24
+ import { useFocusTrap } from '@/hooks/useFocusTrap';
25
+
26
+ interface CommandItem {
27
+ id: string;
28
+ label: string;
29
+ description?: string;
30
+ icon: React.ReactNode;
31
+ shortcut?: string;
32
+ category: string;
33
+ action: () => void;
34
+ }
35
+
36
+ export function CommandPalette() {
37
+ const [query, setQuery] = useState('');
38
+ const [selectedIndex, setSelectedIndex] = useState(0);
39
+ const inputRef = useRef<HTMLInputElement>(null);
40
+ const listRef = useRef<HTMLDivElement>(null);
41
+ const modalRef = useRef<HTMLDivElement>(null);
42
+
43
+ const { activeModal, closeModal, openModal } = useUIStore();
44
+ const {
45
+ toggleSidebar,
46
+ sidebarVisible,
47
+ toggleTerminal,
48
+ terminalVisible,
49
+ togglePreview,
50
+ previewVisible,
51
+ toggleTerminalMaximized,
52
+ terminalMaximized,
53
+ theme,
54
+ setTheme,
55
+ setActivePanel,
56
+ } = useUIStore();
57
+
58
+ const { currentProject } = useProjectStore();
59
+ const { activeFile, saveFile, openFiles } = useFileStore();
60
+
61
+ const isOpen = activeModal === 'commandPalette';
62
+
63
+ // Focus trap for accessibility
64
+ useFocusTrap(modalRef, isOpen, {
65
+ onEscape: closeModal,
66
+ autoFocus: false, // We handle focus manually to the input
67
+ });
68
+
69
+ // Define all commands
70
+ const commands: CommandItem[] = useMemo(() => [
71
+ // File Commands
72
+ {
73
+ id: 'quick-open',
74
+ label: 'Go to File',
75
+ description: 'Quickly open a file by name',
76
+ icon: <Search className="w-4 h-4" />,
77
+ shortcut: '⌘P',
78
+ category: 'File',
79
+ action: () => {
80
+ closeModal();
81
+ setTimeout(() => openModal('quickOpen'), 50);
82
+ },
83
+ },
84
+ {
85
+ id: 'global-search',
86
+ label: 'Search in Files',
87
+ description: 'Search text in all files',
88
+ icon: <FileText className="w-4 h-4" />,
89
+ shortcut: '⌘⇧F',
90
+ category: 'File',
91
+ action: () => {
92
+ closeModal();
93
+ setTimeout(() => openModal('globalSearch'), 50);
94
+ },
95
+ },
96
+ {
97
+ id: 'save-file',
98
+ label: 'Save File',
99
+ description: 'Save the current file',
100
+ icon: <Save className="w-4 h-4" />,
101
+ shortcut: '⌘S',
102
+ category: 'File',
103
+ action: () => {
104
+ if (activeFile) {
105
+ saveFile(activeFile);
106
+ }
107
+ closeModal();
108
+ },
109
+ },
110
+
111
+ // View Commands
112
+ {
113
+ id: 'toggle-sidebar',
114
+ label: sidebarVisible ? 'Hide Sidebar' : 'Show Sidebar',
115
+ icon: <LayoutPanelLeft className="w-4 h-4" />,
116
+ shortcut: '⌘B',
117
+ category: 'View',
118
+ action: () => {
119
+ toggleSidebar();
120
+ closeModal();
121
+ },
122
+ },
123
+ {
124
+ id: 'toggle-terminal',
125
+ label: terminalVisible ? 'Hide Terminal' : 'Show Terminal',
126
+ icon: <Terminal className="w-4 h-4" />,
127
+ shortcut: '⌘`',
128
+ category: 'View',
129
+ action: () => {
130
+ toggleTerminal();
131
+ closeModal();
132
+ },
133
+ },
134
+ {
135
+ id: 'toggle-terminal-max',
136
+ label: terminalMaximized ? 'Restore Terminal' : 'Maximize Terminal',
137
+ icon: terminalMaximized ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />,
138
+ category: 'View',
139
+ action: () => {
140
+ toggleTerminalMaximized();
141
+ closeModal();
142
+ },
143
+ },
144
+ {
145
+ id: 'toggle-preview',
146
+ label: previewVisible ? 'Hide Preview' : 'Show Preview',
147
+ icon: previewVisible ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />,
148
+ shortcut: '⌘⇧V',
149
+ category: 'View',
150
+ action: () => {
151
+ togglePreview();
152
+ closeModal();
153
+ },
154
+ },
155
+
156
+ // Panel Commands
157
+ {
158
+ id: 'show-git',
159
+ label: 'Show Source Control',
160
+ icon: <GitBranch className="w-4 h-4" />,
161
+ category: 'Panels',
162
+ action: () => {
163
+ setActivePanel('git');
164
+ closeModal();
165
+ },
166
+ },
167
+ {
168
+ id: 'show-specs',
169
+ label: 'Show Specs',
170
+ icon: <FileText className="w-4 h-4" />,
171
+ category: 'Panels',
172
+ action: () => {
173
+ setActivePanel('specs');
174
+ closeModal();
175
+ },
176
+ },
177
+
178
+ // Theme Commands
179
+ {
180
+ id: 'theme-dark',
181
+ label: 'Dark Theme',
182
+ icon: <Moon className="w-4 h-4" />,
183
+ category: 'Preferences',
184
+ action: () => {
185
+ setTheme('dark');
186
+ closeModal();
187
+ },
188
+ },
189
+ {
190
+ id: 'theme-light',
191
+ label: 'Light Theme',
192
+ icon: <Sun className="w-4 h-4" />,
193
+ category: 'Preferences',
194
+ action: () => {
195
+ setTheme('light');
196
+ closeModal();
197
+ },
198
+ },
199
+ ], [
200
+ activeFile, saveFile, sidebarVisible, terminalVisible,
201
+ previewVisible, terminalMaximized, theme, closeModal, openModal,
202
+ toggleSidebar, toggleTerminal, togglePreview,
203
+ toggleTerminalMaximized, setTheme, setActivePanel
204
+ ]);
205
+
206
+ // Filter commands based on query
207
+ const filteredCommands = useMemo(() => {
208
+ if (!query.trim()) return commands;
209
+
210
+ const lowerQuery = query.toLowerCase();
211
+ return commands.filter((cmd) => {
212
+ const searchText = `${cmd.label} ${cmd.description || ''} ${cmd.category}`.toLowerCase();
213
+ return searchText.includes(lowerQuery);
214
+ });
215
+ }, [commands, query]);
216
+
217
+ // Group commands by category
218
+ const groupedCommands = useMemo(() => {
219
+ const groups: Record<string, CommandItem[]> = {};
220
+ for (const cmd of filteredCommands) {
221
+ if (!groups[cmd.category]) {
222
+ groups[cmd.category] = [];
223
+ }
224
+ groups[cmd.category].push(cmd);
225
+ }
226
+ return groups;
227
+ }, [filteredCommands]);
228
+
229
+ // Flatten for keyboard navigation
230
+ const flatCommands = useMemo(() => {
231
+ return Object.values(groupedCommands).flat();
232
+ }, [groupedCommands]);
233
+
234
+ // Focus input when opened
235
+ useEffect(() => {
236
+ if (isOpen) {
237
+ setQuery('');
238
+ setSelectedIndex(0);
239
+ setTimeout(() => inputRef.current?.focus(), 50);
240
+ }
241
+ }, [isOpen]);
242
+
243
+ // Keyboard navigation
244
+ const handleKeyDown = (e: React.KeyboardEvent) => {
245
+ switch (e.key) {
246
+ case 'ArrowDown':
247
+ e.preventDefault();
248
+ setSelectedIndex((i) => Math.min(i + 1, flatCommands.length - 1));
249
+ break;
250
+ case 'ArrowUp':
251
+ e.preventDefault();
252
+ setSelectedIndex((i) => Math.max(i - 1, 0));
253
+ break;
254
+ case 'Enter':
255
+ e.preventDefault();
256
+ if (flatCommands[selectedIndex]) {
257
+ flatCommands[selectedIndex].action();
258
+ }
259
+ break;
260
+ case 'Escape':
261
+ e.preventDefault();
262
+ closeModal();
263
+ break;
264
+ }
265
+ };
266
+
267
+ // Scroll selected item into view
268
+ useEffect(() => {
269
+ const listElement = listRef.current;
270
+ if (!listElement) return;
271
+
272
+ const items = listElement.querySelectorAll('[data-command-item]');
273
+ const selectedElement = items[selectedIndex] as HTMLElement;
274
+ if (selectedElement) {
275
+ selectedElement.scrollIntoView({ block: 'nearest' });
276
+ }
277
+ }, [selectedIndex]);
278
+
279
+ if (!isOpen) return null;
280
+
281
+ let currentIndex = 0;
282
+
283
+ return (
284
+ <div className="fixed inset-0 z-50 flex items-start justify-center pt-[15vh]">
285
+ {/* Backdrop */}
286
+ <div
287
+ className="absolute inset-0 bg-black/60 backdrop-blur-sm"
288
+ onClick={closeModal}
289
+ aria-hidden="true"
290
+ />
291
+
292
+ {/* Modal */}
293
+ <div
294
+ ref={modalRef}
295
+ className="relative w-full max-w-xl bg-[#12121a] border border-white/10 rounded-xl shadow-2xl overflow-hidden"
296
+ role="dialog"
297
+ aria-modal="true"
298
+ aria-label="Command palette"
299
+ >
300
+ {/* Search Input */}
301
+ <div className="flex items-center gap-3 px-4 py-3 border-b border-white/10">
302
+ <Command className="w-5 h-5 text-purple-400" />
303
+ <input
304
+ ref={inputRef}
305
+ type="text"
306
+ value={query}
307
+ onChange={(e) => {
308
+ setQuery(e.target.value);
309
+ setSelectedIndex(0);
310
+ }}
311
+ onKeyDown={handleKeyDown}
312
+ placeholder="Type a command..."
313
+ className="flex-1 bg-transparent text-white placeholder-gray-500 outline-none text-sm"
314
+ autoComplete="off"
315
+ spellCheck={false}
316
+ />
317
+ {query && (
318
+ <button
319
+ onClick={() => setQuery('')}
320
+ className="p-1 hover:bg-white/10 rounded transition-colors"
321
+ >
322
+ <X className="w-4 h-4 text-gray-500" />
323
+ </button>
324
+ )}
325
+ <div className="text-xs text-gray-600 border border-white/10 px-1.5 py-0.5 rounded">
326
+ esc
327
+ </div>
328
+ </div>
329
+
330
+ {/* Commands */}
331
+ <div ref={listRef} className="max-h-[50vh] overflow-y-auto" role="listbox" aria-label="Commands">
332
+ {flatCommands.length === 0 ? (
333
+ <div className="px-4 py-8 text-center text-gray-500 text-sm">
334
+ No commands found
335
+ </div>
336
+ ) : (
337
+ Object.entries(groupedCommands).map(([category, cmds]) => (
338
+ <div key={category}>
339
+ <div className="px-4 py-2 text-xs font-medium text-gray-500 uppercase tracking-wider bg-black/20">
340
+ {category}
341
+ </div>
342
+ {cmds.map((cmd) => {
343
+ const index = currentIndex++;
344
+ const isSelected = index === selectedIndex;
345
+
346
+ return (
347
+ <button
348
+ key={cmd.id}
349
+ data-command-item
350
+ onClick={() => cmd.action()}
351
+ className={cn(
352
+ 'w-full flex items-center gap-3 px-4 py-2.5 text-left transition-colors',
353
+ isSelected
354
+ ? 'bg-purple-500/20 border-l-2 border-purple-500'
355
+ : 'hover:bg-white/5 border-l-2 border-transparent'
356
+ )}
357
+ role="option"
358
+ aria-selected={isSelected}
359
+ tabIndex={isSelected ? 0 : -1}
360
+ >
361
+ <div className="text-gray-400">{cmd.icon}</div>
362
+ <div className="flex-1 min-w-0">
363
+ <div className="text-sm text-white">{cmd.label}</div>
364
+ {cmd.description && (
365
+ <div className="text-xs text-gray-500 truncate">
366
+ {cmd.description}
367
+ </div>
368
+ )}
369
+ </div>
370
+ {cmd.shortcut && (
371
+ <div className="text-xs text-gray-600 font-mono">
372
+ {cmd.shortcut}
373
+ </div>
374
+ )}
375
+ </button>
376
+ );
377
+ })}
378
+ </div>
379
+ ))
380
+ )}
381
+ </div>
382
+
383
+ {/* Footer */}
384
+ <div className="flex items-center justify-between px-4 py-2 border-t border-white/10 text-xs text-gray-500">
385
+ <div className="flex items-center gap-4">
386
+ <span>↑↓ navigate</span>
387
+ <span>↵ execute</span>
388
+ </div>
389
+ </div>
390
+ </div>
391
+ </div>
392
+ );
393
+ }