orbital-command 0.1.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 (325) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +396 -0
  3. package/bin/orbital.js +362 -0
  4. package/dist/assets/WorkflowVisualizer-BZ21PIIF.js +84 -0
  5. package/dist/assets/WorkflowVisualizer-BZV40eAE.css +1 -0
  6. package/dist/assets/charts-D__PA1zp.js +72 -0
  7. package/dist/assets/index-D1G6i0nS.css +1 -0
  8. package/dist/assets/index-DpItvKpf.js +419 -0
  9. package/dist/assets/ui-BvF022GT.js +53 -0
  10. package/dist/assets/vendor-Dzv9lrRc.js +59 -0
  11. package/dist/index.html +19 -0
  12. package/dist/scanner-sweep.png +0 -0
  13. package/dist/server/server/adapters/index.js +34 -0
  14. package/dist/server/server/adapters/iterm2-adapter.js +29 -0
  15. package/dist/server/server/adapters/subprocess-adapter.js +21 -0
  16. package/dist/server/server/adapters/terminal-adapter.js +1 -0
  17. package/dist/server/server/config.js +156 -0
  18. package/dist/server/server/database.js +90 -0
  19. package/dist/server/server/index.js +372 -0
  20. package/dist/server/server/init.js +811 -0
  21. package/dist/server/server/parsers/event-parser.js +64 -0
  22. package/dist/server/server/parsers/scope-parser.js +188 -0
  23. package/dist/server/server/routes/config-routes.js +163 -0
  24. package/dist/server/server/routes/data-routes.js +461 -0
  25. package/dist/server/server/routes/dispatch-routes.js +215 -0
  26. package/dist/server/server/routes/git-routes.js +92 -0
  27. package/dist/server/server/routes/scope-routes.js +215 -0
  28. package/dist/server/server/routes/sprint-routes.js +116 -0
  29. package/dist/server/server/routes/version-routes.js +130 -0
  30. package/dist/server/server/routes/workflow-routes.js +185 -0
  31. package/dist/server/server/schema.js +90 -0
  32. package/dist/server/server/services/batch-orchestrator.js +253 -0
  33. package/dist/server/server/services/claude-session-service.js +352 -0
  34. package/dist/server/server/services/config-service.js +132 -0
  35. package/dist/server/server/services/deploy-service.js +51 -0
  36. package/dist/server/server/services/event-service.js +63 -0
  37. package/dist/server/server/services/gate-service.js +83 -0
  38. package/dist/server/server/services/git-service.js +309 -0
  39. package/dist/server/server/services/github-service.js +145 -0
  40. package/dist/server/server/services/readiness-service.js +184 -0
  41. package/dist/server/server/services/scope-cache.js +72 -0
  42. package/dist/server/server/services/scope-service.js +424 -0
  43. package/dist/server/server/services/sprint-orchestrator.js +312 -0
  44. package/dist/server/server/services/sprint-service.js +293 -0
  45. package/dist/server/server/services/workflow-service.js +397 -0
  46. package/dist/server/server/utils/cc-hooks-parser.js +49 -0
  47. package/dist/server/server/utils/dispatch-utils.js +305 -0
  48. package/dist/server/server/utils/logger.js +86 -0
  49. package/dist/server/server/utils/terminal-launcher.js +388 -0
  50. package/dist/server/server/utils/worktree-manager.js +98 -0
  51. package/dist/server/server/watchers/event-watcher.js +81 -0
  52. package/dist/server/server/watchers/scope-watcher.js +33 -0
  53. package/dist/server/shared/api-types.js +5 -0
  54. package/dist/server/shared/default-workflow.json +616 -0
  55. package/dist/server/shared/workflow-config.js +44 -0
  56. package/dist/server/shared/workflow-engine.js +353 -0
  57. package/index.html +15 -0
  58. package/package.json +110 -0
  59. package/postcss.config.js +6 -0
  60. package/schemas/orbital.config.schema.json +83 -0
  61. package/scripts/postinstall.js +24 -0
  62. package/scripts/start.sh +20 -0
  63. package/server/adapters/index.ts +41 -0
  64. package/server/adapters/iterm2-adapter.ts +37 -0
  65. package/server/adapters/subprocess-adapter.ts +25 -0
  66. package/server/adapters/terminal-adapter.ts +24 -0
  67. package/server/config.ts +234 -0
  68. package/server/database.ts +107 -0
  69. package/server/index.ts +452 -0
  70. package/server/init.ts +891 -0
  71. package/server/parsers/event-parser.ts +74 -0
  72. package/server/parsers/scope-parser.ts +240 -0
  73. package/server/routes/config-routes.ts +182 -0
  74. package/server/routes/data-routes.ts +548 -0
  75. package/server/routes/dispatch-routes.ts +275 -0
  76. package/server/routes/git-routes.ts +112 -0
  77. package/server/routes/scope-routes.ts +262 -0
  78. package/server/routes/sprint-routes.ts +142 -0
  79. package/server/routes/version-routes.ts +156 -0
  80. package/server/routes/workflow-routes.ts +198 -0
  81. package/server/schema.ts +90 -0
  82. package/server/services/batch-orchestrator.ts +286 -0
  83. package/server/services/claude-session-service.ts +441 -0
  84. package/server/services/config-service.ts +151 -0
  85. package/server/services/deploy-service.ts +98 -0
  86. package/server/services/event-service.ts +98 -0
  87. package/server/services/gate-service.ts +126 -0
  88. package/server/services/git-service.ts +391 -0
  89. package/server/services/github-service.ts +183 -0
  90. package/server/services/readiness-service.ts +250 -0
  91. package/server/services/scope-cache.ts +81 -0
  92. package/server/services/scope-service.ts +476 -0
  93. package/server/services/sprint-orchestrator.ts +361 -0
  94. package/server/services/sprint-service.ts +415 -0
  95. package/server/services/workflow-service.ts +461 -0
  96. package/server/utils/cc-hooks-parser.ts +70 -0
  97. package/server/utils/dispatch-utils.ts +395 -0
  98. package/server/utils/logger.ts +109 -0
  99. package/server/utils/terminal-launcher.ts +462 -0
  100. package/server/utils/worktree-manager.ts +104 -0
  101. package/server/watchers/event-watcher.ts +100 -0
  102. package/server/watchers/scope-watcher.ts +38 -0
  103. package/shared/api-types.ts +20 -0
  104. package/shared/default-workflow.json +616 -0
  105. package/shared/workflow-config.ts +170 -0
  106. package/shared/workflow-engine.ts +427 -0
  107. package/src/App.tsx +33 -0
  108. package/src/components/AgentBadge.tsx +40 -0
  109. package/src/components/BatchPreflightModal.tsx +115 -0
  110. package/src/components/CardDisplayToggle.tsx +74 -0
  111. package/src/components/ColumnHeaderActions.tsx +55 -0
  112. package/src/components/ColumnMenu.tsx +99 -0
  113. package/src/components/DeployHistory.tsx +141 -0
  114. package/src/components/DispatchModal.tsx +164 -0
  115. package/src/components/DispatchPopover.tsx +139 -0
  116. package/src/components/DragOverlay.tsx +25 -0
  117. package/src/components/DriftSidebar.tsx +140 -0
  118. package/src/components/EnvironmentStrip.tsx +88 -0
  119. package/src/components/ErrorBoundary.tsx +62 -0
  120. package/src/components/FilterChip.tsx +105 -0
  121. package/src/components/GateIndicator.tsx +33 -0
  122. package/src/components/IdeaDetailModal.tsx +190 -0
  123. package/src/components/IdeaFormDialog.tsx +113 -0
  124. package/src/components/KanbanColumn.tsx +201 -0
  125. package/src/components/MarkdownRenderer.tsx +114 -0
  126. package/src/components/NeonGrid.tsx +128 -0
  127. package/src/components/PromotionQueue.tsx +89 -0
  128. package/src/components/ScopeCard.tsx +234 -0
  129. package/src/components/ScopeDetailModal.tsx +255 -0
  130. package/src/components/ScopeFilterBar.tsx +152 -0
  131. package/src/components/SearchInput.tsx +102 -0
  132. package/src/components/SessionPanel.tsx +335 -0
  133. package/src/components/SprintContainer.tsx +303 -0
  134. package/src/components/SprintDependencyDialog.tsx +78 -0
  135. package/src/components/SprintPreflightModal.tsx +138 -0
  136. package/src/components/StatusBar.tsx +168 -0
  137. package/src/components/SwimCell.tsx +67 -0
  138. package/src/components/SwimLaneRow.tsx +94 -0
  139. package/src/components/SwimlaneBoardView.tsx +108 -0
  140. package/src/components/VersionBadge.tsx +139 -0
  141. package/src/components/ViewModeSelector.tsx +114 -0
  142. package/src/components/config/AgentChip.tsx +53 -0
  143. package/src/components/config/AgentCreateDialog.tsx +321 -0
  144. package/src/components/config/AgentEditor.tsx +175 -0
  145. package/src/components/config/DirectoryTree.tsx +582 -0
  146. package/src/components/config/FileEditor.tsx +550 -0
  147. package/src/components/config/HookChip.tsx +50 -0
  148. package/src/components/config/StageCard.tsx +198 -0
  149. package/src/components/config/TransitionZone.tsx +173 -0
  150. package/src/components/config/UnifiedWorkflowPipeline.tsx +216 -0
  151. package/src/components/config/WorkflowPipeline.tsx +161 -0
  152. package/src/components/source-control/BranchList.tsx +93 -0
  153. package/src/components/source-control/BranchPanel.tsx +105 -0
  154. package/src/components/source-control/CommitLog.tsx +100 -0
  155. package/src/components/source-control/CommitRow.tsx +47 -0
  156. package/src/components/source-control/GitHubPanel.tsx +110 -0
  157. package/src/components/source-control/GitHubSetupGuide.tsx +52 -0
  158. package/src/components/source-control/GitOverviewBar.tsx +101 -0
  159. package/src/components/source-control/PullRequestList.tsx +69 -0
  160. package/src/components/source-control/WorktreeList.tsx +80 -0
  161. package/src/components/ui/badge.tsx +41 -0
  162. package/src/components/ui/button.tsx +55 -0
  163. package/src/components/ui/card.tsx +78 -0
  164. package/src/components/ui/dialog.tsx +94 -0
  165. package/src/components/ui/popover.tsx +33 -0
  166. package/src/components/ui/scroll-area.tsx +54 -0
  167. package/src/components/ui/separator.tsx +28 -0
  168. package/src/components/ui/tabs.tsx +52 -0
  169. package/src/components/ui/toggle-switch.tsx +35 -0
  170. package/src/components/ui/tooltip.tsx +27 -0
  171. package/src/components/workflow/AddEdgeDialog.tsx +217 -0
  172. package/src/components/workflow/AddListDialog.tsx +201 -0
  173. package/src/components/workflow/ChecklistEditor.tsx +239 -0
  174. package/src/components/workflow/CommandPrefixManager.tsx +118 -0
  175. package/src/components/workflow/ConfigSettingsPanel.tsx +189 -0
  176. package/src/components/workflow/DirectionSelector.tsx +133 -0
  177. package/src/components/workflow/DispatchConfigPanel.tsx +180 -0
  178. package/src/components/workflow/EdgeDetailPanel.tsx +236 -0
  179. package/src/components/workflow/EdgePropertyEditor.tsx +251 -0
  180. package/src/components/workflow/EditToolbar.tsx +138 -0
  181. package/src/components/workflow/HookDetailPanel.tsx +250 -0
  182. package/src/components/workflow/HookExecutionLog.tsx +24 -0
  183. package/src/components/workflow/HookSourceModal.tsx +129 -0
  184. package/src/components/workflow/HooksDashboard.tsx +363 -0
  185. package/src/components/workflow/ListPropertyEditor.tsx +251 -0
  186. package/src/components/workflow/MigrationPreviewDialog.tsx +237 -0
  187. package/src/components/workflow/MovementRulesPanel.tsx +188 -0
  188. package/src/components/workflow/NodeDetailPanel.tsx +245 -0
  189. package/src/components/workflow/PresetSelector.tsx +414 -0
  190. package/src/components/workflow/SkillCommandBuilder.tsx +174 -0
  191. package/src/components/workflow/WorkflowEdgeComponent.tsx +145 -0
  192. package/src/components/workflow/WorkflowNode.tsx +147 -0
  193. package/src/components/workflow/graphLayout.ts +186 -0
  194. package/src/components/workflow/mergeHooks.ts +85 -0
  195. package/src/components/workflow/useEditHistory.ts +88 -0
  196. package/src/components/workflow/useWorkflowEditor.ts +262 -0
  197. package/src/components/workflow/validateConfig.ts +70 -0
  198. package/src/hooks/useActiveDispatches.ts +198 -0
  199. package/src/hooks/useBoardSettings.ts +170 -0
  200. package/src/hooks/useCardDisplay.ts +57 -0
  201. package/src/hooks/useCcHooks.ts +24 -0
  202. package/src/hooks/useConfigTree.ts +51 -0
  203. package/src/hooks/useEnforcementRules.ts +46 -0
  204. package/src/hooks/useEvents.ts +59 -0
  205. package/src/hooks/useFileEditor.ts +165 -0
  206. package/src/hooks/useGates.ts +57 -0
  207. package/src/hooks/useIdeaActions.ts +53 -0
  208. package/src/hooks/useKanbanDnd.ts +410 -0
  209. package/src/hooks/useOrbitalConfig.ts +54 -0
  210. package/src/hooks/usePipeline.ts +47 -0
  211. package/src/hooks/usePipelineData.ts +338 -0
  212. package/src/hooks/useReconnect.ts +25 -0
  213. package/src/hooks/useScopeFilters.ts +125 -0
  214. package/src/hooks/useScopeSessions.ts +44 -0
  215. package/src/hooks/useScopes.ts +67 -0
  216. package/src/hooks/useSearch.ts +67 -0
  217. package/src/hooks/useSettings.tsx +187 -0
  218. package/src/hooks/useSocket.ts +25 -0
  219. package/src/hooks/useSourceControl.ts +105 -0
  220. package/src/hooks/useSprintPreflight.ts +55 -0
  221. package/src/hooks/useSprints.ts +154 -0
  222. package/src/hooks/useStatusBarHighlight.ts +18 -0
  223. package/src/hooks/useSwimlaneBoardSettings.ts +104 -0
  224. package/src/hooks/useTheme.ts +9 -0
  225. package/src/hooks/useTransitionReadiness.ts +53 -0
  226. package/src/hooks/useVersion.ts +155 -0
  227. package/src/hooks/useViolations.ts +65 -0
  228. package/src/hooks/useWorkflow.tsx +125 -0
  229. package/src/hooks/useZoomModifier.ts +19 -0
  230. package/src/index.css +797 -0
  231. package/src/layouts/DashboardLayout.tsx +113 -0
  232. package/src/lib/collisionDetection.ts +20 -0
  233. package/src/lib/scope-fields.ts +61 -0
  234. package/src/lib/swimlane.ts +146 -0
  235. package/src/lib/utils.ts +15 -0
  236. package/src/main.tsx +19 -0
  237. package/src/socket.ts +11 -0
  238. package/src/types/index.ts +497 -0
  239. package/src/views/AgentFeed.tsx +339 -0
  240. package/src/views/DeployPipeline.tsx +59 -0
  241. package/src/views/EnforcementView.tsx +378 -0
  242. package/src/views/PrimitivesConfig.tsx +500 -0
  243. package/src/views/QualityGates.tsx +1012 -0
  244. package/src/views/ScopeBoard.tsx +454 -0
  245. package/src/views/SessionTimeline.tsx +516 -0
  246. package/src/views/Settings.tsx +183 -0
  247. package/src/views/SourceControl.tsx +95 -0
  248. package/src/views/WorkflowVisualizer.tsx +382 -0
  249. package/tailwind.config.js +161 -0
  250. package/templates/agents/AUTO-INVOKE.md +180 -0
  251. package/templates/agents/CONFLICT-RESOLUTION.md +128 -0
  252. package/templates/agents/QUICK-REFERENCE.md +122 -0
  253. package/templates/agents/README.md +188 -0
  254. package/templates/agents/SKILL-TRIGGERS.md +100 -0
  255. package/templates/agents/blue-team/frontend-designer.md +424 -0
  256. package/templates/agents/green-team/architect.md +526 -0
  257. package/templates/agents/green-team/rules-enforcer.md +131 -0
  258. package/templates/agents/red-team/attacker-learned.md +24 -0
  259. package/templates/agents/red-team/attacker.md +486 -0
  260. package/templates/agents/red-team/chaos.md +548 -0
  261. package/templates/agents/reference/component-registry.md +82 -0
  262. package/templates/agents/workflows/full-mode.md +218 -0
  263. package/templates/agents/workflows/quick-mode.md +118 -0
  264. package/templates/agents/workflows/security-mode.md +283 -0
  265. package/templates/anti-patterns/dangerous-shortcuts.md +427 -0
  266. package/templates/config/agent-triggers.json +92 -0
  267. package/templates/hooks/agent-team-gate.sh +31 -0
  268. package/templates/hooks/agent-trigger.sh +97 -0
  269. package/templates/hooks/block-push.sh +66 -0
  270. package/templates/hooks/block-workarounds.sh +61 -0
  271. package/templates/hooks/blocker-check.sh +28 -0
  272. package/templates/hooks/completion-checklist.sh +28 -0
  273. package/templates/hooks/decision-capture.sh +15 -0
  274. package/templates/hooks/dependency-check.sh +27 -0
  275. package/templates/hooks/end-session.sh +31 -0
  276. package/templates/hooks/exploration-logger.sh +37 -0
  277. package/templates/hooks/files-changed-summary.sh +37 -0
  278. package/templates/hooks/get-session-id.sh +49 -0
  279. package/templates/hooks/git-commit-guard.sh +34 -0
  280. package/templates/hooks/init-session.sh +93 -0
  281. package/templates/hooks/orbital-emit.sh +79 -0
  282. package/templates/hooks/orbital-report-deploy.sh +78 -0
  283. package/templates/hooks/orbital-report-gates.sh +40 -0
  284. package/templates/hooks/orbital-report-violation.sh +36 -0
  285. package/templates/hooks/orbital-scope-update.sh +53 -0
  286. package/templates/hooks/phase-verify-reminder.sh +26 -0
  287. package/templates/hooks/review-gate-check.sh +82 -0
  288. package/templates/hooks/scope-commit-logger.sh +37 -0
  289. package/templates/hooks/scope-create-cleanup.sh +36 -0
  290. package/templates/hooks/scope-create-gate.sh +80 -0
  291. package/templates/hooks/scope-create-tracker.sh +17 -0
  292. package/templates/hooks/scope-file-sync.sh +53 -0
  293. package/templates/hooks/scope-gate.sh +35 -0
  294. package/templates/hooks/scope-helpers.sh +188 -0
  295. package/templates/hooks/scope-lifecycle-gate.sh +139 -0
  296. package/templates/hooks/scope-prepare.sh +244 -0
  297. package/templates/hooks/scope-transition.sh +172 -0
  298. package/templates/hooks/session-enforcer.sh +143 -0
  299. package/templates/hooks/time-tracker.sh +33 -0
  300. package/templates/lessons-learned.md +15 -0
  301. package/templates/orbital.config.json +35 -0
  302. package/templates/presets/development.json +42 -0
  303. package/templates/presets/gitflow.json +712 -0
  304. package/templates/presets/minimal.json +23 -0
  305. package/templates/quick/rules.md +218 -0
  306. package/templates/scopes/_template.md +255 -0
  307. package/templates/settings-hooks.json +98 -0
  308. package/templates/skills/git-commit/SKILL.md +85 -0
  309. package/templates/skills/git-dev/SKILL.md +99 -0
  310. package/templates/skills/git-hotfix/SKILL.md +223 -0
  311. package/templates/skills/git-main/SKILL.md +84 -0
  312. package/templates/skills/git-production/SKILL.md +165 -0
  313. package/templates/skills/git-staging/SKILL.md +112 -0
  314. package/templates/skills/scope-create/SKILL.md +81 -0
  315. package/templates/skills/scope-fix-review/SKILL.md +168 -0
  316. package/templates/skills/scope-implement/SKILL.md +110 -0
  317. package/templates/skills/scope-post-review/SKILL.md +144 -0
  318. package/templates/skills/scope-pre-review/SKILL.md +211 -0
  319. package/templates/skills/scope-verify/SKILL.md +201 -0
  320. package/templates/skills/session-init/SKILL.md +62 -0
  321. package/templates/skills/session-resume/SKILL.md +201 -0
  322. package/templates/skills/test-checks/SKILL.md +171 -0
  323. package/templates/skills/test-code-review/SKILL.md +252 -0
  324. package/tsconfig.json +25 -0
  325. package/vite.config.ts +38 -0
@@ -0,0 +1,78 @@
1
+ import { AlertTriangle, Plus, X } from 'lucide-react';
2
+ import {
3
+ Dialog,
4
+ DialogContent,
5
+ DialogHeader,
6
+ DialogTitle,
7
+ DialogDescription,
8
+ } from '@/components/ui/dialog';
9
+ import { Button } from '@/components/ui/button';
10
+ import { Badge } from '@/components/ui/badge';
11
+ import { formatScopeId } from '@/lib/utils';
12
+
13
+ interface UnmetDep {
14
+ scope_id: number;
15
+ missing: Array<{ scope_id: number; title: string; status: string }>;
16
+ }
17
+
18
+ interface SprintDependencyDialogProps {
19
+ open: boolean;
20
+ unmetDeps: UnmetDep[];
21
+ onAddAll: (scopeIds: number[]) => void;
22
+ onCancel: () => void;
23
+ }
24
+
25
+ export function SprintDependencyDialog({ open, unmetDeps, onAddAll, onCancel }: SprintDependencyDialogProps) {
26
+ // Collect all unique missing scope IDs
27
+ const allMissing = new Map<number, { title: string; status: string }>();
28
+ for (const dep of unmetDeps) {
29
+ for (const m of dep.missing) {
30
+ if (!allMissing.has(m.scope_id)) {
31
+ allMissing.set(m.scope_id, { title: m.title, status: m.status });
32
+ }
33
+ }
34
+ }
35
+
36
+ return (
37
+ <Dialog open={open} onOpenChange={(o) => !o && onCancel()}>
38
+ <DialogContent className="sm:max-w-md">
39
+ <DialogHeader>
40
+ <DialogTitle className="flex items-center gap-2 text-sm">
41
+ <AlertTriangle className="h-4 w-4 text-warning-amber" />
42
+ Unmet Dependencies
43
+ </DialogTitle>
44
+ <DialogDescription className="text-xs">
45
+ Some scopes you added depend on scopes not yet in this sprint.
46
+ </DialogDescription>
47
+ </DialogHeader>
48
+
49
+ <div className="space-y-3 my-2">
50
+ {unmetDeps.map((dep) => (
51
+ <div key={dep.scope_id} className="text-xs">
52
+ <span className="font-mono text-muted-foreground">{formatScopeId(dep.scope_id)}</span>
53
+ <span className="ml-1">depends on:</span>
54
+ <div className="ml-4 mt-1 space-y-1">
55
+ {dep.missing.map((m) => (
56
+ <div key={m.scope_id} className="flex items-center gap-2">
57
+ <span className="font-mono text-muted-foreground">{formatScopeId(m.scope_id)}</span>
58
+ <span className="truncate">{m.title}</span>
59
+ <Badge variant="outline" className="text-[10px] ml-auto shrink-0">{m.status}</Badge>
60
+ </div>
61
+ ))}
62
+ </div>
63
+ </div>
64
+ ))}
65
+ </div>
66
+
67
+ <div className="flex justify-end gap-2 pt-2 border-t">
68
+ <Button variant="ghost" size="sm" onClick={onCancel}>
69
+ <X className="h-3 w-3 mr-1" /> Cancel
70
+ </Button>
71
+ <Button size="sm" onClick={() => onAddAll([...allMissing.keys()])}>
72
+ <Plus className="h-3 w-3 mr-1" /> Add All Dependencies
73
+ </Button>
74
+ </div>
75
+ </DialogContent>
76
+ </Dialog>
77
+ );
78
+ }
@@ -0,0 +1,138 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { Play, ArrowRight, AlertTriangle } from 'lucide-react';
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogHeader,
7
+ DialogTitle,
8
+ DialogDescription,
9
+ } from '@/components/ui/dialog';
10
+ import { Button } from '@/components/ui/button';
11
+ import { Badge } from '@/components/ui/badge';
12
+ import { formatScopeId } from '@/lib/utils';
13
+ import type { Sprint } from '@/types';
14
+
15
+ interface SprintPreflightModalProps {
16
+ open: boolean;
17
+ sprint: Sprint | null;
18
+ graph: { layers: number[][]; edges: Array<{ from: number; to: number }> } | null;
19
+ loading: boolean;
20
+ onConfirm: () => void;
21
+ onCancel: () => void;
22
+ }
23
+
24
+ export function SprintPreflightModal({
25
+ open,
26
+ sprint,
27
+ graph,
28
+ loading,
29
+ onConfirm,
30
+ onCancel,
31
+ }: SprintPreflightModalProps) {
32
+ const [dispatching, setDispatching] = useState(false);
33
+
34
+ useEffect(() => {
35
+ if (open) setDispatching(false);
36
+ }, [open]);
37
+
38
+ if (!sprint) return null;
39
+
40
+ const scopeMap = new Map(sprint.scopes.map((s) => [s.scope_id, s]));
41
+ const layers = graph?.layers ?? [];
42
+ const totalScopes = sprint.scope_ids.length;
43
+
44
+ const handleConfirm = async () => {
45
+ setDispatching(true);
46
+ onConfirm();
47
+ };
48
+
49
+ return (
50
+ <Dialog open={open} onOpenChange={(o) => !o && onCancel()}>
51
+ <DialogContent className="sm:max-w-lg">
52
+ <DialogHeader>
53
+ <DialogTitle className="flex items-center gap-2 text-sm">
54
+ <Play className="h-4 w-4 text-cyan-400" />
55
+ Dispatch Sprint: {sprint.name}
56
+ </DialogTitle>
57
+ <DialogDescription className="text-xs">
58
+ {totalScopes} scope{totalScopes !== 1 ? 's' : ''} will be dispatched in{' '}
59
+ {layers.length} layer{layers.length !== 1 ? 's' : ''} with max{' '}
60
+ {sprint.concurrency_cap} concurrent agents.
61
+ </DialogDescription>
62
+ </DialogHeader>
63
+
64
+ {/* Execution Graph */}
65
+ <div className="my-3 space-y-3 max-h-64 overflow-y-auto">
66
+ {layers.map((layer, idx) => (
67
+ <div key={idx}>
68
+ <div className="flex items-center gap-2 mb-1">
69
+ <Badge variant="outline" className="text-[10px]">Layer {idx}</Badge>
70
+ {idx === 0 && <span className="text-[10px] text-cyan-400">Launches first</span>}
71
+ {idx > 0 && (
72
+ <span className="text-[10px] text-muted-foreground">
73
+ Waits for Layer {idx - 1}
74
+ </span>
75
+ )}
76
+ </div>
77
+ <div className="ml-4 space-y-1">
78
+ {layer.map((scopeId) => {
79
+ const ss = scopeMap.get(scopeId);
80
+ return (
81
+ <div key={scopeId} className="flex items-center gap-2 text-xs">
82
+ <span className="font-mono text-muted-foreground w-8 shrink-0">
83
+ {formatScopeId(scopeId)}
84
+ </span>
85
+ <span className="truncate flex-1">{ss?.title ?? 'Unknown'}</span>
86
+ {ss?.effort_estimate && (
87
+ <span className="text-[10px] text-muted-foreground shrink-0">
88
+ {ss.effort_estimate}
89
+ </span>
90
+ )}
91
+ </div>
92
+ );
93
+ })}
94
+ </div>
95
+ {idx < layers.length - 1 && (
96
+ <div className="flex justify-center my-1">
97
+ <ArrowRight className="h-3 w-3 text-muted-foreground rotate-90" />
98
+ </div>
99
+ )}
100
+ </div>
101
+ ))}
102
+ </div>
103
+
104
+ {/* Warnings */}
105
+ {layers.length === 0 && !loading && (
106
+ <div className="flex items-center gap-2 rounded border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-xs text-amber-400">
107
+ <AlertTriangle className="h-3.5 w-3.5 shrink-0" />
108
+ Could not compute execution layers. Sprint may have issues.
109
+ </div>
110
+ )}
111
+
112
+ {/* Actions */}
113
+ <div className="flex justify-end gap-2 pt-2 border-t">
114
+ <Button variant="ghost" size="sm" onClick={onCancel} disabled={dispatching}>
115
+ Cancel
116
+ </Button>
117
+ <Button
118
+ size="sm"
119
+ onClick={handleConfirm}
120
+ disabled={dispatching || loading || layers.length === 0}
121
+ className="bg-cyan-600 hover:bg-cyan-700"
122
+ >
123
+ {dispatching ? (
124
+ <span className="flex items-center gap-1">
125
+ <span className="h-3 w-3 animate-spin rounded-full border border-white border-t-transparent" />
126
+ Dispatching...
127
+ </span>
128
+ ) : (
129
+ <>
130
+ <Play className="h-3 w-3 mr-1" /> Dispatch Sprint
131
+ </>
132
+ )}
133
+ </Button>
134
+ </div>
135
+ </DialogContent>
136
+ </Dialog>
137
+ );
138
+ }
@@ -0,0 +1,168 @@
1
+ import { useMemo } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { useScopes } from '@/hooks/useScopes';
4
+ import { useWorkflow } from '@/hooks/useWorkflow.tsx';
5
+ import { useTheme } from '@/hooks/useTheme';
6
+ import { formatScopeId } from '@/lib/utils';
7
+ import { cn } from '@/lib/utils';
8
+ import { VersionBadge } from '@/components/VersionBadge';
9
+ import type { Scope } from '@/types';
10
+
11
+ export function StatusBar() {
12
+ const { scopes } = useScopes();
13
+ const { engine } = useWorkflow();
14
+ const { neonGlass } = useTheme();
15
+ const navigate = useNavigate();
16
+
17
+ const boardColumns = useMemo(() => engine.getBoardColumns(), [engine]);
18
+ const entryPointId = useMemo(() => engine.getEntryPoint().id, [engine]);
19
+
20
+ const columnOrder = useMemo(() => {
21
+ const map = new Map<string, number>();
22
+ boardColumns.forEach((col, i) => map.set(col.id, i));
23
+ return map;
24
+ }, [boardColumns]);
25
+
26
+ const columnColorMap = useMemo(() => {
27
+ const map = new Map<string, string>();
28
+ for (const col of boardColumns) map.set(col.id, col.color);
29
+ return map;
30
+ }, [boardColumns]);
31
+
32
+ const { inProgress, needsAttention } = useMemo(() => {
33
+ const prog: Scope[] = [];
34
+ const attn: Scope[] = [];
35
+
36
+ for (const scope of scopes) {
37
+ if (scope.status === entryPointId) continue;
38
+ if (engine.isTerminalStatus(scope.status)) continue;
39
+
40
+ if (scope.blocked_by.length > 0) {
41
+ attn.push(scope);
42
+ } else {
43
+ prog.push(scope);
44
+ }
45
+ }
46
+
47
+ return { inProgress: groupByStatus(prog, columnOrder), needsAttention: groupByStatus(attn, columnOrder) };
48
+ }, [scopes, entryPointId, engine, columnOrder]);
49
+
50
+ const handleBadgeClick = (e: React.MouseEvent, scopeId: number) => {
51
+ e.stopPropagation();
52
+ navigate(`/?highlight=${scopeId}`);
53
+ };
54
+
55
+ const hasScopes = inProgress.size > 0 || needsAttention.size > 0;
56
+
57
+ return (
58
+ <div className={cn(
59
+ 'fixed bottom-0 left-24 right-0 z-40 border-t border-border bg-surface/95 backdrop-blur-sm',
60
+ neonGlass && 'ticker-glass'
61
+ )}>
62
+ <div className="flex items-center px-4 py-2">
63
+ {/* Scrollable scope badges */}
64
+ {hasScopes && (
65
+ <div className="flex min-w-0 flex-1 items-center gap-4 overflow-x-auto">
66
+ {inProgress.size > 0 && (
67
+ <>
68
+ <span className="flex-shrink-0 text-xxs uppercase tracking-wider font-normal text-muted-foreground">
69
+ In Progress
70
+ </span>
71
+ <ScopeBadges groups={inProgress} colorMap={columnColorMap} onClick={handleBadgeClick} />
72
+ </>
73
+ )}
74
+
75
+ {inProgress.size > 0 && needsAttention.size > 0 && (
76
+ <div className="h-4 w-px flex-shrink-0 bg-border" />
77
+ )}
78
+
79
+ {needsAttention.size > 0 && (
80
+ <>
81
+ <span className="flex-shrink-0 text-xxs uppercase tracking-wider font-normal text-warning-amber">
82
+ Needs Attention
83
+ </span>
84
+ <ScopeBadges groups={needsAttention} colorMap={columnColorMap} onClick={handleBadgeClick} />
85
+ </>
86
+ )}
87
+ </div>
88
+ )}
89
+
90
+ {/* Spacer when no scopes */}
91
+ {!hasScopes && <div className="flex-1" />}
92
+
93
+ {/* Version badge — pinned right */}
94
+ <div className="flex-shrink-0 ml-4">
95
+ <VersionBadge />
96
+ </div>
97
+ </div>
98
+ </div>
99
+ );
100
+ }
101
+
102
+ function ScopeBadges({
103
+ groups,
104
+ colorMap,
105
+ onClick,
106
+ }: {
107
+ groups: Map<string, Scope[]>;
108
+ colorMap: Map<string, string>;
109
+ onClick: (e: React.MouseEvent, scopeId: number) => void;
110
+ }) {
111
+ return (
112
+ <>
113
+ {Array.from(groups.entries()).map(([status, scopeList]) => {
114
+ const color = colorMap.get(status) ?? '220 70% 50%';
115
+ const hex = hslToHex(color);
116
+ return scopeList.map((scope) => (
117
+ <button
118
+ key={scope.id}
119
+ onClick={(e) => onClick(e, scope.id)}
120
+ className="flex flex-shrink-0 items-center gap-1.5 rounded-md border px-2 py-0.5 text-xxs transition-colors hover:brightness-125 cursor-pointer"
121
+ style={{
122
+ backgroundColor: `${hex}15`,
123
+ borderColor: `${hex}40`,
124
+ }}
125
+ >
126
+ <span
127
+ className="h-2 w-2 rounded-full flex-shrink-0"
128
+ style={{ backgroundColor: `hsl(${color})` }}
129
+ />
130
+ <span className="max-w-[120px] truncate">
131
+ {formatScopeId(scope.id)} {scope.title}
132
+ </span>
133
+ </button>
134
+ ));
135
+ })}
136
+ </>
137
+ );
138
+ }
139
+
140
+ function groupByStatus(scopes: Scope[], columnOrder: Map<string, number>): Map<string, Scope[]> {
141
+ const map = new Map<string, Scope[]>();
142
+ for (const scope of scopes) {
143
+ const list = map.get(scope.status);
144
+ if (list) list.push(scope);
145
+ else map.set(scope.status, [scope]);
146
+ }
147
+ // Sort by column order
148
+ return new Map(
149
+ Array.from(map.entries()).sort(
150
+ ([a], [b]) => (columnOrder.get(a) ?? 999) - (columnOrder.get(b) ?? 999)
151
+ )
152
+ );
153
+ }
154
+
155
+ function hslToHex(hsl: string): string {
156
+ const parts = hsl.match(/[\d.]+/g);
157
+ if (!parts || parts.length < 3) return '#888888';
158
+ const h = parseFloat(parts[0]);
159
+ const s = parseFloat(parts[1]) / 100;
160
+ const l = parseFloat(parts[2]) / 100;
161
+ const a = s * Math.min(l, 1 - l);
162
+ const f = (n: number) => {
163
+ const k = (n + h / 30) % 12;
164
+ const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
165
+ return Math.round(255 * color).toString(16).padStart(2, '0');
166
+ };
167
+ return `#${f(0)}${f(8)}${f(4)}`;
168
+ }
@@ -0,0 +1,67 @@
1
+ import { useDroppable } from '@dnd-kit/core';
2
+ import type { Scope, ScopeStatus, CardDisplayConfig } from '@/types';
3
+ import { ScopeCard } from './ScopeCard';
4
+ import { cn } from '@/lib/utils';
5
+
6
+ interface SwimCellProps {
7
+ laneValue: string;
8
+ status: ScopeStatus;
9
+ scopes: Scope[];
10
+ onScopeClick?: (scope: Scope) => void;
11
+ cardDisplay?: CardDisplayConfig;
12
+ dimmedIds?: Set<number>;
13
+ isDragActive: boolean;
14
+ isValidDrop: boolean;
15
+ isCollapsed: boolean;
16
+ }
17
+
18
+ export function SwimCell({
19
+ laneValue,
20
+ status,
21
+ scopes = [],
22
+ onScopeClick,
23
+ cardDisplay,
24
+ dimmedIds,
25
+ isDragActive,
26
+ isValidDrop,
27
+ isCollapsed,
28
+ }: SwimCellProps) {
29
+ const droppableId = `swim::${laneValue}::${status}`;
30
+ const { setNodeRef, isOver } = useDroppable({ id: droppableId });
31
+
32
+ if (isCollapsed) return null;
33
+
34
+ return (
35
+ <div
36
+ ref={setNodeRef}
37
+ className={cn(
38
+ 'swim-cell min-h-[48px] rounded border border-white/[0.04] p-1 transition-colors',
39
+ isDragActive && isOver && isValidDrop && 'ring-2 ring-green-500/60 border-green-500/40 bg-green-500/5',
40
+ isDragActive && isOver && !isValidDrop && 'ring-2 ring-red-500/50 border-red-500/30 bg-red-500/5',
41
+ isDragActive && !isOver && isValidDrop && 'border-green-500/20',
42
+ scopes.length === 0 && 'border-dashed border-white/[0.06]',
43
+ )}
44
+ >
45
+ <div className="space-y-1.5">
46
+ {scopes.filter((s) => !s.is_ghost).map((scope) => (
47
+ <ScopeCard
48
+ key={scope.id}
49
+ scope={scope}
50
+ onClick={onScopeClick}
51
+ cardDisplay={cardDisplay}
52
+ dimmed={dimmedIds?.has(scope.id)}
53
+ />
54
+ ))}
55
+ {scopes.filter((s) => s.is_ghost).map((scope) => (
56
+ <ScopeCard
57
+ key={scope.id}
58
+ scope={scope}
59
+ onClick={onScopeClick}
60
+ cardDisplay={cardDisplay}
61
+ dimmed={dimmedIds?.has(scope.id)}
62
+ />
63
+ ))}
64
+ </div>
65
+ </div>
66
+ );
67
+ }
@@ -0,0 +1,94 @@
1
+ import { ChevronRight } from 'lucide-react';
2
+ import type { SwimLane } from '@/lib/swimlane';
3
+ import type { Scope, ScopeStatus, CardDisplayConfig, BoardColumn } from '@/types';
4
+ import { SwimCell } from './SwimCell';
5
+ import { cn } from '@/lib/utils';
6
+
7
+ interface SwimLaneRowProps {
8
+ lane: SwimLane;
9
+ columns: BoardColumn[];
10
+ collapsedColumns: Set<string>;
11
+ isLaneCollapsed: boolean;
12
+ onToggleLane: () => void;
13
+ onScopeClick?: (scope: Scope) => void;
14
+ cardDisplay?: CardDisplayConfig;
15
+ dimmedIds?: Set<number>;
16
+ isDragActive: boolean;
17
+ validTargets: Set<ScopeStatus>;
18
+ }
19
+
20
+ export function SwimLaneRow({
21
+ lane,
22
+ columns,
23
+ collapsedColumns,
24
+ isLaneCollapsed,
25
+ onToggleLane,
26
+ onScopeClick,
27
+ cardDisplay,
28
+ dimmedIds,
29
+ isDragActive,
30
+ validTargets,
31
+ }: SwimLaneRowProps) {
32
+ if (isLaneCollapsed) {
33
+ return (
34
+ <>
35
+ {/* Lane label cell — collapsed */}
36
+ <button
37
+ onClick={onToggleLane}
38
+ className="swim-lane-header flex items-center gap-2 rounded-l px-3 py-1.5 text-left hover:bg-white/[0.04] transition-colors cursor-pointer sticky left-0 z-10 bg-background"
39
+ >
40
+ <div className={cn('h-full w-0.5 rounded-full shrink-0 self-stretch', lane.color)} />
41
+ <ChevronRight className="h-3 w-3 text-muted-foreground shrink-0" />
42
+ <span className="text-xxs font-medium text-muted-foreground truncate capitalize">
43
+ {lane.label}
44
+ </span>
45
+ <span className="ml-auto rounded-full bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground shrink-0">
46
+ {lane.count}
47
+ </span>
48
+ </button>
49
+ {/* Empty cells across columns */}
50
+ {columns.map((col) => (
51
+ <div key={col.id} className={cn(collapsedColumns.has(col.id) && 'hidden')} />
52
+ ))}
53
+ </>
54
+ );
55
+ }
56
+
57
+ return (
58
+ <>
59
+ {/* Lane label cell */}
60
+ <button
61
+ onClick={onToggleLane}
62
+ className="swim-lane-header flex items-start gap-2 rounded-l px-3 py-2 text-left hover:bg-white/[0.04] transition-colors cursor-pointer sticky left-0 z-10 bg-background"
63
+ >
64
+ <div className={cn('w-0.5 rounded-full shrink-0 min-h-[32px] self-stretch', lane.color)} />
65
+ <div className="flex flex-col gap-1 min-w-0">
66
+ <div className="flex items-center gap-1.5">
67
+ <ChevronRight className="h-3 w-3 text-muted-foreground shrink-0 rotate-90 transition-transform" />
68
+ <span className="swim-lane-label text-xxs font-medium text-foreground/80 truncate capitalize">
69
+ {lane.label}
70
+ </span>
71
+ </div>
72
+ <span className="text-[10px] text-muted-foreground">
73
+ {lane.count} scope{lane.count !== 1 ? 's' : ''}
74
+ </span>
75
+ </div>
76
+ </button>
77
+ {/* Cells per column */}
78
+ {columns.map((col) => (
79
+ <SwimCell
80
+ key={col.id}
81
+ laneValue={lane.value}
82
+ status={col.id}
83
+ scopes={lane.cells[col.id]}
84
+ onScopeClick={onScopeClick}
85
+ cardDisplay={cardDisplay}
86
+ dimmedIds={dimmedIds}
87
+ isDragActive={isDragActive}
88
+ isValidDrop={validTargets.has(col.id)}
89
+ isCollapsed={collapsedColumns.has(col.id)}
90
+ />
91
+ ))}
92
+ </>
93
+ );
94
+ }
@@ -0,0 +1,108 @@
1
+ import { Info } from 'lucide-react';
2
+ import type { SwimLane } from '@/lib/swimlane';
3
+ import type { Scope, ScopeStatus, CardDisplayConfig, BoardColumn, Sprint } from '@/types';
4
+ import { SwimLaneRow } from './SwimLaneRow';
5
+ import { cn } from '@/lib/utils';
6
+ import { useTheme } from '@/hooks/useTheme';
7
+
8
+ interface SwimlaneBoardViewProps {
9
+ lanes: SwimLane[];
10
+ columns: BoardColumn[];
11
+ collapsedColumns: Set<string>;
12
+ collapsedLanes: Set<string>;
13
+ onToggleLane: (laneValue: string) => void;
14
+ onToggleCollapse: (columnId: string) => void;
15
+ onScopeClick?: (scope: Scope) => void;
16
+ cardDisplay?: CardDisplayConfig;
17
+ dimmedIds?: Set<number>;
18
+ isDragActive: boolean;
19
+ validTargets: Set<ScopeStatus>;
20
+ sprints: Sprint[];
21
+ }
22
+
23
+ export function SwimlaneBoardView({
24
+ lanes,
25
+ columns,
26
+ collapsedColumns,
27
+ collapsedLanes,
28
+ onToggleLane,
29
+ onToggleCollapse,
30
+ onScopeClick,
31
+ cardDisplay,
32
+ dimmedIds,
33
+ isDragActive,
34
+ validTargets,
35
+ sprints,
36
+ }: SwimlaneBoardViewProps) {
37
+ const { neonGlass } = useTheme();
38
+
39
+ // Filter out collapsed columns for grid sizing
40
+ const visibleColumns = columns.filter((c) => !collapsedColumns.has(c.id));
41
+ const gridTemplateColumns = `140px ${visibleColumns.map(() => '200px').join(' ')}`;
42
+
43
+ const hasActiveSprints = sprints.some((s) =>
44
+ s.group_type === 'sprint' || (s.group_type === 'batch' && s.status !== 'completed')
45
+ );
46
+
47
+ return (
48
+ <div className="min-h-0 flex-1 overflow-auto">
49
+ {/* Sprint info banner */}
50
+ {hasActiveSprints && (
51
+ <div className="mb-2 flex items-center gap-2 rounded border border-primary/20 bg-primary/5 px-3 py-1.5 text-xs text-muted-foreground">
52
+ <Info className="h-3.5 w-3.5 text-primary shrink-0" />
53
+ Sprint groups are hidden in swimlane view
54
+ </div>
55
+ )}
56
+
57
+ <div
58
+ className="grid gap-px pb-4"
59
+ style={{ gridTemplateColumns, width: 'max-content' }}
60
+ >
61
+ {/* Column headers — sticky top row */}
62
+ <div className="sticky top-0 z-20 bg-background" />
63
+ {visibleColumns.map((col) => (
64
+ <button
65
+ key={col.id}
66
+ onClick={() => onToggleCollapse(col.id)}
67
+ className={cn(
68
+ 'sticky top-0 z-20 flex items-center gap-1.5 rounded-t border-b border-white/[0.06] bg-background px-2 py-1.5 text-left cursor-pointer hover:bg-white/[0.03] transition-colors',
69
+ neonGlass && 'border-b-white/[0.08]',
70
+ )}
71
+ >
72
+ <div className={cn('h-2 w-2 rounded-full shrink-0', neonGlass && 'animate-glow-pulse')} style={{ backgroundColor: `hsl(${col.color})` }} />
73
+ <span className="text-xxs uppercase tracking-wider font-normal text-muted-foreground truncate">
74
+ {col.label}
75
+ </span>
76
+ </button>
77
+ ))}
78
+
79
+ {/* Lane rows */}
80
+ {lanes.map((lane) => (
81
+ <SwimLaneRow
82
+ key={lane.value}
83
+ lane={lane}
84
+ columns={visibleColumns}
85
+ collapsedColumns={collapsedColumns}
86
+ isLaneCollapsed={collapsedLanes.has(lane.value)}
87
+ onToggleLane={() => onToggleLane(lane.value)}
88
+ onScopeClick={onScopeClick}
89
+ cardDisplay={cardDisplay}
90
+ dimmedIds={dimmedIds}
91
+ isDragActive={isDragActive}
92
+ validTargets={validTargets}
93
+ />
94
+ ))}
95
+
96
+ {/* Empty state */}
97
+ {lanes.length === 0 && (
98
+ <>
99
+ <div />
100
+ <div className={cn('col-span-full py-12 text-center text-xs text-muted-foreground')}>
101
+ No scopes to display
102
+ </div>
103
+ </>
104
+ )}
105
+ </div>
106
+ </div>
107
+ );
108
+ }