@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,534 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState, useCallback } from 'react';
4
+ import { useGitStore } from '@/lib/stores/gitStore';
5
+ import { cn } from '@/lib/utils';
6
+ import {
7
+ GitBranch,
8
+ GitCommit,
9
+ Plus,
10
+ Minus,
11
+ Check,
12
+ X,
13
+ RefreshCw,
14
+ Upload,
15
+ Download,
16
+ ChevronDown,
17
+ ChevronRight,
18
+ FileText,
19
+ FilePlus,
20
+ FileX,
21
+ FileEdit,
22
+ RotateCcw,
23
+ AlertCircle,
24
+ } from 'lucide-react';
25
+
26
+ interface GitPanelProps {
27
+ projectPath: string;
28
+ }
29
+
30
+ type FileStatus = 'added' | 'modified' | 'deleted' | 'renamed' | 'copied';
31
+
32
+ function getFileStatusIcon(status: FileStatus) {
33
+ switch (status) {
34
+ case 'added':
35
+ return <FilePlus className="w-4 h-4 text-green-400" />;
36
+ case 'deleted':
37
+ return <FileX className="w-4 h-4 text-red-400" />;
38
+ case 'modified':
39
+ return <FileEdit className="w-4 h-4 text-yellow-400" />;
40
+ case 'renamed':
41
+ case 'copied':
42
+ return <FileText className="w-4 h-4 text-blue-400" />;
43
+ default:
44
+ return <FileText className="w-4 h-4 text-gray-400" />;
45
+ }
46
+ }
47
+
48
+ function getFileStatusBadge(status: FileStatus) {
49
+ const badges: Record<FileStatus, { text: string; color: string }> = {
50
+ added: { text: 'A', color: 'text-green-400 bg-green-400/10' },
51
+ modified: { text: 'M', color: 'text-yellow-400 bg-yellow-400/10' },
52
+ deleted: { text: 'D', color: 'text-red-400 bg-red-400/10' },
53
+ renamed: { text: 'R', color: 'text-blue-400 bg-blue-400/10' },
54
+ copied: { text: 'C', color: 'text-blue-400 bg-blue-400/10' },
55
+ };
56
+
57
+ const badge = badges[status];
58
+ return (
59
+ <span className={cn('text-xs font-mono px-1.5 py-0.5 rounded', badge.color)}>
60
+ {badge.text}
61
+ </span>
62
+ );
63
+ }
64
+
65
+ export function GitPanel({ projectPath }: GitPanelProps) {
66
+ const {
67
+ status,
68
+ branches,
69
+ isLoading,
70
+ error,
71
+ fetchStatus,
72
+ fetchBranches,
73
+ stageFiles,
74
+ unstageFiles,
75
+ stageAll,
76
+ unstageAll,
77
+ commit,
78
+ push,
79
+ pull,
80
+ checkout,
81
+ createBranch,
82
+ discardChanges,
83
+ initRepo,
84
+ setError,
85
+ } = useGitStore();
86
+
87
+ const [commitMessage, setCommitMessage] = useState('');
88
+ const [showBranches, setShowBranches] = useState(false);
89
+ const [newBranchName, setNewBranchName] = useState('');
90
+ const [showNewBranch, setShowNewBranch] = useState(false);
91
+ const [stagedExpanded, setStagedExpanded] = useState(true);
92
+ const [unstagedExpanded, setUnstagedExpanded] = useState(true);
93
+ const [untrackedExpanded, setUntrackedExpanded] = useState(true);
94
+
95
+ // Fetch status on mount and set up polling
96
+ useEffect(() => {
97
+ fetchStatus(projectPath);
98
+ fetchBranches(projectPath);
99
+
100
+ // Poll for status every 5 seconds
101
+ const interval = setInterval(() => {
102
+ fetchStatus(projectPath);
103
+ }, 5000);
104
+
105
+ return () => clearInterval(interval);
106
+ }, [projectPath, fetchStatus, fetchBranches]);
107
+
108
+ const handleRefresh = useCallback(() => {
109
+ fetchStatus(projectPath);
110
+ fetchBranches(projectPath);
111
+ }, [projectPath, fetchStatus, fetchBranches]);
112
+
113
+ const handleCommit = useCallback(async () => {
114
+ if (!commitMessage.trim()) return;
115
+
116
+ const result = await commit(projectPath, commitMessage);
117
+ if (result.success) {
118
+ setCommitMessage('');
119
+ }
120
+ }, [projectPath, commitMessage, commit]);
121
+
122
+ const handlePush = useCallback(() => {
123
+ push(projectPath);
124
+ }, [projectPath, push]);
125
+
126
+ const handlePull = useCallback(() => {
127
+ pull(projectPath);
128
+ }, [projectPath, pull]);
129
+
130
+ const handleCheckout = useCallback(async (branch: string) => {
131
+ await checkout(projectPath, branch);
132
+ setShowBranches(false);
133
+ }, [projectPath, checkout]);
134
+
135
+ const handleCreateBranch = useCallback(async () => {
136
+ if (!newBranchName.trim()) return;
137
+
138
+ const result = await createBranch(projectPath, newBranchName);
139
+ if (result.success) {
140
+ setNewBranchName('');
141
+ setShowNewBranch(false);
142
+ }
143
+ }, [projectPath, newBranchName, createBranch]);
144
+
145
+ const handleInit = useCallback(() => {
146
+ initRepo(projectPath);
147
+ }, [projectPath, initRepo]);
148
+
149
+ // Not a git repo
150
+ if (status && !status.isRepo) {
151
+ return (
152
+ <div className="h-full flex flex-col bg-[#0a0a0f]">
153
+ <div className="p-4 border-b border-white/10">
154
+ <h2 className="text-sm font-semibold text-white flex items-center gap-2">
155
+ <GitBranch className="w-4 h-4" />
156
+ Source Control
157
+ </h2>
158
+ </div>
159
+
160
+ <div className="flex-1 flex flex-col items-center justify-center p-6 text-center">
161
+ <div className="w-16 h-16 rounded-full bg-white/5 flex items-center justify-center mb-4">
162
+ <GitBranch className="w-8 h-8 text-gray-500" />
163
+ </div>
164
+ <p className="text-sm text-gray-400 mb-4">
165
+ This folder is not a Git repository
166
+ </p>
167
+ <button
168
+ onClick={handleInit}
169
+ disabled={isLoading}
170
+ className="px-4 py-2 bg-purple-500 hover:bg-purple-600 text-white text-sm rounded-lg transition-colors disabled:opacity-50"
171
+ >
172
+ Initialize Repository
173
+ </button>
174
+ </div>
175
+ </div>
176
+ );
177
+ }
178
+
179
+ const totalChanges = (status?.staged.length || 0) + (status?.unstaged.length || 0) + (status?.untracked.length || 0);
180
+
181
+ return (
182
+ <div className="h-full flex flex-col bg-[#0a0a0f]">
183
+ {/* Header */}
184
+ <div className="p-3 border-b border-white/10">
185
+ <div className="flex items-center justify-between mb-2">
186
+ <h2 className="text-sm font-semibold text-white flex items-center gap-2">
187
+ <GitBranch className="w-4 h-4" />
188
+ Source Control
189
+ {totalChanges > 0 && (
190
+ <span className="px-1.5 py-0.5 text-xs bg-purple-500/20 text-purple-400 rounded">
191
+ {totalChanges}
192
+ </span>
193
+ )}
194
+ </h2>
195
+ <button
196
+ onClick={handleRefresh}
197
+ disabled={isLoading}
198
+ className="p-1 text-gray-400 hover:text-white hover:bg-white/10 rounded transition-colors disabled:opacity-50"
199
+ >
200
+ <RefreshCw className={cn('w-4 h-4', isLoading && 'animate-spin')} />
201
+ </button>
202
+ </div>
203
+
204
+ {/* Branch Selector */}
205
+ <div className="relative">
206
+ <button
207
+ onClick={() => setShowBranches(!showBranches)}
208
+ className="w-full flex items-center justify-between gap-2 px-3 py-1.5 bg-white/5 border border-white/10 rounded-lg text-sm hover:bg-white/10 transition-colors"
209
+ >
210
+ <div className="flex items-center gap-2">
211
+ <GitBranch className="w-4 h-4 text-purple-400" />
212
+ <span className="text-white">{status?.branch || 'main'}</span>
213
+ </div>
214
+ <div className="flex items-center gap-2">
215
+ {status?.ahead ? (
216
+ <span className="text-xs text-green-400">{status.ahead}</span>
217
+ ) : null}
218
+ {status?.behind ? (
219
+ <span className="text-xs text-yellow-400">{status.behind}</span>
220
+ ) : null}
221
+ <ChevronDown className={cn('w-4 h-4 text-gray-500 transition-transform', showBranches && 'rotate-180')} />
222
+ </div>
223
+ </button>
224
+
225
+ {/* Branch Dropdown */}
226
+ {showBranches && (
227
+ <div className="absolute top-full left-0 right-0 mt-1 bg-[#12121a] border border-white/10 rounded-lg shadow-xl z-50 overflow-hidden">
228
+ <div className="max-h-48 overflow-y-auto">
229
+ {branches.map((branch) => (
230
+ <button
231
+ key={branch.name}
232
+ onClick={() => handleCheckout(branch.name)}
233
+ className={cn(
234
+ 'w-full flex items-center gap-2 px-3 py-2 text-sm hover:bg-white/5 transition-colors',
235
+ branch.current && 'bg-purple-500/10'
236
+ )}
237
+ >
238
+ {branch.current && <Check className="w-4 h-4 text-purple-400" />}
239
+ {!branch.current && <div className="w-4" />}
240
+ <span className={branch.current ? 'text-purple-400' : 'text-gray-300'}>
241
+ {branch.name}
242
+ </span>
243
+ </button>
244
+ ))}
245
+ </div>
246
+
247
+ <div className="border-t border-white/10 p-2">
248
+ {showNewBranch ? (
249
+ <div className="flex gap-2">
250
+ <input
251
+ type="text"
252
+ value={newBranchName}
253
+ onChange={(e) => setNewBranchName(e.target.value)}
254
+ onKeyDown={(e) => e.key === 'Enter' && handleCreateBranch()}
255
+ placeholder="New branch name"
256
+ className="flex-1 px-2 py-1 bg-white/5 border border-white/10 rounded text-sm text-white placeholder-gray-500 focus:outline-none focus:border-purple-500"
257
+ autoFocus
258
+ />
259
+ <button
260
+ onClick={handleCreateBranch}
261
+ className="p-1 text-green-400 hover:bg-white/10 rounded"
262
+ >
263
+ <Check className="w-4 h-4" />
264
+ </button>
265
+ <button
266
+ onClick={() => { setShowNewBranch(false); setNewBranchName(''); }}
267
+ className="p-1 text-red-400 hover:bg-white/10 rounded"
268
+ >
269
+ <X className="w-4 h-4" />
270
+ </button>
271
+ </div>
272
+ ) : (
273
+ <button
274
+ onClick={() => setShowNewBranch(true)}
275
+ className="w-full flex items-center gap-2 px-2 py-1 text-sm text-gray-400 hover:text-white hover:bg-white/5 rounded transition-colors"
276
+ >
277
+ <Plus className="w-4 h-4" />
278
+ Create new branch
279
+ </button>
280
+ )}
281
+ </div>
282
+ </div>
283
+ )}
284
+ </div>
285
+ </div>
286
+
287
+ {/* Error Display */}
288
+ {error && (
289
+ <div className="mx-3 mt-3 p-2 bg-red-500/10 border border-red-500/30 rounded-lg flex items-center gap-2">
290
+ <AlertCircle className="w-4 h-4 text-red-400 flex-shrink-0" />
291
+ <span className="text-xs text-red-400 flex-1">{error}</span>
292
+ <button onClick={() => setError(null)} className="text-red-400 hover:text-red-300">
293
+ <X className="w-4 h-4" />
294
+ </button>
295
+ </div>
296
+ )}
297
+
298
+ {/* Changes */}
299
+ <div className="flex-1 overflow-y-auto">
300
+ {/* Staged Changes */}
301
+ {status?.staged && status.staged.length > 0 && (
302
+ <div className="border-b border-white/5">
303
+ <div
304
+ role="button"
305
+ tabIndex={0}
306
+ onClick={() => setStagedExpanded(!stagedExpanded)}
307
+ onKeyDown={(e) => e.key === 'Enter' && setStagedExpanded(!stagedExpanded)}
308
+ className="w-full flex items-center justify-between px-3 py-2 hover:bg-white/5 transition-colors cursor-pointer"
309
+ >
310
+ <div className="flex items-center gap-2">
311
+ {stagedExpanded ? (
312
+ <ChevronDown className="w-4 h-4 text-gray-500" />
313
+ ) : (
314
+ <ChevronRight className="w-4 h-4 text-gray-500" />
315
+ )}
316
+ <span className="text-xs font-medium text-green-400">Staged Changes</span>
317
+ <span className="text-xs text-gray-500">{status.staged.length}</span>
318
+ </div>
319
+ <button
320
+ onClick={(e) => { e.stopPropagation(); unstageAll(projectPath); }}
321
+ className="p-1 text-gray-400 hover:text-white hover:bg-white/10 rounded"
322
+ title="Unstage All"
323
+ >
324
+ <Minus className="w-3 h-3" />
325
+ </button>
326
+ </div>
327
+
328
+ {stagedExpanded && (
329
+ <div className="pb-2">
330
+ {status.staged.map((file) => (
331
+ <div
332
+ key={file.path}
333
+ className="group flex items-center gap-2 px-3 py-1 hover:bg-white/5 transition-colors"
334
+ >
335
+ {getFileStatusIcon(file.status)}
336
+ <span className="flex-1 text-xs text-gray-300 truncate" title={file.path}>
337
+ {file.path}
338
+ </span>
339
+ {getFileStatusBadge(file.status)}
340
+ <button
341
+ onClick={() => unstageFiles(projectPath, [file.path])}
342
+ className="opacity-0 group-hover:opacity-100 p-1 text-gray-400 hover:text-white hover:bg-white/10 rounded transition-opacity"
343
+ title="Unstage"
344
+ >
345
+ <Minus className="w-3 h-3" />
346
+ </button>
347
+ </div>
348
+ ))}
349
+ </div>
350
+ )}
351
+ </div>
352
+ )}
353
+
354
+ {/* Unstaged Changes */}
355
+ {status?.unstaged && status.unstaged.length > 0 && (
356
+ <div className="border-b border-white/5">
357
+ <div
358
+ role="button"
359
+ tabIndex={0}
360
+ onClick={() => setUnstagedExpanded(!unstagedExpanded)}
361
+ onKeyDown={(e) => e.key === 'Enter' && setUnstagedExpanded(!unstagedExpanded)}
362
+ className="w-full flex items-center justify-between px-3 py-2 hover:bg-white/5 transition-colors cursor-pointer"
363
+ >
364
+ <div className="flex items-center gap-2">
365
+ {unstagedExpanded ? (
366
+ <ChevronDown className="w-4 h-4 text-gray-500" />
367
+ ) : (
368
+ <ChevronRight className="w-4 h-4 text-gray-500" />
369
+ )}
370
+ <span className="text-xs font-medium text-yellow-400">Changes</span>
371
+ <span className="text-xs text-gray-500">{status.unstaged.length}</span>
372
+ </div>
373
+ <div className="flex gap-1">
374
+ <button
375
+ onClick={(e) => { e.stopPropagation(); discardChanges(projectPath, status.unstaged.map(f => f.path)); }}
376
+ className="p-1 text-gray-400 hover:text-red-400 hover:bg-white/10 rounded"
377
+ title="Discard All"
378
+ >
379
+ <RotateCcw className="w-3 h-3" />
380
+ </button>
381
+ <button
382
+ onClick={(e) => { e.stopPropagation(); stageFiles(projectPath, status.unstaged.map(f => f.path)); }}
383
+ className="p-1 text-gray-400 hover:text-white hover:bg-white/10 rounded"
384
+ title="Stage All"
385
+ >
386
+ <Plus className="w-3 h-3" />
387
+ </button>
388
+ </div>
389
+ </div>
390
+
391
+ {unstagedExpanded && (
392
+ <div className="pb-2">
393
+ {status.unstaged.map((file) => (
394
+ <div
395
+ key={file.path}
396
+ className="group flex items-center gap-2 px-3 py-1 hover:bg-white/5 transition-colors"
397
+ >
398
+ {getFileStatusIcon(file.status)}
399
+ <span className="flex-1 text-xs text-gray-300 truncate" title={file.path}>
400
+ {file.path}
401
+ </span>
402
+ {getFileStatusBadge(file.status)}
403
+ <button
404
+ onClick={() => discardChanges(projectPath, [file.path])}
405
+ className="opacity-0 group-hover:opacity-100 p-1 text-gray-400 hover:text-red-400 hover:bg-white/10 rounded transition-opacity"
406
+ title="Discard"
407
+ >
408
+ <RotateCcw className="w-3 h-3" />
409
+ </button>
410
+ <button
411
+ onClick={() => stageFiles(projectPath, [file.path])}
412
+ className="opacity-0 group-hover:opacity-100 p-1 text-gray-400 hover:text-white hover:bg-white/10 rounded transition-opacity"
413
+ title="Stage"
414
+ >
415
+ <Plus className="w-3 h-3" />
416
+ </button>
417
+ </div>
418
+ ))}
419
+ </div>
420
+ )}
421
+ </div>
422
+ )}
423
+
424
+ {/* Untracked Files */}
425
+ {status?.untracked && status.untracked.length > 0 && (
426
+ <div className="border-b border-white/5">
427
+ <div
428
+ role="button"
429
+ tabIndex={0}
430
+ onClick={() => setUntrackedExpanded(!untrackedExpanded)}
431
+ onKeyDown={(e) => e.key === 'Enter' && setUntrackedExpanded(!untrackedExpanded)}
432
+ className="w-full flex items-center justify-between px-3 py-2 hover:bg-white/5 transition-colors cursor-pointer"
433
+ >
434
+ <div className="flex items-center gap-2">
435
+ {untrackedExpanded ? (
436
+ <ChevronDown className="w-4 h-4 text-gray-500" />
437
+ ) : (
438
+ <ChevronRight className="w-4 h-4 text-gray-500" />
439
+ )}
440
+ <span className="text-xs font-medium text-gray-400">Untracked</span>
441
+ <span className="text-xs text-gray-500">{status.untracked.length}</span>
442
+ </div>
443
+ <button
444
+ onClick={(e) => { e.stopPropagation(); stageFiles(projectPath, status.untracked); }}
445
+ className="p-1 text-gray-400 hover:text-white hover:bg-white/10 rounded"
446
+ title="Stage All"
447
+ >
448
+ <Plus className="w-3 h-3" />
449
+ </button>
450
+ </div>
451
+
452
+ {untrackedExpanded && (
453
+ <div className="pb-2">
454
+ {status.untracked.map((file) => (
455
+ <div
456
+ key={file}
457
+ className="group flex items-center gap-2 px-3 py-1 hover:bg-white/5 transition-colors"
458
+ >
459
+ <FilePlus className="w-4 h-4 text-green-400" />
460
+ <span className="flex-1 text-xs text-gray-300 truncate" title={file}>
461
+ {file}
462
+ </span>
463
+ <span className="text-xs font-mono px-1.5 py-0.5 rounded text-green-400 bg-green-400/10">U</span>
464
+ <button
465
+ onClick={() => stageFiles(projectPath, [file])}
466
+ className="opacity-0 group-hover:opacity-100 p-1 text-gray-400 hover:text-white hover:bg-white/10 rounded transition-opacity"
467
+ title="Stage"
468
+ >
469
+ <Plus className="w-3 h-3" />
470
+ </button>
471
+ </div>
472
+ ))}
473
+ </div>
474
+ )}
475
+ </div>
476
+ )}
477
+
478
+ {/* No Changes */}
479
+ {totalChanges === 0 && status?.isRepo && (
480
+ <div className="flex flex-col items-center justify-center py-8 text-center">
481
+ <Check className="w-8 h-8 text-green-400 mb-2" />
482
+ <p className="text-sm text-gray-400">No changes</p>
483
+ <p className="text-xs text-gray-600">Working tree clean</p>
484
+ </div>
485
+ )}
486
+ </div>
487
+
488
+ {/* Commit Section */}
489
+ {status?.isRepo && (
490
+ <div className="border-t border-white/10 p-3">
491
+ {/* Commit Message */}
492
+ <textarea
493
+ value={commitMessage}
494
+ onChange={(e) => setCommitMessage(e.target.value)}
495
+ placeholder="Commit message"
496
+ className="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-sm text-white placeholder-gray-500 focus:outline-none focus:border-purple-500 resize-none"
497
+ rows={3}
498
+ />
499
+
500
+ {/* Action Buttons */}
501
+ <div className="flex gap-2 mt-2">
502
+ <button
503
+ onClick={handleCommit}
504
+ disabled={!commitMessage.trim() || !status?.staged?.length || isLoading}
505
+ className="flex-1 flex items-center justify-center gap-2 px-3 py-2 bg-purple-500 hover:bg-purple-600 disabled:bg-gray-600 disabled:cursor-not-allowed text-white text-sm rounded-lg transition-colors"
506
+ >
507
+ <GitCommit className="w-4 h-4" />
508
+ Commit
509
+ </button>
510
+ </div>
511
+
512
+ <div className="flex gap-2 mt-2">
513
+ <button
514
+ onClick={handlePull}
515
+ disabled={isLoading}
516
+ className="flex-1 flex items-center justify-center gap-2 px-3 py-1.5 bg-white/5 hover:bg-white/10 text-gray-300 text-xs rounded-lg transition-colors disabled:opacity-50"
517
+ >
518
+ <Download className="w-3 h-3" />
519
+ Pull
520
+ </button>
521
+ <button
522
+ onClick={handlePush}
523
+ disabled={isLoading}
524
+ className="flex-1 flex items-center justify-center gap-2 px-3 py-1.5 bg-white/5 hover:bg-white/10 text-gray-300 text-xs rounded-lg transition-colors disabled:opacity-50"
525
+ >
526
+ <Upload className="w-3 h-3" />
527
+ Push
528
+ </button>
529
+ </div>
530
+ </div>
531
+ )}
532
+ </div>
533
+ );
534
+ }
@@ -0,0 +1,15 @@
1
+ 'use client';
2
+
3
+ import { ReactNode } from 'react';
4
+
5
+ interface ShellProps {
6
+ children: ReactNode;
7
+ }
8
+
9
+ export function Shell({ children }: ShellProps) {
10
+ return (
11
+ <div className="flex-1 flex overflow-hidden">
12
+ {children}
13
+ </div>
14
+ );
15
+ }
@@ -0,0 +1,100 @@
1
+ 'use client';
2
+
3
+ import { useProjectStore } from '@/lib/stores/projectStore';
4
+ import { useFileStore } from '@/lib/stores/fileStore';
5
+ import { useUIStore } from '@/lib/stores/uiStore';
6
+ import {
7
+ FolderOpen,
8
+ GitBranch,
9
+ CheckCircle,
10
+ XCircle,
11
+ Zap,
12
+ Sparkles
13
+ } from 'lucide-react';
14
+
15
+ export function StatusBar() {
16
+ const { currentProject, health } = useProjectStore();
17
+ const { activeFile, openFiles } = useFileStore();
18
+ const { selectedModel, autopilot } = useUIStore();
19
+
20
+ const activeOpenFile = openFiles.find(f => f.path === activeFile);
21
+
22
+ const modelNames: Record<string, string> = {
23
+ 'claude-sonnet-4': 'Sonnet 4',
24
+ 'claude-opus-4': 'Opus 4',
25
+ 'auto': 'Auto',
26
+ };
27
+
28
+ return (
29
+ <div className="h-7 bg-[#12121a] border-t border-white/10 text-gray-400 flex items-center justify-between px-3 text-xs">
30
+ {/* Left side */}
31
+ <div className="flex items-center gap-4">
32
+ {/* Project name */}
33
+ {currentProject && (
34
+ <div className="flex items-center gap-1.5 text-gray-300">
35
+ <FolderOpen className="w-3.5 h-3.5 text-purple-400" />
36
+ <span>{currentProject.name}</span>
37
+ </div>
38
+ )}
39
+
40
+ {/* Git branch */}
41
+ <div className="flex items-center gap-1.5">
42
+ <GitBranch className="w-3.5 h-3.5" />
43
+ <span>main</span>
44
+ </div>
45
+
46
+ {/* Claude CLI status */}
47
+ <div className="flex items-center gap-1.5">
48
+ {health?.claudeCli.installed ? (
49
+ <>
50
+ <CheckCircle className="w-3.5 h-3.5 text-green-400" />
51
+ <span>Claude CLI</span>
52
+ </>
53
+ ) : (
54
+ <>
55
+ <XCircle className="w-3.5 h-3.5 text-red-400" />
56
+ <span>Claude CLI not found</span>
57
+ </>
58
+ )}
59
+ </div>
60
+
61
+ </div>
62
+
63
+ {/* Right side */}
64
+ <div className="flex items-center gap-4">
65
+ {/* Autopilot indicator */}
66
+ {autopilot.enabled && (
67
+ <div className="flex items-center gap-1.5 text-purple-400">
68
+ <Sparkles className="w-3.5 h-3.5 animate-pulse" />
69
+ <span>Autopilot</span>
70
+ </div>
71
+ )}
72
+
73
+ {/* Model */}
74
+ <div className="flex items-center gap-1.5">
75
+ <Zap className="w-3.5 h-3.5 text-purple-400" />
76
+ <span>{modelNames[selectedModel] || selectedModel}</span>
77
+ </div>
78
+
79
+ {/* File info */}
80
+ {activeOpenFile && (
81
+ <>
82
+ <span>Ln 1, Col 1</span>
83
+ <span className="text-gray-500">|</span>
84
+ <span>{activeOpenFile.language}</span>
85
+ <span className="text-gray-500">|</span>
86
+ <span>UTF-8</span>
87
+ </>
88
+ )}
89
+
90
+ {/* Stats */}
91
+ {currentProject && (
92
+ <div className="flex items-center gap-2 text-gray-500">
93
+ <span>{currentProject.stats.stories} stories</span>
94
+ <span>{currentProject.stats.adrs} ADRs</span>
95
+ </div>
96
+ )}
97
+ </div>
98
+ </div>
99
+ );
100
+ }