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,13 +1,14 @@
1
1
  import { Router } from 'express';
2
- import type { Server } from 'socket.io';
2
+ import type { Emitter } from '../project-emitter.js';
3
3
  import { ConfigService, isValidPrimitiveType } from '../services/config-service.js';
4
4
  import type { ConfigPrimitiveType } from '../services/config-service.js';
5
5
  import type { WorkflowService } from '../services/workflow-service.js';
6
+ import { catchRoute, inferErrorStatus } from '../utils/route-helpers.js';
6
7
 
7
8
  interface ConfigRouteDeps {
8
9
  projectRoot: string;
9
10
  workflowService: WorkflowService;
10
- io: Server;
11
+ io: Emitter;
11
12
  }
12
13
 
13
14
  export function createConfigRoutes({ projectRoot, workflowService: _workflowService, io }: ConfigRouteDeps): Router {
@@ -24,21 +25,17 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
24
25
  }
25
26
 
26
27
  // GET /config/:type/tree — directory tree with frontmatter
27
- router.get('/config/:type/tree', (req, res) => {
28
+ router.get('/config/:type/tree', catchRoute((req, res) => {
28
29
  const type = parseType(req.params.type, res);
29
30
  if (!type) return;
30
31
 
31
- try {
32
- const basePath = configService.getBasePath(type);
33
- const tree = configService.scanDirectory(basePath);
34
- res.json({ success: true, data: tree });
35
- } catch (err) {
36
- res.status(500).json({ success: false, error: errMsg(err) });
37
- }
38
- });
32
+ const basePath = configService.getBasePath(type);
33
+ const tree = configService.scanDirectory(basePath);
34
+ res.json({ success: true, data: tree });
35
+ }));
39
36
 
40
37
  // GET /config/:type/file?path=<relative> — file content
41
- router.get('/config/:type/file', (req, res) => {
38
+ router.get('/config/:type/file', catchRoute((req, res) => {
42
39
  const type = parseType(req.params.type, res);
43
40
  if (!type) return;
44
41
 
@@ -48,19 +45,13 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
48
45
  return;
49
46
  }
50
47
 
51
- try {
52
- const basePath = configService.getBasePath(type);
53
- const content = configService.readFile(basePath, filePath);
54
- res.json({ success: true, data: { path: filePath, content } });
55
- } catch (err) {
56
- const msg = errMsg(err);
57
- const status = msg.includes('traversal') ? 403 : msg.includes('ENOENT') || msg.includes('not found') ? 404 : 500;
58
- res.status(status).json({ success: false, error: msg });
59
- }
60
- });
48
+ const basePath = configService.getBasePath(type);
49
+ const content = configService.readFile(basePath, filePath);
50
+ res.json({ success: true, data: { path: filePath, content } });
51
+ }, inferErrorStatus));
61
52
 
62
53
  // PUT /config/:type/file — save file { path, content }
63
- router.put('/config/:type/file', (req, res) => {
54
+ router.put('/config/:type/file', catchRoute((req, res) => {
64
55
  const type = parseType(req.params.type, res);
65
56
  if (!type) return;
66
57
 
@@ -70,20 +61,14 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
70
61
  return;
71
62
  }
72
63
 
73
- try {
74
- const basePath = configService.getBasePath(type);
75
- configService.writeFile(basePath, filePath, content);
76
- io.emit(`config:${type}:changed`, { action: 'updated', path: filePath });
77
- res.json({ success: true });
78
- } catch (err) {
79
- const msg = errMsg(err);
80
- const status = msg.includes('traversal') ? 403 : msg.includes('not found') ? 404 : 500;
81
- res.status(status).json({ success: false, error: msg });
82
- }
83
- });
64
+ const basePath = configService.getBasePath(type);
65
+ configService.writeFile(basePath, filePath, content);
66
+ io.emit(`config:${type}:changed`, { action: 'updated', path: filePath });
67
+ res.json({ success: true });
68
+ }, inferErrorStatus));
84
69
 
85
70
  // POST /config/:type/file — create file { path, content }
86
- router.post('/config/:type/file', (req, res) => {
71
+ router.post('/config/:type/file', catchRoute((req, res) => {
87
72
  const type = parseType(req.params.type, res);
88
73
  if (!type) return;
89
74
 
@@ -93,20 +78,14 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
93
78
  return;
94
79
  }
95
80
 
96
- try {
97
- const basePath = configService.getBasePath(type);
98
- configService.createFile(basePath, filePath, content);
99
- io.emit(`config:${type}:changed`, { action: 'created', path: filePath });
100
- res.status(201).json({ success: true });
101
- } catch (err) {
102
- const msg = errMsg(err);
103
- const status = msg.includes('traversal') ? 403 : msg.includes('already exists') ? 409 : 500;
104
- res.status(status).json({ success: false, error: msg });
105
- }
106
- });
81
+ const basePath = configService.getBasePath(type);
82
+ configService.createFile(basePath, filePath, content);
83
+ io.emit(`config:${type}:changed`, { action: 'created', path: filePath });
84
+ res.status(201).json({ success: true });
85
+ }, inferErrorStatus));
107
86
 
108
87
  // DELETE /config/:type/file?path=<relative> — delete file
109
- router.delete('/config/:type/file', (req, res) => {
88
+ router.delete('/config/:type/file', catchRoute((req, res) => {
110
89
  const type = parseType(req.params.type, res);
111
90
  if (!type) return;
112
91
 
@@ -116,20 +95,14 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
116
95
  return;
117
96
  }
118
97
 
119
- try {
120
- const basePath = configService.getBasePath(type);
121
- configService.deleteFile(basePath, filePath);
122
- io.emit(`config:${type}:changed`, { action: 'deleted', path: filePath });
123
- res.json({ success: true });
124
- } catch (err) {
125
- const msg = errMsg(err);
126
- const status = msg.includes('traversal') ? 403 : msg.includes('not found') ? 404 : msg.includes('directory') ? 400 : 500;
127
- res.status(status).json({ success: false, error: msg });
128
- }
129
- });
98
+ const basePath = configService.getBasePath(type);
99
+ configService.deleteFile(basePath, filePath);
100
+ io.emit(`config:${type}:changed`, { action: 'deleted', path: filePath });
101
+ res.json({ success: true });
102
+ }, inferErrorStatus));
130
103
 
131
104
  // POST /config/:type/rename — rename { oldPath, newPath }
132
- router.post('/config/:type/rename', (req, res) => {
105
+ router.post('/config/:type/rename', catchRoute((req, res) => {
133
106
  const type = parseType(req.params.type, res);
134
107
  if (!type) return;
135
108
 
@@ -139,20 +112,14 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
139
112
  return;
140
113
  }
141
114
 
142
- try {
143
- const basePath = configService.getBasePath(type);
144
- configService.renameFile(basePath, oldPath, newPath);
145
- io.emit(`config:${type}:changed`, { action: 'renamed', oldPath, newPath });
146
- res.json({ success: true });
147
- } catch (err) {
148
- const msg = errMsg(err);
149
- const status = msg.includes('traversal') ? 403 : msg.includes('not found') ? 404 : msg.includes('already exists') ? 409 : 500;
150
- res.status(status).json({ success: false, error: msg });
151
- }
152
- });
115
+ const basePath = configService.getBasePath(type);
116
+ configService.renameFile(basePath, oldPath, newPath);
117
+ io.emit(`config:${type}:changed`, { action: 'renamed', oldPath, newPath });
118
+ res.json({ success: true });
119
+ }, inferErrorStatus));
153
120
 
154
121
  // POST /config/:type/folder — create folder { path }
155
- router.post('/config/:type/folder', (req, res) => {
122
+ router.post('/config/:type/folder', catchRoute((req, res) => {
156
123
  const type = parseType(req.params.type, res);
157
124
  if (!type) return;
158
125
 
@@ -162,21 +129,12 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
162
129
  return;
163
130
  }
164
131
 
165
- try {
166
- const basePath = configService.getBasePath(type);
167
- configService.createFolder(basePath, folderPath);
168
- io.emit(`config:${type}:changed`, { action: 'folder-created', path: folderPath });
169
- res.status(201).json({ success: true });
170
- } catch (err) {
171
- const msg = errMsg(err);
172
- const status = msg.includes('traversal') ? 403 : msg.includes('already exists') ? 409 : 500;
173
- res.status(status).json({ success: false, error: msg });
174
- }
175
- });
132
+ const basePath = configService.getBasePath(type);
133
+ configService.createFolder(basePath, folderPath);
134
+ io.emit(`config:${type}:changed`, { action: 'folder-created', path: folderPath });
135
+ res.status(201).json({ success: true });
136
+ }, inferErrorStatus));
176
137
 
177
138
  return router;
178
139
  }
179
140
 
180
- function errMsg(err: unknown): string {
181
- return err instanceof Error ? err.message : String(err);
182
- }
@@ -3,134 +3,51 @@ import { execFile } from 'child_process';
3
3
  import path from 'path';
4
4
  import { promisify } from 'util';
5
5
  import type Database from 'better-sqlite3';
6
- import type { Server } from 'socket.io';
6
+ import type { Emitter } from '../project-emitter.js';
7
+ import type { EventService } from '../services/event-service.js';
7
8
  import type { GateService } from '../services/gate-service.js';
8
9
  import type { DeployService } from '../services/deploy-service.js';
10
+ import type { GitService } from '../services/git-service.js';
9
11
  import type { WorkflowEngine } from '../../shared/workflow-engine.js';
10
12
  import { getHookEnforcement } from '../../shared/workflow-config.js';
11
13
  import { getClaudeSessions, getSessionStats, type SessionStats } from '../services/claude-session-service.js';
12
14
  import { launchInTerminal } from '../utils/terminal-launcher.js';
15
+ import type { OrbitalConfig } from '../config.js';
16
+ import { buildClaudeFlags } from '../utils/flag-builder.js';
17
+ import { createLogger } from '../utils/logger.js';
18
+ import { parseJsonFields, type Row } from '../utils/json-fields.js';
13
19
 
14
- const execFileAsync = promisify(execFile);
15
-
16
- // ─── Types & Helpers ────────────────────────────────────────
17
-
18
- interface DriftCommit { sha: string; message: string; author: string; date: string }
19
- interface BranchHead { sha: string; date: string; message: string }
20
- interface PipelineDriftData {
21
- devToStaging: { count: number; commits: DriftCommit[]; oldestDate: string | null };
22
- stagingToMain: { count: number; commits: DriftCommit[]; oldestDate: string | null };
23
- heads: { dev: BranchHead; staging: BranchHead; main: BranchHead };
24
- }
25
-
26
- const JSON_FIELDS = ['tags', 'blocked_by', 'blocks', 'data', 'discoveries', 'next_steps', 'details'];
27
-
28
- type Row = Record<string, unknown>;
29
-
30
- function parseJsonFields(row: Row): Row {
31
- const parsed = { ...row };
32
- for (const field of JSON_FIELDS) {
33
- if (typeof parsed[field] === 'string') {
34
- try { parsed[field] = JSON.parse(parsed[field] as string); } catch { /* keep string */ }
35
- }
36
- }
37
- return parsed;
38
- }
39
-
40
- function parseDriftCommits(raw: string): DriftCommit[] {
41
- if (!raw) return [];
42
- return raw.split('\n').map((line) => {
43
- const [sha, date, message, author] = line.split('|');
44
- return { sha, date, message: message ?? '', author: author ?? '' };
45
- });
46
- }
20
+ const log = createLogger('server');
47
21
 
48
- function parseHead(raw: string): BranchHead {
49
- const [sha, date, message] = raw.split('|');
50
- return { sha: sha ?? '', date: date ?? '', message: message ?? '' };
51
- }
22
+ const execFileAsync = promisify(execFile);
52
23
 
53
24
  // ─── Route Factory ──────────────────────────────────────────
54
25
 
55
26
  interface DataRouteDeps {
56
27
  db: Database.Database;
57
- io: Server;
28
+ io: Emitter;
29
+ eventService: EventService;
58
30
  gateService: GateService;
59
31
  deployService: DeployService;
32
+ gitService: GitService;
60
33
  engine: WorkflowEngine;
61
34
  projectRoot: string;
62
35
  inferScopeStatus: (type: string, scopeId: unknown, data: Record<string, unknown>) => void;
36
+ config: OrbitalConfig;
63
37
  }
64
38
 
65
39
  export function createDataRoutes({
66
- db, io, gateService, deployService, engine, projectRoot, inferScopeStatus,
40
+ db, io, eventService, gateService, deployService, gitService, engine, projectRoot, inferScopeStatus, config,
67
41
  }: DataRouteDeps): Router {
68
42
  const router = Router();
69
43
 
70
- // ─── Pipeline Drift (cached) ─────────────────────────────
71
-
72
- let driftCache: { data: PipelineDriftData; ts: number } | null = null;
73
- const DRIFT_CACHE_MS = 60_000;
74
-
75
- async function gitLog(args: string[]): Promise<string> {
76
- const { stdout } = await execFileAsync('git', args, { cwd: projectRoot });
77
- return stdout.trim();
78
- }
79
-
80
- async function computeDrift(): Promise<PipelineDriftData> {
81
- if (driftCache && Date.now() - driftCache.ts < DRIFT_CACHE_MS) return driftCache.data;
82
-
83
- const [devToStagingRaw, stagingToMainRaw, devHead, stagingHead, mainHead] =
84
- await Promise.all([
85
- gitLog(['log', 'origin/dev', '--not', 'origin/staging', '--reverse', '--format=%H|%aI|%s|%an']),
86
- gitLog(['log', 'origin/staging', '--not', 'origin/main', '--reverse', '--format=%H|%aI|%s|%an']),
87
- gitLog(['log', 'origin/dev', '-1', '--format=%H|%aI|%s']),
88
- gitLog(['log', 'origin/staging', '-1', '--format=%H|%aI|%s']),
89
- gitLog(['log', 'origin/main', '-1', '--format=%H|%aI|%s']),
90
- ]);
91
-
92
- const devToStaging = parseDriftCommits(devToStagingRaw);
93
- const stagingToMain = parseDriftCommits(stagingToMainRaw);
94
-
95
- const data: PipelineDriftData = {
96
- devToStaging: {
97
- count: devToStaging.length,
98
- commits: devToStaging,
99
- oldestDate: devToStaging[0]?.date ?? null,
100
- },
101
- stagingToMain: {
102
- count: stagingToMain.length,
103
- commits: stagingToMain,
104
- oldestDate: stagingToMain[0]?.date ?? null,
105
- },
106
- heads: {
107
- dev: parseHead(devHead),
108
- staging: parseHead(stagingHead),
109
- main: parseHead(mainHead),
110
- },
111
- };
112
-
113
- driftCache = { data, ts: Date.now() };
114
- return data;
115
- }
116
-
117
44
  // ─── Event Routes ──────────────────────────────────────────
118
45
 
119
46
  router.get('/events', (req, res) => {
120
47
  const limit = Number(req.query.limit) || 50;
121
48
  const type = req.query.type as string | undefined;
122
- const scopeId = req.query.scope_id as string | undefined;
123
-
124
- let query = 'SELECT * FROM events WHERE 1=1';
125
- const params: unknown[] = [];
126
-
127
- if (type) { query += ' AND type = ?'; params.push(type); }
128
- if (scopeId) { query += ' AND scope_id = ?'; params.push(Number(scopeId)); }
129
-
130
- query += ' ORDER BY timestamp DESC LIMIT ?';
131
- params.push(limit);
132
-
133
- const events = db.prepare(query).all(...params) as Row[];
49
+ const scopeId = req.query.scope_id ? Number(req.query.scope_id) : undefined;
50
+ const events = eventService.getFiltered({ limit, type, scopeId }) as unknown as Row[];
134
51
  res.json(events.map(parseJsonFields));
135
52
  });
136
53
 
@@ -167,23 +84,9 @@ export function createDataRoutes({
167
84
 
168
85
  router.get('/events/violations/summary', (_req, res) => {
169
86
  try {
170
- const byRule = db.prepare(
171
- `SELECT JSON_EXTRACT(data, '$.rule') as rule, COUNT(*) as count, MAX(timestamp) as last_seen
172
- FROM events WHERE type = 'VIOLATION' GROUP BY rule ORDER BY count DESC`
173
- ).all();
174
- const byFile = db.prepare(
175
- `SELECT JSON_EXTRACT(data, '$.file') as file, COUNT(*) as count FROM events
176
- WHERE type = 'VIOLATION' AND JSON_EXTRACT(data, '$.file') IS NOT NULL AND JSON_EXTRACT(data, '$.file') != ''
177
- GROUP BY file ORDER BY count DESC LIMIT 20`
178
- ).all();
179
- const overrides = db.prepare(
180
- `SELECT JSON_EXTRACT(data, '$.rule') as rule, JSON_EXTRACT(data, '$.reason') as reason, timestamp as date
181
- FROM events WHERE type = 'OVERRIDE' ORDER BY timestamp DESC LIMIT 50`
182
- ).all();
183
- const totalViolations = db.prepare(`SELECT COUNT(*) as count FROM events WHERE type = 'VIOLATION'`).get() as { count: number };
184
- const totalOverrides = db.prepare(`SELECT COUNT(*) as count FROM events WHERE type = 'OVERRIDE'`).get() as { count: number };
185
- res.json({ byRule, byFile, overrides, totalViolations: totalViolations.count, totalOverrides: totalOverrides.count });
186
- } catch {
87
+ res.json(eventService.getViolationSummary());
88
+ } catch (err) {
89
+ log.error('Violations summary failed', { error: String(err) });
187
90
  res.status(500).json({ error: 'Failed to query violations summary' });
188
91
  }
189
92
  });
@@ -204,19 +107,7 @@ export function createDataRoutes({
204
107
  }
205
108
  }
206
109
 
207
- // Query violation and override stats per rule
208
- const violationStats = db.prepare(
209
- `SELECT JSON_EXTRACT(data, '$.rule') as rule, COUNT(*) as count, MAX(timestamp) as last_seen
210
- FROM events WHERE type = 'VIOLATION' GROUP BY rule`
211
- ).all() as Array<{ rule: string; count: number; last_seen: string }>;
212
-
213
- const overrideStats = db.prepare(
214
- `SELECT JSON_EXTRACT(data, '$.rule') as rule, COUNT(*) as count
215
- FROM events WHERE type = 'OVERRIDE' GROUP BY rule`
216
- ).all() as Array<{ rule: string; count: number }>;
217
-
218
- const violationMap = new Map(violationStats.map((v) => [v.rule, v]));
219
- const overrideMap = new Map(overrideStats.map((o) => [o.rule, o]));
110
+ const { violations: violationMap, overrides: overrideMap } = eventService.getViolationStatsByRule();
220
111
 
221
112
  // Build summary counts
222
113
  const summary = { guards: 0, gates: 0, lifecycle: 0, observers: 0 };
@@ -239,7 +130,8 @@ export function createDataRoutes({
239
130
  }));
240
131
 
241
132
  res.json({ summary, rules, totalEdges: allEdges.length });
242
- } catch {
133
+ } catch (err) {
134
+ log.error('Enforcement rules failed', { error: String(err) });
243
135
  res.status(500).json({ error: 'Failed to query enforcement rules' });
244
136
  }
245
137
  });
@@ -248,14 +140,10 @@ export function createDataRoutes({
248
140
 
249
141
  router.get('/events/violations/trend', (req, res) => {
250
142
  try {
251
- const days = Number(req.query.days) || 30;
252
- const trend = db.prepare(
253
- `SELECT date(timestamp) as day, JSON_EXTRACT(data, '$.rule') as rule, COUNT(*) as count
254
- FROM events WHERE type = 'VIOLATION' AND timestamp >= datetime('now', ? || ' days')
255
- GROUP BY day, rule ORDER BY day ASC`
256
- ).all(`-${days}`) as Array<{ day: string; rule: string; count: number }>;
257
- res.json(trend);
258
- } catch {
143
+ const days = Math.max(1, Math.min(365, Number(req.query.days) || 30));
144
+ res.json(eventService.getViolationTrend(days));
145
+ } catch (err) {
146
+ log.error('Violation trends failed', { error: String(err) });
259
147
  res.status(500).json({ error: 'Failed to query violation trends' });
260
148
  }
261
149
  });
@@ -330,8 +218,7 @@ export function createDataRoutes({
330
218
 
331
219
  router.get('/pipeline/drift', async (_req, res) => {
332
220
  try {
333
- const drift = await computeDrift();
334
- res.json(drift);
221
+ res.json(await gitService.getPipelineDrift());
335
222
  } catch (err) {
336
223
  res.status(500).json({ error: 'Failed to compute drift', details: String(err) });
337
224
  }
@@ -339,19 +226,9 @@ export function createDataRoutes({
339
226
 
340
227
  router.get('/deployments/frequency', (_req, res) => {
341
228
  try {
342
- const rows = db.prepare(
343
- `SELECT environment, strftime('%Y-W%W', started_at) as week, COUNT(*) as count
344
- FROM deployments WHERE started_at > datetime('now', '-56 days') GROUP BY environment, week ORDER BY week ASC`
345
- ).all() as Array<{ environment: string; week: string; count: number }>;
346
- const weekMap = new Map<string, { week: string; staging: number; production: number }>();
347
- for (const row of rows) {
348
- if (!weekMap.has(row.week)) weekMap.set(row.week, { week: row.week, staging: 0, production: 0 });
349
- const entry = weekMap.get(row.week)!;
350
- if (row.environment === 'staging') entry.staging = row.count;
351
- if (row.environment === 'production') entry.production = row.count;
352
- }
353
- res.json([...weekMap.values()]);
354
- } catch {
229
+ res.json(eventService.getDeployFrequency());
230
+ } catch (err) {
231
+ log.error('Deploy frequency query failed', { error: String(err) });
355
232
  res.status(500).json({ error: 'Failed to query deployment frequency' });
356
233
  }
357
234
  });
@@ -417,7 +294,7 @@ export function createDataRoutes({
417
294
  let stats: SessionStats | null = null;
418
295
 
419
296
  if (parsed.claude_session_id && typeof parsed.claude_session_id === 'string') {
420
- const claudeSessions = await getClaudeSessions();
297
+ const claudeSessions = await getClaudeSessions(undefined, projectRoot);
421
298
  const match = claudeSessions.find(s => s.id === parsed.claude_session_id);
422
299
  if (match) {
423
300
  meta = {
@@ -429,7 +306,7 @@ export function createDataRoutes({
429
306
  lastActiveAt: match.lastActiveAt,
430
307
  };
431
308
  }
432
- stats = getSessionStats(parsed.claude_session_id as string);
309
+ stats = getSessionStats(parsed.claude_session_id as string, projectRoot);
433
310
  }
434
311
 
435
312
  if (!content) {
@@ -466,7 +343,8 @@ export function createDataRoutes({
466
343
  return;
467
344
  }
468
345
 
469
- const resumeCmd = `cd '${projectRoot}' && claude --dangerously-skip-permissions --resume '${claude_session_id}'`;
346
+ const flagsStr = buildClaudeFlags(config.claude.dispatchFlags);
347
+ const resumeCmd = `cd '${projectRoot}' && claude ${flagsStr} --resume '${claude_session_id}'`;
470
348
 
471
349
  try {
472
350
  await launchInTerminal(resumeCmd);
@@ -1,15 +1,17 @@
1
1
  import { Router } from 'express';
2
2
  import type Database from 'better-sqlite3';
3
- import type { Server } from 'socket.io';
3
+ import type { Emitter } from '../project-emitter.js';
4
4
  import type { ScopeService } from '../services/scope-service.js';
5
- import { launchInCategorizedTerminal, escapeForAnsiC, buildSessionName, snapshotSessionPids, discoverNewSession, renameSession } from '../utils/terminal-launcher.js';
5
+ import { launchInCategorizedTerminal, escapeForAnsiC, shellQuote, buildSessionName, snapshotSessionPids, discoverNewSession, renameSession } from '../utils/terminal-launcher.js';
6
6
  import { resolveDispatchEvent, resolveAbandonedDispatchesForScope, getActiveScopeIds, getAbandonedScopeIds, linkPidToDispatch } from '../utils/dispatch-utils.js';
7
7
  import type { WorkflowEngine } from '../../shared/workflow-engine.js';
8
+ import type { OrbitalConfig } from '../config.js';
9
+ import { buildClaudeFlags, buildEnvVarPrefix } from '../utils/flag-builder.js';
8
10
  import { createLogger } from '../utils/logger.js';
9
11
 
10
12
  const log = createLogger('dispatch');
11
13
 
12
- const MAX_BATCH_SIZE = 20;
14
+ const DEFAULT_MAX_BATCH_SIZE = 20;
13
15
 
14
16
  interface DispatchBody {
15
17
  scope_id?: number;
@@ -20,13 +22,14 @@ interface DispatchBody {
20
22
 
21
23
  interface DispatchRouteDeps {
22
24
  db: Database.Database;
23
- io: Server;
25
+ io: Emitter;
24
26
  scopeService: ScopeService;
25
27
  projectRoot: string;
26
28
  engine: WorkflowEngine;
29
+ config: OrbitalConfig;
27
30
  }
28
31
 
29
- export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine }: DispatchRouteDeps): Router {
32
+ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine, config }: DispatchRouteDeps): Router {
30
33
  const router = Router();
31
34
 
32
35
  router.get('/dispatch/active-scopes', (_req, res) => {
@@ -79,6 +82,19 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
79
82
  }
80
83
  }
81
84
 
85
+ // Max concurrent dispatches guard
86
+ const maxConcurrent = config.dispatch.maxConcurrent;
87
+ if (maxConcurrent > 0) {
88
+ const activeCount = (db.prepare(
89
+ `SELECT COUNT(*) as count FROM events
90
+ WHERE type = 'DISPATCH' AND JSON_EXTRACT(data, '$.resolved') IS NULL`
91
+ ).get() as { count: number }).count;
92
+ if (activeCount >= maxConcurrent) {
93
+ res.status(429).json({ error: `Max concurrent dispatches reached (${maxConcurrent})` });
94
+ return;
95
+ }
96
+ }
97
+
82
98
  // Update scope status if transition provided
83
99
  if (scope_id != null && transition?.to) {
84
100
  const result = scopeService.updateStatus(scope_id, transition.to, 'dispatch');
@@ -107,22 +123,28 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
107
123
  const sessionName = buildSessionName({ scopeId: scope_id ?? undefined, title: scope?.title, command });
108
124
  const beforePids = snapshotSessionPids(projectRoot);
109
125
 
110
- // Launch in iTerm — interactive TUI mode (no -p) for full visibility
126
+ // Launch in iTerm — interactive TUI mode (no -p unless printMode) for full visibility
111
127
  const promptText = prompt ?? command;
112
128
  const escaped = escapeForAnsiC(promptText);
113
- const fullCmd = `cd '${projectRoot}' && ORBITAL_DISPATCH_ID='${eventId}' claude --dangerously-skip-permissions $'${escaped}'`;
129
+ const flagsStr = buildClaudeFlags(config.claude.dispatchFlags);
130
+ const envPrefix = buildEnvVarPrefix(config.dispatch.envVars);
131
+ const fullCmd = `cd '${shellQuote(projectRoot)}' && ${envPrefix}ORBITAL_DISPATCH_ID='${shellQuote(eventId)}' claude ${flagsStr} $'${escaped}'`;
114
132
  try {
115
133
  await launchInCategorizedTerminal(command, fullCmd, sessionName);
116
134
  res.json({ ok: true, dispatch_id: eventId, scope_id: scope_id ?? null });
117
135
 
118
- // Fire-and-forget: discover session PID, link to dispatch, and rename
136
+ // Fire-and-forget: discover session PID, link to dispatch, and rename.
137
+ // If discovery fails, SESSION_START event handler will link via ORBITAL_DISPATCH_ID.
119
138
  discoverNewSession(projectRoot, beforePids)
120
139
  .then((session) => {
121
- if (!session) return;
140
+ if (!session) {
141
+ log.warn('PID discovery returned null — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId, scope_id });
142
+ return;
143
+ }
122
144
  linkPidToDispatch(db, eventId, session.pid);
123
145
  if (sessionName) renameSession(projectRoot, session.sessionId, sessionName);
124
146
  })
125
- .catch(err => log.error('PID discovery failed', { error: err.message }));
147
+ .catch(err => log.warn('PID discovery failed — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId, error: err.message }));
126
148
  } catch (err) {
127
149
  if (scope_id != null && transition?.from) {
128
150
  scopeService.updateStatus(scope_id, transition.from, 'rollback');
@@ -211,8 +233,9 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
211
233
  }
212
234
 
213
235
  // W-12: Validate batch size and scope ID types
214
- if (scope_ids.length > MAX_BATCH_SIZE) {
215
- res.status(400).json({ error: `Maximum batch size is ${MAX_BATCH_SIZE}` });
236
+ const maxBatch = config.dispatch.maxBatchSize || DEFAULT_MAX_BATCH_SIZE;
237
+ if (scope_ids.length > maxBatch) {
238
+ res.status(400).json({ error: `Maximum batch size is ${maxBatch}` });
216
239
  return;
217
240
  }
218
241
  if (!scope_ids.every(id => Number.isInteger(id) && id > 0)) {
@@ -245,21 +268,27 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
245
268
  timestamp: new Date().toISOString(),
246
269
  });
247
270
 
248
- // Launch single CLI session
271
+ // Launch single CLI session with batch env vars
249
272
  const batchEscaped = escapeForAnsiC(command);
250
273
  const beforePids = snapshotSessionPids(projectRoot);
251
- const fullCmd = `cd '${projectRoot}' && ORBITAL_DISPATCH_ID='${eventId}' claude --dangerously-skip-permissions -p $'${batchEscaped}'`;
274
+ const batchFlags = buildClaudeFlags(config.claude.dispatchFlags);
275
+ const envPrefix = buildEnvVarPrefix(config.dispatch.envVars);
276
+ const fullCmd = `cd '${shellQuote(projectRoot)}' && ${envPrefix}ORBITAL_DISPATCH_ID='${shellQuote(eventId)}' claude ${batchFlags} $'${batchEscaped}'`;
252
277
  try {
253
278
  await launchInCategorizedTerminal(command, fullCmd);
254
279
  res.json({ ok: true, dispatch_id: eventId, scope_ids });
255
280
 
256
- // Fire-and-forget: discover session PID and link to dispatch
281
+ // Fire-and-forget: discover session PID and link to dispatch.
282
+ // If discovery fails, SESSION_START event handler will link via ORBITAL_DISPATCH_ID.
257
283
  discoverNewSession(projectRoot, beforePids)
258
284
  .then((session) => {
259
- if (!session) return;
285
+ if (!session) {
286
+ log.warn('Batch PID discovery returned null — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId });
287
+ return;
288
+ }
260
289
  linkPidToDispatch(db, eventId, session.pid);
261
290
  })
262
- .catch(err => log.error('Batch PID discovery failed', { error: err.message }));
291
+ .catch(err => log.warn('Batch PID discovery failed — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId, error: err.message }));
263
292
  } catch (err) {
264
293
  if (transition?.from) {
265
294
  for (const id of scope_ids) {