orbital-command 0.2.0 → 1.0.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 (431) hide show
  1. package/README.md +67 -42
  2. package/bin/commands/config.js +19 -0
  3. package/bin/commands/events.js +40 -0
  4. package/bin/commands/launch.js +126 -0
  5. package/bin/commands/manifest.js +283 -0
  6. package/bin/commands/registry.js +104 -0
  7. package/bin/commands/update.js +24 -0
  8. package/bin/lib/helpers.js +229 -0
  9. package/bin/orbital.js +147 -319
  10. package/dist/assets/Landing-CfQdHR0N.js +11 -0
  11. package/dist/assets/PrimitivesConfig-DThSipFy.js +32 -0
  12. package/dist/assets/QualityGates-B4kxM5UU.js +26 -0
  13. package/dist/assets/SessionTimeline-Bz1iZnmg.js +1 -0
  14. package/dist/assets/Settings-DLcZwbCT.js +12 -0
  15. package/dist/assets/SourceControl-BMNIz7Lt.js +36 -0
  16. package/dist/assets/WorkflowVisualizer-CxuSBOYu.js +69 -0
  17. package/dist/assets/arrow-down-DVPp6_qp.js +6 -0
  18. package/dist/assets/bot-NFaJBDn_.js +6 -0
  19. package/dist/assets/charts-LGLb8hyU.js +68 -0
  20. package/dist/assets/circle-x-IsFCkBZu.js +6 -0
  21. package/dist/assets/file-text-J1cebZXF.js +6 -0
  22. package/dist/assets/globe-WzeyHsUc.js +6 -0
  23. package/dist/assets/index-BdJ57EhC.css +1 -0
  24. package/dist/assets/index-o4ScMAuR.js +349 -0
  25. package/dist/assets/key-CKR8JJSj.js +6 -0
  26. package/dist/assets/minus-CHBsJyjp.js +6 -0
  27. package/dist/assets/radio-xqZaR-Uk.js +6 -0
  28. package/dist/assets/rocket-D_xvvNG6.js +6 -0
  29. package/dist/assets/shield-TdB1yv_a.js +6 -0
  30. package/dist/assets/ui-BmsSg9jU.js +53 -0
  31. package/dist/assets/useSocketListener-0L5yiN5i.js +1 -0
  32. package/dist/assets/useWorkflowEditor-CqeRWVQX.js +11 -0
  33. package/dist/assets/{vendor-Dzv9lrRc.js → vendor-Bqt8AJn2.js} +1 -1
  34. package/dist/assets/workflow-constants-Rw-GmgHZ.js +6 -0
  35. package/dist/assets/zap-C9wqYMpl.js +6 -0
  36. package/dist/favicon.svg +1 -0
  37. package/dist/index.html +6 -5
  38. package/dist/server/server/__tests__/data-routes.test.js +126 -0
  39. package/dist/server/server/__tests__/helpers/db.js +17 -0
  40. package/dist/server/server/__tests__/helpers/mock-emitter.js +8 -0
  41. package/dist/server/server/__tests__/scope-routes.test.js +138 -0
  42. package/dist/server/server/__tests__/sprint-routes.test.js +102 -0
  43. package/dist/server/server/__tests__/workflow-routes.test.js +107 -0
  44. package/dist/server/server/config-migrator.js +135 -0
  45. package/dist/server/server/config.js +51 -7
  46. package/dist/server/server/database.js +21 -28
  47. package/dist/server/server/global-config.js +143 -0
  48. package/dist/server/server/index.js +118 -276
  49. package/dist/server/server/init.js +243 -225
  50. package/dist/server/server/launch.js +29 -0
  51. package/dist/server/server/manifest-types.js +8 -0
  52. package/dist/server/server/manifest.js +454 -0
  53. package/dist/server/server/migrate-legacy.js +229 -0
  54. package/dist/server/server/parsers/event-parser.js +4 -1
  55. package/dist/server/server/parsers/event-parser.test.js +117 -0
  56. package/dist/server/server/parsers/scope-parser.js +74 -28
  57. package/dist/server/server/parsers/scope-parser.test.js +230 -0
  58. package/dist/server/server/project-context.js +265 -0
  59. package/dist/server/server/project-emitter.js +41 -0
  60. package/dist/server/server/project-manager.js +297 -0
  61. package/dist/server/server/routes/aggregate-routes.js +871 -0
  62. package/dist/server/server/routes/config-routes.js +41 -90
  63. package/dist/server/server/routes/data-routes.js +25 -123
  64. package/dist/server/server/routes/dispatch-routes.js +37 -15
  65. package/dist/server/server/routes/git-routes.js +74 -0
  66. package/dist/server/server/routes/manifest-routes.js +319 -0
  67. package/dist/server/server/routes/scope-routes.js +45 -28
  68. package/dist/server/server/routes/sync-routes.js +134 -0
  69. package/dist/server/server/routes/version-routes.js +1 -15
  70. package/dist/server/server/routes/workflow-routes.js +9 -3
  71. package/dist/server/server/schema.js +3 -0
  72. package/dist/server/server/services/batch-orchestrator.js +41 -17
  73. package/dist/server/server/services/claude-session-service.js +17 -14
  74. package/dist/server/server/services/config-service.js +10 -1
  75. package/dist/server/server/services/deploy-service.test.js +119 -0
  76. package/dist/server/server/services/event-service.js +64 -1
  77. package/dist/server/server/services/event-service.test.js +191 -0
  78. package/dist/server/server/services/gate-service.test.js +105 -0
  79. package/dist/server/server/services/git-service.js +108 -4
  80. package/dist/server/server/services/github-service.js +110 -2
  81. package/dist/server/server/services/readiness-service.test.js +190 -0
  82. package/dist/server/server/services/scope-cache.js +5 -1
  83. package/dist/server/server/services/scope-cache.test.js +142 -0
  84. package/dist/server/server/services/scope-service.js +222 -131
  85. package/dist/server/server/services/scope-service.test.js +137 -0
  86. package/dist/server/server/services/sprint-orchestrator.js +29 -15
  87. package/dist/server/server/services/sprint-service.js +23 -3
  88. package/dist/server/server/services/sprint-service.test.js +238 -0
  89. package/dist/server/server/services/sync-service.js +434 -0
  90. package/dist/server/server/services/sync-types.js +2 -0
  91. package/dist/server/server/services/workflow-service.js +26 -5
  92. package/dist/server/server/services/workflow-service.test.js +159 -0
  93. package/dist/server/server/settings-sync.js +284 -0
  94. package/dist/server/server/uninstall.js +195 -0
  95. package/dist/server/server/update-planner.js +279 -0
  96. package/dist/server/server/update.js +212 -0
  97. package/dist/server/server/utils/cc-hooks-parser.js +3 -0
  98. package/dist/server/server/utils/cc-hooks-parser.test.js +86 -0
  99. package/dist/server/server/utils/dispatch-utils.js +83 -24
  100. package/dist/server/server/utils/dispatch-utils.test.js +182 -0
  101. package/dist/server/server/utils/flag-builder.js +54 -0
  102. package/dist/server/server/utils/json-fields.js +14 -0
  103. package/dist/server/server/utils/json-fields.test.js +73 -0
  104. package/dist/server/server/utils/logger.js +37 -3
  105. package/dist/server/server/utils/package-info.js +30 -0
  106. package/dist/server/server/utils/route-helpers.js +47 -0
  107. package/dist/server/server/utils/route-helpers.test.js +115 -0
  108. package/dist/server/server/utils/terminal-launcher.js +79 -25
  109. package/dist/server/server/utils/worktree-manager.js +13 -4
  110. package/dist/server/server/validator.js +230 -0
  111. package/dist/server/server/watchers/event-watcher.js +28 -13
  112. package/dist/server/server/watchers/global-watcher.js +63 -0
  113. package/dist/server/server/watchers/scope-watcher.js +27 -12
  114. package/dist/server/server/wizard/config-editor.js +237 -0
  115. package/dist/server/server/wizard/detect.js +96 -0
  116. package/dist/server/server/wizard/doctor.js +115 -0
  117. package/dist/server/server/wizard/index.js +340 -0
  118. package/dist/server/server/wizard/phases/confirm.js +39 -0
  119. package/dist/server/server/wizard/phases/project-setup.js +90 -0
  120. package/dist/server/server/wizard/phases/setup-wizard.js +66 -0
  121. package/dist/server/server/wizard/phases/welcome.js +32 -0
  122. package/dist/server/server/wizard/phases/workflow-setup.js +22 -0
  123. package/dist/server/server/wizard/types.js +29 -0
  124. package/dist/server/server/wizard/ui.js +73 -0
  125. package/dist/server/shared/__fixtures__/workflow-configs.js +75 -0
  126. package/dist/server/shared/api-types.js +80 -1
  127. package/dist/server/shared/default-workflow.json +65 -0
  128. package/dist/server/shared/onboarding-tour.test.js +81 -0
  129. package/dist/server/shared/project-colors.js +24 -0
  130. package/dist/server/shared/workflow-config.test.js +84 -0
  131. package/dist/server/shared/workflow-engine.js +1 -1
  132. package/dist/server/shared/workflow-engine.test.js +302 -0
  133. package/dist/server/shared/workflow-normalizer.js +101 -0
  134. package/dist/server/shared/workflow-normalizer.test.js +100 -0
  135. package/dist/server/src/components/onboarding/tour-steps.js +84 -0
  136. package/package.json +34 -29
  137. package/schemas/orbital.config.schema.json +2 -5
  138. package/scripts/postinstall.js +18 -6
  139. package/scripts/release.sh +53 -0
  140. package/server/__tests__/data-routes.test.ts +151 -0
  141. package/server/__tests__/helpers/db.ts +19 -0
  142. package/server/__tests__/helpers/mock-emitter.ts +10 -0
  143. package/server/__tests__/scope-routes.test.ts +158 -0
  144. package/server/__tests__/sprint-routes.test.ts +118 -0
  145. package/server/__tests__/workflow-routes.test.ts +120 -0
  146. package/server/config-migrator.ts +160 -0
  147. package/server/config.ts +64 -12
  148. package/server/database.ts +22 -31
  149. package/server/global-config.ts +204 -0
  150. package/server/index.ts +139 -316
  151. package/server/init.ts +266 -234
  152. package/server/launch.ts +32 -0
  153. package/server/manifest-types.ts +145 -0
  154. package/server/manifest.ts +494 -0
  155. package/server/migrate-legacy.ts +290 -0
  156. package/server/parsers/event-parser.test.ts +135 -0
  157. package/server/parsers/event-parser.ts +4 -1
  158. package/server/parsers/scope-parser.test.ts +270 -0
  159. package/server/parsers/scope-parser.ts +79 -31
  160. package/server/project-context.ts +325 -0
  161. package/server/project-emitter.ts +50 -0
  162. package/server/project-manager.ts +368 -0
  163. package/server/routes/aggregate-routes.ts +968 -0
  164. package/server/routes/config-routes.ts +43 -85
  165. package/server/routes/data-routes.ts +34 -156
  166. package/server/routes/dispatch-routes.ts +46 -17
  167. package/server/routes/git-routes.ts +77 -0
  168. package/server/routes/manifest-routes.ts +388 -0
  169. package/server/routes/scope-routes.ts +39 -30
  170. package/server/routes/sync-routes.ts +175 -0
  171. package/server/routes/version-routes.ts +1 -16
  172. package/server/routes/workflow-routes.ts +9 -3
  173. package/server/schema.ts +3 -0
  174. package/server/services/batch-orchestrator.ts +41 -17
  175. package/server/services/claude-session-service.ts +16 -14
  176. package/server/services/config-service.ts +10 -1
  177. package/server/services/deploy-service.test.ts +145 -0
  178. package/server/services/deploy-service.ts +2 -2
  179. package/server/services/event-service.test.ts +242 -0
  180. package/server/services/event-service.ts +92 -3
  181. package/server/services/gate-service.test.ts +131 -0
  182. package/server/services/gate-service.ts +2 -2
  183. package/server/services/git-service.ts +137 -4
  184. package/server/services/github-service.ts +120 -2
  185. package/server/services/readiness-service.test.ts +217 -0
  186. package/server/services/scope-cache.test.ts +167 -0
  187. package/server/services/scope-cache.ts +4 -1
  188. package/server/services/scope-service.test.ts +169 -0
  189. package/server/services/scope-service.ts +224 -130
  190. package/server/services/sprint-orchestrator.ts +30 -15
  191. package/server/services/sprint-service.test.ts +271 -0
  192. package/server/services/sprint-service.ts +29 -5
  193. package/server/services/sync-service.ts +482 -0
  194. package/server/services/sync-types.ts +77 -0
  195. package/server/services/workflow-service.test.ts +190 -0
  196. package/server/services/workflow-service.ts +29 -9
  197. package/server/settings-sync.ts +359 -0
  198. package/server/uninstall.ts +214 -0
  199. package/server/update-planner.ts +346 -0
  200. package/server/update.ts +263 -0
  201. package/server/utils/cc-hooks-parser.test.ts +96 -0
  202. package/server/utils/cc-hooks-parser.ts +4 -0
  203. package/server/utils/dispatch-utils.test.ts +245 -0
  204. package/server/utils/dispatch-utils.ts +102 -30
  205. package/server/utils/flag-builder.ts +56 -0
  206. package/server/utils/json-fields.test.ts +83 -0
  207. package/server/utils/json-fields.ts +14 -0
  208. package/server/utils/logger.ts +40 -3
  209. package/server/utils/package-info.ts +32 -0
  210. package/server/utils/route-helpers.test.ts +144 -0
  211. package/server/utils/route-helpers.ts +50 -0
  212. package/server/utils/terminal-launcher.ts +85 -25
  213. package/server/utils/worktree-manager.ts +9 -4
  214. package/server/validator.ts +270 -0
  215. package/server/watchers/event-watcher.ts +24 -12
  216. package/server/watchers/global-watcher.ts +77 -0
  217. package/server/watchers/scope-watcher.ts +21 -9
  218. package/server/wizard/config-editor.ts +248 -0
  219. package/server/wizard/detect.ts +104 -0
  220. package/server/wizard/doctor.ts +114 -0
  221. package/server/wizard/index.ts +438 -0
  222. package/server/wizard/phases/confirm.ts +45 -0
  223. package/server/wizard/phases/project-setup.ts +106 -0
  224. package/server/wizard/phases/setup-wizard.ts +78 -0
  225. package/server/wizard/phases/welcome.ts +39 -0
  226. package/server/wizard/phases/workflow-setup.ts +28 -0
  227. package/server/wizard/types.ts +56 -0
  228. package/server/wizard/ui.ts +92 -0
  229. package/shared/__fixtures__/workflow-configs.ts +80 -0
  230. package/shared/api-types.ts +106 -0
  231. package/shared/onboarding-tour.test.ts +94 -0
  232. package/shared/project-colors.ts +24 -0
  233. package/shared/workflow-config.test.ts +111 -0
  234. package/shared/workflow-config.ts +7 -0
  235. package/shared/workflow-engine.test.ts +388 -0
  236. package/shared/workflow-engine.ts +1 -1
  237. package/shared/workflow-normalizer.test.ts +119 -0
  238. package/shared/workflow-normalizer.ts +118 -0
  239. package/templates/agents/QUICK-REFERENCE.md +1 -0
  240. package/templates/agents/README.md +1 -0
  241. package/templates/agents/SKILL-TRIGGERS.md +11 -0
  242. package/templates/agents/green-team/deep-dive.md +361 -0
  243. package/templates/hooks/end-session.sh +4 -1
  244. package/templates/hooks/init-session.sh +1 -0
  245. package/templates/hooks/orbital-emit.sh +2 -2
  246. package/templates/hooks/orbital-report-deploy.sh +4 -4
  247. package/templates/hooks/orbital-report-gates.sh +4 -4
  248. package/templates/hooks/orbital-scope-update.sh +1 -1
  249. package/templates/hooks/scope-commit-logger.sh +2 -2
  250. package/templates/hooks/scope-create-cleanup.sh +2 -2
  251. package/templates/hooks/scope-create-gate.sh +2 -5
  252. package/templates/hooks/scope-gate.sh +4 -6
  253. package/templates/hooks/scope-helpers.sh +28 -1
  254. package/templates/hooks/scope-lifecycle-gate.sh +14 -5
  255. package/templates/hooks/scope-prepare.sh +67 -12
  256. package/templates/hooks/scope-transition.sh +14 -6
  257. package/templates/hooks/time-tracker.sh +2 -5
  258. package/templates/migrations/renames.json +1 -0
  259. package/templates/orbital.config.json +8 -6
  260. package/{shared/default-workflow.json → templates/presets/default.json} +65 -0
  261. package/templates/presets/development.json +4 -4
  262. package/templates/presets/gitflow.json +7 -0
  263. package/templates/prompts/README.md +23 -0
  264. package/templates/prompts/deep-dive-audit.md +94 -0
  265. package/templates/quick/rules.md +56 -5
  266. package/templates/settings-hooks.json +1 -1
  267. package/templates/skills/git-commit/SKILL.md +27 -7
  268. package/templates/skills/git-dev/SKILL.md +13 -4
  269. package/templates/skills/git-main/SKILL.md +13 -3
  270. package/templates/skills/git-production/SKILL.md +9 -2
  271. package/templates/skills/git-staging/SKILL.md +11 -3
  272. package/templates/skills/scope-create/SKILL.md +17 -3
  273. package/templates/skills/scope-fix-review/SKILL.md +14 -7
  274. package/templates/skills/scope-implement/SKILL.md +15 -4
  275. package/templates/skills/scope-post-review/SKILL.md +77 -7
  276. package/templates/skills/scope-pre-review/SKILL.md +11 -4
  277. package/templates/skills/scope-verify/SKILL.md +5 -3
  278. package/templates/skills/test-code-review/SKILL.md +41 -33
  279. package/templates/skills/test-scaffold/SKILL.md +222 -0
  280. package/dist/assets/WorkflowVisualizer-BZ21PIIF.js +0 -84
  281. package/dist/assets/charts-D__PA1zp.js +0 -72
  282. package/dist/assets/index-D1G6i0nS.css +0 -1
  283. package/dist/assets/index-DpItvKpf.js +0 -419
  284. package/dist/assets/ui-BvF022GT.js +0 -53
  285. package/index.html +0 -15
  286. package/postcss.config.js +0 -6
  287. package/src/App.tsx +0 -33
  288. package/src/components/AgentBadge.tsx +0 -40
  289. package/src/components/BatchPreflightModal.tsx +0 -115
  290. package/src/components/CardDisplayToggle.tsx +0 -74
  291. package/src/components/ColumnHeaderActions.tsx +0 -55
  292. package/src/components/ColumnMenu.tsx +0 -99
  293. package/src/components/DeployHistory.tsx +0 -141
  294. package/src/components/DispatchModal.tsx +0 -164
  295. package/src/components/DispatchPopover.tsx +0 -139
  296. package/src/components/DragOverlay.tsx +0 -25
  297. package/src/components/DriftSidebar.tsx +0 -140
  298. package/src/components/EnvironmentStrip.tsx +0 -88
  299. package/src/components/ErrorBoundary.tsx +0 -62
  300. package/src/components/FilterChip.tsx +0 -105
  301. package/src/components/GateIndicator.tsx +0 -33
  302. package/src/components/IdeaDetailModal.tsx +0 -190
  303. package/src/components/IdeaFormDialog.tsx +0 -113
  304. package/src/components/KanbanColumn.tsx +0 -201
  305. package/src/components/MarkdownRenderer.tsx +0 -114
  306. package/src/components/NeonGrid.tsx +0 -128
  307. package/src/components/PromotionQueue.tsx +0 -89
  308. package/src/components/ScopeCard.tsx +0 -234
  309. package/src/components/ScopeDetailModal.tsx +0 -255
  310. package/src/components/ScopeFilterBar.tsx +0 -152
  311. package/src/components/SearchInput.tsx +0 -102
  312. package/src/components/SessionPanel.tsx +0 -335
  313. package/src/components/SprintContainer.tsx +0 -303
  314. package/src/components/SprintDependencyDialog.tsx +0 -78
  315. package/src/components/SprintPreflightModal.tsx +0 -138
  316. package/src/components/StatusBar.tsx +0 -168
  317. package/src/components/SwimCell.tsx +0 -67
  318. package/src/components/SwimLaneRow.tsx +0 -94
  319. package/src/components/SwimlaneBoardView.tsx +0 -108
  320. package/src/components/VersionBadge.tsx +0 -139
  321. package/src/components/ViewModeSelector.tsx +0 -114
  322. package/src/components/config/AgentChip.tsx +0 -53
  323. package/src/components/config/AgentCreateDialog.tsx +0 -321
  324. package/src/components/config/AgentEditor.tsx +0 -175
  325. package/src/components/config/DirectoryTree.tsx +0 -582
  326. package/src/components/config/FileEditor.tsx +0 -550
  327. package/src/components/config/HookChip.tsx +0 -50
  328. package/src/components/config/StageCard.tsx +0 -198
  329. package/src/components/config/TransitionZone.tsx +0 -173
  330. package/src/components/config/UnifiedWorkflowPipeline.tsx +0 -216
  331. package/src/components/config/WorkflowPipeline.tsx +0 -161
  332. package/src/components/source-control/BranchList.tsx +0 -93
  333. package/src/components/source-control/BranchPanel.tsx +0 -105
  334. package/src/components/source-control/CommitLog.tsx +0 -100
  335. package/src/components/source-control/CommitRow.tsx +0 -47
  336. package/src/components/source-control/GitHubPanel.tsx +0 -110
  337. package/src/components/source-control/GitHubSetupGuide.tsx +0 -52
  338. package/src/components/source-control/GitOverviewBar.tsx +0 -101
  339. package/src/components/source-control/PullRequestList.tsx +0 -69
  340. package/src/components/source-control/WorktreeList.tsx +0 -80
  341. package/src/components/ui/badge.tsx +0 -41
  342. package/src/components/ui/button.tsx +0 -55
  343. package/src/components/ui/card.tsx +0 -78
  344. package/src/components/ui/dialog.tsx +0 -94
  345. package/src/components/ui/popover.tsx +0 -33
  346. package/src/components/ui/scroll-area.tsx +0 -54
  347. package/src/components/ui/separator.tsx +0 -28
  348. package/src/components/ui/tabs.tsx +0 -52
  349. package/src/components/ui/toggle-switch.tsx +0 -35
  350. package/src/components/ui/tooltip.tsx +0 -27
  351. package/src/components/workflow/AddEdgeDialog.tsx +0 -217
  352. package/src/components/workflow/AddListDialog.tsx +0 -201
  353. package/src/components/workflow/ChecklistEditor.tsx +0 -239
  354. package/src/components/workflow/CommandPrefixManager.tsx +0 -118
  355. package/src/components/workflow/ConfigSettingsPanel.tsx +0 -189
  356. package/src/components/workflow/DirectionSelector.tsx +0 -133
  357. package/src/components/workflow/DispatchConfigPanel.tsx +0 -180
  358. package/src/components/workflow/EdgeDetailPanel.tsx +0 -236
  359. package/src/components/workflow/EdgePropertyEditor.tsx +0 -251
  360. package/src/components/workflow/EditToolbar.tsx +0 -138
  361. package/src/components/workflow/HookDetailPanel.tsx +0 -250
  362. package/src/components/workflow/HookExecutionLog.tsx +0 -24
  363. package/src/components/workflow/HookSourceModal.tsx +0 -129
  364. package/src/components/workflow/HooksDashboard.tsx +0 -363
  365. package/src/components/workflow/ListPropertyEditor.tsx +0 -251
  366. package/src/components/workflow/MigrationPreviewDialog.tsx +0 -237
  367. package/src/components/workflow/MovementRulesPanel.tsx +0 -188
  368. package/src/components/workflow/NodeDetailPanel.tsx +0 -245
  369. package/src/components/workflow/PresetSelector.tsx +0 -414
  370. package/src/components/workflow/SkillCommandBuilder.tsx +0 -174
  371. package/src/components/workflow/WorkflowEdgeComponent.tsx +0 -145
  372. package/src/components/workflow/WorkflowNode.tsx +0 -147
  373. package/src/components/workflow/graphLayout.ts +0 -186
  374. package/src/components/workflow/mergeHooks.ts +0 -85
  375. package/src/components/workflow/useEditHistory.ts +0 -88
  376. package/src/components/workflow/useWorkflowEditor.ts +0 -262
  377. package/src/components/workflow/validateConfig.ts +0 -70
  378. package/src/hooks/useActiveDispatches.ts +0 -198
  379. package/src/hooks/useBoardSettings.ts +0 -170
  380. package/src/hooks/useCardDisplay.ts +0 -57
  381. package/src/hooks/useCcHooks.ts +0 -24
  382. package/src/hooks/useConfigTree.ts +0 -51
  383. package/src/hooks/useEnforcementRules.ts +0 -46
  384. package/src/hooks/useEvents.ts +0 -59
  385. package/src/hooks/useFileEditor.ts +0 -165
  386. package/src/hooks/useGates.ts +0 -57
  387. package/src/hooks/useIdeaActions.ts +0 -53
  388. package/src/hooks/useKanbanDnd.ts +0 -410
  389. package/src/hooks/useOrbitalConfig.ts +0 -54
  390. package/src/hooks/usePipeline.ts +0 -47
  391. package/src/hooks/usePipelineData.ts +0 -338
  392. package/src/hooks/useReconnect.ts +0 -25
  393. package/src/hooks/useScopeFilters.ts +0 -125
  394. package/src/hooks/useScopeSessions.ts +0 -44
  395. package/src/hooks/useScopes.ts +0 -67
  396. package/src/hooks/useSearch.ts +0 -67
  397. package/src/hooks/useSettings.tsx +0 -187
  398. package/src/hooks/useSocket.ts +0 -25
  399. package/src/hooks/useSourceControl.ts +0 -105
  400. package/src/hooks/useSprintPreflight.ts +0 -55
  401. package/src/hooks/useSprints.ts +0 -154
  402. package/src/hooks/useStatusBarHighlight.ts +0 -18
  403. package/src/hooks/useSwimlaneBoardSettings.ts +0 -104
  404. package/src/hooks/useTheme.ts +0 -9
  405. package/src/hooks/useTransitionReadiness.ts +0 -53
  406. package/src/hooks/useVersion.ts +0 -155
  407. package/src/hooks/useViolations.ts +0 -65
  408. package/src/hooks/useWorkflow.tsx +0 -125
  409. package/src/hooks/useZoomModifier.ts +0 -19
  410. package/src/index.css +0 -797
  411. package/src/layouts/DashboardLayout.tsx +0 -113
  412. package/src/lib/collisionDetection.ts +0 -20
  413. package/src/lib/scope-fields.ts +0 -61
  414. package/src/lib/swimlane.ts +0 -146
  415. package/src/lib/utils.ts +0 -15
  416. package/src/main.tsx +0 -19
  417. package/src/socket.ts +0 -11
  418. package/src/types/index.ts +0 -497
  419. package/src/views/AgentFeed.tsx +0 -339
  420. package/src/views/DeployPipeline.tsx +0 -59
  421. package/src/views/EnforcementView.tsx +0 -378
  422. package/src/views/PrimitivesConfig.tsx +0 -500
  423. package/src/views/QualityGates.tsx +0 -1012
  424. package/src/views/ScopeBoard.tsx +0 -454
  425. package/src/views/SessionTimeline.tsx +0 -516
  426. package/src/views/Settings.tsx +0 -183
  427. package/src/views/SourceControl.tsx +0 -95
  428. package/src/views/WorkflowVisualizer.tsx +0 -382
  429. package/tailwind.config.js +0 -161
  430. package/tsconfig.json +0 -25
  431. package/vite.config.ts +0 -38
@@ -1,5 +1,6 @@
1
1
  import { Router } from 'express';
2
2
  import { ConfigService, isValidPrimitiveType } from '../services/config-service.js';
3
+ import { catchRoute, inferErrorStatus } from '../utils/route-helpers.js';
3
4
  export function createConfigRoutes({ projectRoot, workflowService: _workflowService, io }) {
4
5
  const router = Router();
5
6
  const configService = new ConfigService(projectRoot);
@@ -12,21 +13,16 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
12
13
  return typeParam;
13
14
  }
14
15
  // GET /config/:type/tree — directory tree with frontmatter
15
- router.get('/config/:type/tree', (req, res) => {
16
+ router.get('/config/:type/tree', catchRoute((req, res) => {
16
17
  const type = parseType(req.params.type, res);
17
18
  if (!type)
18
19
  return;
19
- try {
20
- const basePath = configService.getBasePath(type);
21
- const tree = configService.scanDirectory(basePath);
22
- res.json({ success: true, data: tree });
23
- }
24
- catch (err) {
25
- res.status(500).json({ success: false, error: errMsg(err) });
26
- }
27
- });
20
+ const basePath = configService.getBasePath(type);
21
+ const tree = configService.scanDirectory(basePath);
22
+ res.json({ success: true, data: tree });
23
+ }));
28
24
  // GET /config/:type/file?path=<relative> — file content
29
- router.get('/config/:type/file', (req, res) => {
25
+ router.get('/config/:type/file', catchRoute((req, res) => {
30
26
  const type = parseType(req.params.type, res);
31
27
  if (!type)
32
28
  return;
@@ -35,19 +31,12 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
35
31
  res.status(400).json({ success: false, error: 'path query parameter is required' });
36
32
  return;
37
33
  }
38
- try {
39
- const basePath = configService.getBasePath(type);
40
- const content = configService.readFile(basePath, filePath);
41
- res.json({ success: true, data: { path: filePath, content } });
42
- }
43
- catch (err) {
44
- const msg = errMsg(err);
45
- const status = msg.includes('traversal') ? 403 : msg.includes('ENOENT') || msg.includes('not found') ? 404 : 500;
46
- res.status(status).json({ success: false, error: msg });
47
- }
48
- });
34
+ const basePath = configService.getBasePath(type);
35
+ const content = configService.readFile(basePath, filePath);
36
+ res.json({ success: true, data: { path: filePath, content } });
37
+ }, inferErrorStatus));
49
38
  // PUT /config/:type/file — save file { path, content }
50
- router.put('/config/:type/file', (req, res) => {
39
+ router.put('/config/:type/file', catchRoute((req, res) => {
51
40
  const type = parseType(req.params.type, res);
52
41
  if (!type)
53
42
  return;
@@ -56,20 +45,13 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
56
45
  res.status(400).json({ success: false, error: 'path and content are required' });
57
46
  return;
58
47
  }
59
- try {
60
- const basePath = configService.getBasePath(type);
61
- configService.writeFile(basePath, filePath, content);
62
- io.emit(`config:${type}:changed`, { action: 'updated', path: filePath });
63
- res.json({ success: true });
64
- }
65
- catch (err) {
66
- const msg = errMsg(err);
67
- const status = msg.includes('traversal') ? 403 : msg.includes('not found') ? 404 : 500;
68
- res.status(status).json({ success: false, error: msg });
69
- }
70
- });
48
+ const basePath = configService.getBasePath(type);
49
+ configService.writeFile(basePath, filePath, content);
50
+ io.emit(`config:${type}:changed`, { action: 'updated', path: filePath });
51
+ res.json({ success: true });
52
+ }, inferErrorStatus));
71
53
  // POST /config/:type/file — create file { path, content }
72
- router.post('/config/:type/file', (req, res) => {
54
+ router.post('/config/:type/file', catchRoute((req, res) => {
73
55
  const type = parseType(req.params.type, res);
74
56
  if (!type)
75
57
  return;
@@ -78,20 +60,13 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
78
60
  res.status(400).json({ success: false, error: 'path and content are required' });
79
61
  return;
80
62
  }
81
- try {
82
- const basePath = configService.getBasePath(type);
83
- configService.createFile(basePath, filePath, content);
84
- io.emit(`config:${type}:changed`, { action: 'created', path: filePath });
85
- res.status(201).json({ success: true });
86
- }
87
- catch (err) {
88
- const msg = errMsg(err);
89
- const status = msg.includes('traversal') ? 403 : msg.includes('already exists') ? 409 : 500;
90
- res.status(status).json({ success: false, error: msg });
91
- }
92
- });
63
+ const basePath = configService.getBasePath(type);
64
+ configService.createFile(basePath, filePath, content);
65
+ io.emit(`config:${type}:changed`, { action: 'created', path: filePath });
66
+ res.status(201).json({ success: true });
67
+ }, inferErrorStatus));
93
68
  // DELETE /config/:type/file?path=<relative> — delete file
94
- router.delete('/config/:type/file', (req, res) => {
69
+ router.delete('/config/:type/file', catchRoute((req, res) => {
95
70
  const type = parseType(req.params.type, res);
96
71
  if (!type)
97
72
  return;
@@ -100,20 +75,13 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
100
75
  res.status(400).json({ success: false, error: 'path query parameter is required' });
101
76
  return;
102
77
  }
103
- try {
104
- const basePath = configService.getBasePath(type);
105
- configService.deleteFile(basePath, filePath);
106
- io.emit(`config:${type}:changed`, { action: 'deleted', path: filePath });
107
- res.json({ success: true });
108
- }
109
- catch (err) {
110
- const msg = errMsg(err);
111
- const status = msg.includes('traversal') ? 403 : msg.includes('not found') ? 404 : msg.includes('directory') ? 400 : 500;
112
- res.status(status).json({ success: false, error: msg });
113
- }
114
- });
78
+ const basePath = configService.getBasePath(type);
79
+ configService.deleteFile(basePath, filePath);
80
+ io.emit(`config:${type}:changed`, { action: 'deleted', path: filePath });
81
+ res.json({ success: true });
82
+ }, inferErrorStatus));
115
83
  // POST /config/:type/rename — rename { oldPath, newPath }
116
- router.post('/config/:type/rename', (req, res) => {
84
+ router.post('/config/:type/rename', catchRoute((req, res) => {
117
85
  const type = parseType(req.params.type, res);
118
86
  if (!type)
119
87
  return;
@@ -122,20 +90,13 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
122
90
  res.status(400).json({ success: false, error: 'oldPath and newPath are required' });
123
91
  return;
124
92
  }
125
- try {
126
- const basePath = configService.getBasePath(type);
127
- configService.renameFile(basePath, oldPath, newPath);
128
- io.emit(`config:${type}:changed`, { action: 'renamed', oldPath, newPath });
129
- res.json({ success: true });
130
- }
131
- catch (err) {
132
- const msg = errMsg(err);
133
- const status = msg.includes('traversal') ? 403 : msg.includes('not found') ? 404 : msg.includes('already exists') ? 409 : 500;
134
- res.status(status).json({ success: false, error: msg });
135
- }
136
- });
93
+ const basePath = configService.getBasePath(type);
94
+ configService.renameFile(basePath, oldPath, newPath);
95
+ io.emit(`config:${type}:changed`, { action: 'renamed', oldPath, newPath });
96
+ res.json({ success: true });
97
+ }, inferErrorStatus));
137
98
  // POST /config/:type/folder — create folder { path }
138
- router.post('/config/:type/folder', (req, res) => {
99
+ router.post('/config/:type/folder', catchRoute((req, res) => {
139
100
  const type = parseType(req.params.type, res);
140
101
  if (!type)
141
102
  return;
@@ -144,20 +105,10 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
144
105
  res.status(400).json({ success: false, error: 'path is required' });
145
106
  return;
146
107
  }
147
- try {
148
- const basePath = configService.getBasePath(type);
149
- configService.createFolder(basePath, folderPath);
150
- io.emit(`config:${type}:changed`, { action: 'folder-created', path: folderPath });
151
- res.status(201).json({ success: true });
152
- }
153
- catch (err) {
154
- const msg = errMsg(err);
155
- const status = msg.includes('traversal') ? 403 : msg.includes('already exists') ? 409 : 500;
156
- res.status(status).json({ success: false, error: msg });
157
- }
158
- });
108
+ const basePath = configService.getBasePath(type);
109
+ configService.createFolder(basePath, folderPath);
110
+ io.emit(`config:${type}:changed`, { action: 'folder-created', path: folderPath });
111
+ res.status(201).json({ success: true });
112
+ }, inferErrorStatus));
159
113
  return router;
160
114
  }
161
- function errMsg(err) {
162
- return err instanceof Error ? err.message : String(err);
163
- }
@@ -5,91 +5,19 @@ import { promisify } from 'util';
5
5
  import { getHookEnforcement } from '../../shared/workflow-config.js';
6
6
  import { getClaudeSessions, getSessionStats } from '../services/claude-session-service.js';
7
7
  import { launchInTerminal } from '../utils/terminal-launcher.js';
8
+ import { buildClaudeFlags } from '../utils/flag-builder.js';
9
+ import { createLogger } from '../utils/logger.js';
10
+ import { parseJsonFields } from '../utils/json-fields.js';
11
+ const log = createLogger('server');
8
12
  const execFileAsync = promisify(execFile);
9
- const JSON_FIELDS = ['tags', 'blocked_by', 'blocks', 'data', 'discoveries', 'next_steps', 'details'];
10
- function parseJsonFields(row) {
11
- const parsed = { ...row };
12
- for (const field of JSON_FIELDS) {
13
- if (typeof parsed[field] === 'string') {
14
- try {
15
- parsed[field] = JSON.parse(parsed[field]);
16
- }
17
- catch { /* keep string */ }
18
- }
19
- }
20
- return parsed;
21
- }
22
- function parseDriftCommits(raw) {
23
- if (!raw)
24
- return [];
25
- return raw.split('\n').map((line) => {
26
- const [sha, date, message, author] = line.split('|');
27
- return { sha, date, message: message ?? '', author: author ?? '' };
28
- });
29
- }
30
- function parseHead(raw) {
31
- const [sha, date, message] = raw.split('|');
32
- return { sha: sha ?? '', date: date ?? '', message: message ?? '' };
33
- }
34
- export function createDataRoutes({ db, io, gateService, deployService, engine, projectRoot, inferScopeStatus, }) {
13
+ export function createDataRoutes({ db, io, eventService, gateService, deployService, gitService, engine, projectRoot, inferScopeStatus, config, }) {
35
14
  const router = Router();
36
- // ─── Pipeline Drift (cached) ─────────────────────────────
37
- let driftCache = null;
38
- const DRIFT_CACHE_MS = 60_000;
39
- async function gitLog(args) {
40
- const { stdout } = await execFileAsync('git', args, { cwd: projectRoot });
41
- return stdout.trim();
42
- }
43
- async function computeDrift() {
44
- if (driftCache && Date.now() - driftCache.ts < DRIFT_CACHE_MS)
45
- return driftCache.data;
46
- const [devToStagingRaw, stagingToMainRaw, devHead, stagingHead, mainHead] = await Promise.all([
47
- gitLog(['log', 'origin/dev', '--not', 'origin/staging', '--reverse', '--format=%H|%aI|%s|%an']),
48
- gitLog(['log', 'origin/staging', '--not', 'origin/main', '--reverse', '--format=%H|%aI|%s|%an']),
49
- gitLog(['log', 'origin/dev', '-1', '--format=%H|%aI|%s']),
50
- gitLog(['log', 'origin/staging', '-1', '--format=%H|%aI|%s']),
51
- gitLog(['log', 'origin/main', '-1', '--format=%H|%aI|%s']),
52
- ]);
53
- const devToStaging = parseDriftCommits(devToStagingRaw);
54
- const stagingToMain = parseDriftCommits(stagingToMainRaw);
55
- const data = {
56
- devToStaging: {
57
- count: devToStaging.length,
58
- commits: devToStaging,
59
- oldestDate: devToStaging[0]?.date ?? null,
60
- },
61
- stagingToMain: {
62
- count: stagingToMain.length,
63
- commits: stagingToMain,
64
- oldestDate: stagingToMain[0]?.date ?? null,
65
- },
66
- heads: {
67
- dev: parseHead(devHead),
68
- staging: parseHead(stagingHead),
69
- main: parseHead(mainHead),
70
- },
71
- };
72
- driftCache = { data, ts: Date.now() };
73
- return data;
74
- }
75
15
  // ─── Event Routes ──────────────────────────────────────────
76
16
  router.get('/events', (req, res) => {
77
17
  const limit = Number(req.query.limit) || 50;
78
18
  const type = req.query.type;
79
- const scopeId = req.query.scope_id;
80
- let query = 'SELECT * FROM events WHERE 1=1';
81
- const params = [];
82
- if (type) {
83
- query += ' AND type = ?';
84
- params.push(type);
85
- }
86
- if (scopeId) {
87
- query += ' AND scope_id = ?';
88
- params.push(Number(scopeId));
89
- }
90
- query += ' ORDER BY timestamp DESC LIMIT ?';
91
- params.push(limit);
92
- const events = db.prepare(query).all(...params);
19
+ const scopeId = req.query.scope_id ? Number(req.query.scope_id) : undefined;
20
+ const events = eventService.getFiltered({ limit, type, scopeId });
93
21
  res.json(events.map(parseJsonFields));
94
22
  });
95
23
  router.post('/events', (req, res) => {
@@ -115,18 +43,10 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
115
43
  // ─── Violations Summary ──────────────────────────────────
116
44
  router.get('/events/violations/summary', (_req, res) => {
117
45
  try {
118
- const byRule = db.prepare(`SELECT JSON_EXTRACT(data, '$.rule') as rule, COUNT(*) as count, MAX(timestamp) as last_seen
119
- FROM events WHERE type = 'VIOLATION' GROUP BY rule ORDER BY count DESC`).all();
120
- const byFile = db.prepare(`SELECT JSON_EXTRACT(data, '$.file') as file, COUNT(*) as count FROM events
121
- WHERE type = 'VIOLATION' AND JSON_EXTRACT(data, '$.file') IS NOT NULL AND JSON_EXTRACT(data, '$.file') != ''
122
- GROUP BY file ORDER BY count DESC LIMIT 20`).all();
123
- const overrides = db.prepare(`SELECT JSON_EXTRACT(data, '$.rule') as rule, JSON_EXTRACT(data, '$.reason') as reason, timestamp as date
124
- FROM events WHERE type = 'OVERRIDE' ORDER BY timestamp DESC LIMIT 50`).all();
125
- const totalViolations = db.prepare(`SELECT COUNT(*) as count FROM events WHERE type = 'VIOLATION'`).get();
126
- const totalOverrides = db.prepare(`SELECT COUNT(*) as count FROM events WHERE type = 'OVERRIDE'`).get();
127
- res.json({ byRule, byFile, overrides, totalViolations: totalViolations.count, totalOverrides: totalOverrides.count });
46
+ res.json(eventService.getViolationSummary());
128
47
  }
129
- catch {
48
+ catch (err) {
49
+ log.error('Violations summary failed', { error: String(err) });
130
50
  res.status(500).json({ error: 'Failed to query violations summary' });
131
51
  }
132
52
  });
@@ -144,13 +64,7 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
144
64
  hookEdgeMap.get(hookId).push({ from: edge.from, to: edge.to, label: edge.label });
145
65
  }
146
66
  }
147
- // Query violation and override stats per rule
148
- const violationStats = db.prepare(`SELECT JSON_EXTRACT(data, '$.rule') as rule, COUNT(*) as count, MAX(timestamp) as last_seen
149
- FROM events WHERE type = 'VIOLATION' GROUP BY rule`).all();
150
- const overrideStats = db.prepare(`SELECT JSON_EXTRACT(data, '$.rule') as rule, COUNT(*) as count
151
- FROM events WHERE type = 'OVERRIDE' GROUP BY rule`).all();
152
- const violationMap = new Map(violationStats.map((v) => [v.rule, v]));
153
- const overrideMap = new Map(overrideStats.map((o) => [o.rule, o]));
67
+ const { violations: violationMap, overrides: overrideMap } = eventService.getViolationStatsByRule();
154
68
  // Build summary counts
155
69
  const summary = { guards: 0, gates: 0, lifecycle: 0, observers: 0 };
156
70
  for (const hook of allHooks) {
@@ -175,20 +89,19 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
175
89
  }));
176
90
  res.json({ summary, rules, totalEdges: allEdges.length });
177
91
  }
178
- catch {
92
+ catch (err) {
93
+ log.error('Enforcement rules failed', { error: String(err) });
179
94
  res.status(500).json({ error: 'Failed to query enforcement rules' });
180
95
  }
181
96
  });
182
97
  // ─── Violation Trends ──────────────────────────────────────
183
98
  router.get('/events/violations/trend', (req, res) => {
184
99
  try {
185
- const days = Number(req.query.days) || 30;
186
- const trend = db.prepare(`SELECT date(timestamp) as day, JSON_EXTRACT(data, '$.rule') as rule, COUNT(*) as count
187
- FROM events WHERE type = 'VIOLATION' AND timestamp >= datetime('now', ? || ' days')
188
- GROUP BY day, rule ORDER BY day ASC`).all(`-${days}`);
189
- res.json(trend);
100
+ const days = Math.max(1, Math.min(365, Number(req.query.days) || 30));
101
+ res.json(eventService.getViolationTrend(days));
190
102
  }
191
- catch {
103
+ catch (err) {
104
+ log.error('Violation trends failed', { error: String(err) });
192
105
  res.status(500).json({ error: 'Failed to query violation trends' });
193
106
  }
194
107
  });
@@ -251,8 +164,7 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
251
164
  });
252
165
  router.get('/pipeline/drift', async (_req, res) => {
253
166
  try {
254
- const drift = await computeDrift();
255
- res.json(drift);
167
+ res.json(await gitService.getPipelineDrift());
256
168
  }
257
169
  catch (err) {
258
170
  res.status(500).json({ error: 'Failed to compute drift', details: String(err) });
@@ -260,21 +172,10 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
260
172
  });
261
173
  router.get('/deployments/frequency', (_req, res) => {
262
174
  try {
263
- const rows = db.prepare(`SELECT environment, strftime('%Y-W%W', started_at) as week, COUNT(*) as count
264
- FROM deployments WHERE started_at > datetime('now', '-56 days') GROUP BY environment, week ORDER BY week ASC`).all();
265
- const weekMap = new Map();
266
- for (const row of rows) {
267
- if (!weekMap.has(row.week))
268
- weekMap.set(row.week, { week: row.week, staging: 0, production: 0 });
269
- const entry = weekMap.get(row.week);
270
- if (row.environment === 'staging')
271
- entry.staging = row.count;
272
- if (row.environment === 'production')
273
- entry.production = row.count;
274
- }
275
- res.json([...weekMap.values()]);
175
+ res.json(eventService.getDeployFrequency());
276
176
  }
277
- catch {
177
+ catch (err) {
178
+ log.error('Deploy frequency query failed', { error: String(err) });
278
179
  res.status(500).json({ error: 'Failed to query deployment frequency' });
279
180
  }
280
181
  });
@@ -330,7 +231,7 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
330
231
  let meta = null;
331
232
  let stats = null;
332
233
  if (parsed.claude_session_id && typeof parsed.claude_session_id === 'string') {
333
- const claudeSessions = await getClaudeSessions();
234
+ const claudeSessions = await getClaudeSessions(undefined, projectRoot);
334
235
  const match = claudeSessions.find(s => s.id === parsed.claude_session_id);
335
236
  if (match) {
336
237
  meta = {
@@ -342,7 +243,7 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
342
243
  lastActiveAt: match.lastActiveAt,
343
244
  };
344
245
  }
345
- stats = getSessionStats(parsed.claude_session_id);
246
+ stats = getSessionStats(parsed.claude_session_id, projectRoot);
346
247
  }
347
248
  if (!content) {
348
249
  const parts = [];
@@ -377,7 +278,8 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
377
278
  res.status(400).json({ error: 'Valid claude_session_id (UUID) required' });
378
279
  return;
379
280
  }
380
- const resumeCmd = `cd '${projectRoot}' && claude --dangerously-skip-permissions --resume '${claude_session_id}'`;
281
+ const flagsStr = buildClaudeFlags(config.claude.dispatchFlags);
282
+ const resumeCmd = `cd '${projectRoot}' && claude ${flagsStr} --resume '${claude_session_id}'`;
381
283
  try {
382
284
  await launchInTerminal(resumeCmd);
383
285
  res.json({ ok: true, session_id: claude_session_id });
@@ -1,10 +1,11 @@
1
1
  import { Router } from 'express';
2
- import { launchInCategorizedTerminal, escapeForAnsiC, buildSessionName, snapshotSessionPids, discoverNewSession, renameSession } from '../utils/terminal-launcher.js';
2
+ import { launchInCategorizedTerminal, escapeForAnsiC, shellQuote, buildSessionName, snapshotSessionPids, discoverNewSession, renameSession } from '../utils/terminal-launcher.js';
3
3
  import { resolveDispatchEvent, resolveAbandonedDispatchesForScope, getActiveScopeIds, getAbandonedScopeIds, linkPidToDispatch } from '../utils/dispatch-utils.js';
4
+ import { buildClaudeFlags, buildEnvVarPrefix } from '../utils/flag-builder.js';
4
5
  import { createLogger } from '../utils/logger.js';
5
6
  const log = createLogger('dispatch');
6
- const MAX_BATCH_SIZE = 20;
7
- export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine }) {
7
+ const DEFAULT_MAX_BATCH_SIZE = 20;
8
+ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine, config }) {
8
9
  const router = Router();
9
10
  router.get('/dispatch/active-scopes', (_req, res) => {
10
11
  const scope_ids = getActiveScopeIds(db, scopeService, engine);
@@ -44,6 +45,16 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
44
45
  return;
45
46
  }
46
47
  }
48
+ // Max concurrent dispatches guard
49
+ const maxConcurrent = config.dispatch.maxConcurrent;
50
+ if (maxConcurrent > 0) {
51
+ const activeCount = db.prepare(`SELECT COUNT(*) as count FROM events
52
+ WHERE type = 'DISPATCH' AND JSON_EXTRACT(data, '$.resolved') IS NULL`).get().count;
53
+ if (activeCount >= maxConcurrent) {
54
+ res.status(429).json({ error: `Max concurrent dispatches reached (${maxConcurrent})` });
55
+ return;
56
+ }
57
+ }
47
58
  // Update scope status if transition provided
48
59
  if (scope_id != null && transition?.to) {
49
60
  const result = scopeService.updateStatus(scope_id, transition.to, 'dispatch');
@@ -66,23 +77,28 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
66
77
  const scope = scope_id != null ? scopeService.getById(scope_id) : undefined;
67
78
  const sessionName = buildSessionName({ scopeId: scope_id ?? undefined, title: scope?.title, command });
68
79
  const beforePids = snapshotSessionPids(projectRoot);
69
- // Launch in iTerm — interactive TUI mode (no -p) for full visibility
80
+ // Launch in iTerm — interactive TUI mode (no -p unless printMode) for full visibility
70
81
  const promptText = prompt ?? command;
71
82
  const escaped = escapeForAnsiC(promptText);
72
- const fullCmd = `cd '${projectRoot}' && ORBITAL_DISPATCH_ID='${eventId}' claude --dangerously-skip-permissions $'${escaped}'`;
83
+ const flagsStr = buildClaudeFlags(config.claude.dispatchFlags);
84
+ const envPrefix = buildEnvVarPrefix(config.dispatch.envVars);
85
+ const fullCmd = `cd '${shellQuote(projectRoot)}' && ${envPrefix}ORBITAL_DISPATCH_ID='${shellQuote(eventId)}' claude ${flagsStr} $'${escaped}'`;
73
86
  try {
74
87
  await launchInCategorizedTerminal(command, fullCmd, sessionName);
75
88
  res.json({ ok: true, dispatch_id: eventId, scope_id: scope_id ?? null });
76
- // Fire-and-forget: discover session PID, link to dispatch, and rename
89
+ // Fire-and-forget: discover session PID, link to dispatch, and rename.
90
+ // If discovery fails, SESSION_START event handler will link via ORBITAL_DISPATCH_ID.
77
91
  discoverNewSession(projectRoot, beforePids)
78
92
  .then((session) => {
79
- if (!session)
93
+ if (!session) {
94
+ log.warn('PID discovery returned null — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId, scope_id });
80
95
  return;
96
+ }
81
97
  linkPidToDispatch(db, eventId, session.pid);
82
98
  if (sessionName)
83
99
  renameSession(projectRoot, session.sessionId, sessionName);
84
100
  })
85
- .catch(err => log.error('PID discovery failed', { error: err.message }));
101
+ .catch(err => log.warn('PID discovery failed — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId, error: err.message }));
86
102
  }
87
103
  catch (err) {
88
104
  if (scope_id != null && transition?.from) {
@@ -157,8 +173,9 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
157
173
  return;
158
174
  }
159
175
  // W-12: Validate batch size and scope ID types
160
- if (scope_ids.length > MAX_BATCH_SIZE) {
161
- res.status(400).json({ error: `Maximum batch size is ${MAX_BATCH_SIZE}` });
176
+ const maxBatch = config.dispatch.maxBatchSize || DEFAULT_MAX_BATCH_SIZE;
177
+ if (scope_ids.length > maxBatch) {
178
+ res.status(400).json({ error: `Maximum batch size is ${maxBatch}` });
162
179
  return;
163
180
  }
164
181
  if (!scope_ids.every(id => Number.isInteger(id) && id > 0)) {
@@ -185,21 +202,26 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
185
202
  session_id: null, agent: 'dashboard', data: eventData,
186
203
  timestamp: new Date().toISOString(),
187
204
  });
188
- // Launch single CLI session
205
+ // Launch single CLI session with batch env vars
189
206
  const batchEscaped = escapeForAnsiC(command);
190
207
  const beforePids = snapshotSessionPids(projectRoot);
191
- const fullCmd = `cd '${projectRoot}' && ORBITAL_DISPATCH_ID='${eventId}' claude --dangerously-skip-permissions -p $'${batchEscaped}'`;
208
+ const batchFlags = buildClaudeFlags(config.claude.dispatchFlags);
209
+ const envPrefix = buildEnvVarPrefix(config.dispatch.envVars);
210
+ const fullCmd = `cd '${shellQuote(projectRoot)}' && ${envPrefix}ORBITAL_DISPATCH_ID='${shellQuote(eventId)}' claude ${batchFlags} $'${batchEscaped}'`;
192
211
  try {
193
212
  await launchInCategorizedTerminal(command, fullCmd);
194
213
  res.json({ ok: true, dispatch_id: eventId, scope_ids });
195
- // Fire-and-forget: discover session PID and link to dispatch
214
+ // Fire-and-forget: discover session PID and link to dispatch.
215
+ // If discovery fails, SESSION_START event handler will link via ORBITAL_DISPATCH_ID.
196
216
  discoverNewSession(projectRoot, beforePids)
197
217
  .then((session) => {
198
- if (!session)
218
+ if (!session) {
219
+ log.warn('Batch PID discovery returned null — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId });
199
220
  return;
221
+ }
200
222
  linkPidToDispatch(db, eventId, session.pid);
201
223
  })
202
- .catch(err => log.error('Batch PID discovery failed', { error: err.message }));
224
+ .catch(err => log.warn('Batch PID discovery failed — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId, error: err.message }));
203
225
  }
204
226
  catch (err) {
205
227
  if (transition?.from) {
@@ -88,5 +88,79 @@ export function createGitRoutes({ gitService, githubService, engine }) {
88
88
  res.status(500).json({ error: 'Failed to get PRs', details: String(err) });
89
89
  }
90
90
  });
91
+ // ─── GitHub Auth Flow ──────────────────────────────────────
92
+ router.post('/github/connect', async (req, res) => {
93
+ try {
94
+ const { method, token } = req.body;
95
+ if (method === 'pat' && token) {
96
+ const result = await githubService.connectWithToken(token);
97
+ res.json(result);
98
+ }
99
+ else {
100
+ const result = await githubService.connectOAuth();
101
+ res.json(result);
102
+ }
103
+ }
104
+ catch (err) {
105
+ res.status(500).json({ error: 'Failed to connect', details: String(err) });
106
+ }
107
+ });
108
+ router.get('/github/auth-status', async (_req, res) => {
109
+ try {
110
+ const status = await githubService.getAuthStatus();
111
+ res.json(status);
112
+ }
113
+ catch (err) {
114
+ res.status(500).json({ error: 'Failed to check auth', details: String(err) });
115
+ }
116
+ });
117
+ router.post('/github/disconnect', async (_req, res) => {
118
+ try {
119
+ const result = await githubService.disconnect();
120
+ res.json(result);
121
+ }
122
+ catch (err) {
123
+ res.status(500).json({ error: 'Failed to disconnect', details: String(err) });
124
+ }
125
+ });
126
+ // ─── GitHub CI Checks ──────────────────────────────────────
127
+ router.get('/github/checks/:ref', async (req, res) => {
128
+ try {
129
+ const checks = await githubService.getCheckRuns(req.params.ref);
130
+ res.json(checks);
131
+ }
132
+ catch (err) {
133
+ res.status(500).json({ error: 'Failed to get checks', details: String(err) });
134
+ }
135
+ });
136
+ // ─── Git Health Metrics ────────────────────────────────────
137
+ router.get('/git/health', async (_req, res) => {
138
+ try {
139
+ // Get PR ages for health calculation
140
+ let prAges = [];
141
+ try {
142
+ const prs = await githubService.getOpenPRs();
143
+ const now = Date.now();
144
+ prAges = prs.map(pr => Math.round((now - new Date(pr.createdAt).getTime()) / (1000 * 60 * 60 * 24)));
145
+ }
146
+ catch { /* ok — GitHub may not be connected */ }
147
+ const health = await gitService.getHealthMetrics(prAges);
148
+ res.json(health);
149
+ }
150
+ catch (err) {
151
+ res.status(500).json({ error: 'Failed to get health metrics', details: String(err) });
152
+ }
153
+ });
154
+ // ─── Git Activity Series ───────────────────────────────────
155
+ router.get('/git/activity', async (req, res) => {
156
+ try {
157
+ const days = Number(req.query.days) || 30;
158
+ const activity = await gitService.getActivitySeries(days);
159
+ res.json(activity);
160
+ }
161
+ catch (err) {
162
+ res.status(500).json({ error: 'Failed to get activity', details: String(err) });
163
+ }
164
+ });
91
165
  return router;
92
166
  }