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
@@ -0,0 +1,143 @@
1
+ import crypto from 'crypto';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import os from 'os';
5
+ import { createLogger } from './utils/logger.js';
6
+ const log = createLogger('global-config');
7
+ // ─── Constants ──────────────────────────────────────────────
8
+ /** Global Orbital Command directory */
9
+ export const ORBITAL_HOME = path.join(os.homedir(), '.orbital');
10
+ /** Path to the global registry file */
11
+ export const REGISTRY_PATH = path.join(ORBITAL_HOME, 'config.json');
12
+ /** Path to global primitives directory */
13
+ export const GLOBAL_PRIMITIVES_DIR = path.join(ORBITAL_HOME, 'primitives');
14
+ /** Path to global workflow config */
15
+ export const GLOBAL_WORKFLOW_PATH = path.join(ORBITAL_HOME, 'workflow.json');
16
+ /** Import from shared — re-exported for convenience */
17
+ import { PROJECT_COLORS } from '../shared/project-colors.js';
18
+ export { PROJECT_COLORS };
19
+ // ─── Slug Generation ────────────────────────────────────────
20
+ /**
21
+ * Generate a project slug from a filesystem path.
22
+ * Uses the directory basename, lowercased, with non-alphanumeric chars
23
+ * replaced by hyphens. Deduplicates against existing IDs with a 4-char hash.
24
+ */
25
+ export function generateProjectId(projectRoot, existingIds) {
26
+ const base = path.basename(projectRoot)
27
+ .toLowerCase()
28
+ .replace(/[^a-z0-9-]/g, '-')
29
+ .replace(/-+/g, '-')
30
+ .replace(/^-|-$/g, '');
31
+ const slug = base || 'project';
32
+ if (!existingIds.includes(slug))
33
+ return slug;
34
+ // Collision — append 4-char hash of the full path
35
+ const hash = crypto.createHash('sha256').update(projectRoot).digest('hex').slice(0, 4);
36
+ return `${slug}-${hash}`;
37
+ }
38
+ // ─── Registry I/O ───────────────────────────────────────────
39
+ /** Ensure ~/.orbital/ directory structure exists, including primitives subdirectories. */
40
+ export function ensureOrbitalHome() {
41
+ if (!fs.existsSync(ORBITAL_HOME)) {
42
+ fs.mkdirSync(ORBITAL_HOME, { recursive: true });
43
+ log.info('Created ~/.orbital/', { path: ORBITAL_HOME });
44
+ }
45
+ // Ensure primitives subdirectories exist so the global watcher can start
46
+ for (const sub of ['agents', 'skills', 'hooks', 'config']) {
47
+ const dir = path.join(GLOBAL_PRIMITIVES_DIR, sub);
48
+ if (!fs.existsSync(dir))
49
+ fs.mkdirSync(dir, { recursive: true });
50
+ }
51
+ }
52
+ /** Load the global registry. Creates default if missing. */
53
+ export function loadGlobalConfig() {
54
+ ensureOrbitalHome();
55
+ if (!fs.existsSync(REGISTRY_PATH)) {
56
+ const config = { version: 1, projects: [] };
57
+ saveGlobalConfig(config);
58
+ return config;
59
+ }
60
+ try {
61
+ const raw = fs.readFileSync(REGISTRY_PATH, 'utf-8');
62
+ return JSON.parse(raw);
63
+ }
64
+ catch (err) {
65
+ log.error('Failed to read global config, creating fresh', { error: String(err) });
66
+ const config = { version: 1, projects: [] };
67
+ saveGlobalConfig(config);
68
+ return config;
69
+ }
70
+ }
71
+ /** Save the global registry atomically. */
72
+ export function saveGlobalConfig(config) {
73
+ ensureOrbitalHome();
74
+ const tmpPath = REGISTRY_PATH + '.tmp';
75
+ fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2), 'utf-8');
76
+ fs.renameSync(tmpPath, REGISTRY_PATH);
77
+ }
78
+ // ─── Project Registration ───────────────────────────────────
79
+ /** Pick the next unused color from the palette. */
80
+ function nextColor(usedColors) {
81
+ const available = PROJECT_COLORS.filter(c => !usedColors.includes(c));
82
+ return available[0] ?? PROJECT_COLORS[0];
83
+ }
84
+ /** Register a project in the global config. Returns the registration. */
85
+ export function registerProject(projectRoot, options) {
86
+ const absPath = path.resolve(projectRoot);
87
+ const config = loadGlobalConfig();
88
+ // Check if already registered
89
+ const existing = config.projects.find(p => p.path === absPath);
90
+ if (existing) {
91
+ log.info('Project already registered', { id: existing.id, path: absPath });
92
+ return existing;
93
+ }
94
+ const existingIds = config.projects.map(p => p.id);
95
+ const usedColors = config.projects.map(p => p.color);
96
+ const registration = {
97
+ id: generateProjectId(absPath, existingIds),
98
+ path: absPath,
99
+ name: options?.name ?? path.basename(absPath),
100
+ color: options?.color ?? nextColor(usedColors),
101
+ registeredAt: new Date().toISOString(),
102
+ enabled: true,
103
+ };
104
+ config.projects.push(registration);
105
+ saveGlobalConfig(config);
106
+ log.info('Project registered', { id: registration.id, path: absPath });
107
+ return registration;
108
+ }
109
+ /** Unregister a project by ID or path. Returns true if found. */
110
+ export function unregisterProject(idOrPath) {
111
+ const config = loadGlobalConfig();
112
+ const absPath = path.isAbsolute(idOrPath) ? idOrPath : path.resolve(idOrPath);
113
+ const idx = config.projects.findIndex(p => p.id === idOrPath || p.path === absPath);
114
+ if (idx === -1)
115
+ return false;
116
+ const removed = config.projects.splice(idx, 1)[0];
117
+ saveGlobalConfig(config);
118
+ log.info('Project unregistered', { id: removed.id, path: removed.path });
119
+ return true;
120
+ }
121
+ /** Update a project's registration (color, name, enabled). */
122
+ export function updateProject(id, updates) {
123
+ const config = loadGlobalConfig();
124
+ const project = config.projects.find(p => p.id === id);
125
+ if (!project)
126
+ return null;
127
+ if (updates.name !== undefined)
128
+ project.name = updates.name;
129
+ if (updates.color !== undefined)
130
+ project.color = updates.color;
131
+ if (updates.enabled !== undefined)
132
+ project.enabled = updates.enabled;
133
+ saveGlobalConfig(config);
134
+ return project;
135
+ }
136
+ /** Get all registered projects. */
137
+ export function getRegisteredProjects() {
138
+ return loadGlobalConfig().projects;
139
+ }
140
+ /** Find a registered project by ID. */
141
+ export function findProject(id) {
142
+ return loadGlobalConfig().projects.find(p => p.id === id);
143
+ }
@@ -4,72 +4,36 @@ import { Server } from 'socket.io';
4
4
  import path from 'path';
5
5
  import fs from 'fs';
6
6
  import { fileURLToPath } from 'url';
7
- import { getDatabase, closeDatabase } from './database.js';
8
- import { getConfig, resetConfig } from './config.js';
9
- import { ScopeCache } from './services/scope-cache.js';
10
- import { ScopeService } from './services/scope-service.js';
11
- import { EventService } from './services/event-service.js';
12
- import { GateService } from './services/gate-service.js';
13
- import { DeployService } from './services/deploy-service.js';
14
- import { SprintService } from './services/sprint-service.js';
15
- import { SprintOrchestrator } from './services/sprint-orchestrator.js';
16
- import { BatchOrchestrator } from './services/batch-orchestrator.js';
17
- import { ReadinessService } from './services/readiness-service.js';
18
- import { startScopeWatcher } from './watchers/scope-watcher.js';
19
- import { startEventWatcher } from './watchers/event-watcher.js';
20
- import { ensureDynamicProfiles } from './utils/terminal-launcher.js';
21
- import { syncClaudeSessionsToDB } from './services/claude-session-service.js';
22
- import { resolveStaleDispatches, resolveActiveDispatchesForScope, resolveDispatchesByPid, resolveDispatchesByDispatchId, linkPidToDispatch } from './utils/dispatch-utils.js';
23
- import { createScopeRoutes } from './routes/scope-routes.js';
24
- import { createDataRoutes } from './routes/data-routes.js';
25
- import { createDispatchRoutes } from './routes/dispatch-routes.js';
26
- import { createSprintRoutes } from './routes/sprint-routes.js';
27
- import { createWorkflowRoutes } from './routes/workflow-routes.js';
28
- import { createConfigRoutes } from './routes/config-routes.js';
29
- import { createGitRoutes } from './routes/git-routes.js';
30
7
  import { createVersionRoutes } from './routes/version-routes.js';
31
- import { WorkflowService } from './services/workflow-service.js';
32
- import { GitService } from './services/git-service.js';
33
- import { GitHubService } from './services/github-service.js';
34
- import { WorkflowEngine } from '../shared/workflow-engine.js';
35
- import defaultWorkflow from '../shared/default-workflow.json' with { type: 'json' };
8
+ import { createAggregateRoutes } from './routes/aggregate-routes.js';
36
9
  import { createLogger, setLogLevel } from './utils/logger.js';
37
- // ─── Server Factory ─────────────────────────────────────────
38
- export async function startServer(overrides) {
39
- // Apply project root override before config loads
40
- if (overrides?.projectRoot) {
41
- process.env.ORBITAL_PROJECT_ROOT = overrides.projectRoot;
42
- resetConfig();
43
- }
44
- const config = getConfig();
10
+ // ─── Central Server ─────────────────────────────────────────
11
+ import { ProjectManager } from './project-manager.js';
12
+ import { SyncService } from './services/sync-service.js';
13
+ import { startGlobalWatcher } from './watchers/global-watcher.js';
14
+ import { createSyncRoutes } from './routes/sync-routes.js';
15
+ import { seedGlobalPrimitives } from './init.js';
16
+ import { ensureOrbitalHome, loadGlobalConfig, registerProject as registerProjectGlobal, GLOBAL_PRIMITIVES_DIR, ORBITAL_HOME, } from './global-config.js';
17
+ export async function startCentralServer(overrides) {
18
+ ensureOrbitalHome();
45
19
  const envLevel = process.env.ORBITAL_LOG_LEVEL;
46
20
  if (envLevel && ['debug', 'info', 'warn', 'error'].includes(envLevel)) {
47
21
  setLogLevel(envLevel);
48
22
  }
49
- else {
50
- setLogLevel(config.logLevel);
23
+ const log = createLogger('central');
24
+ const port = overrides?.port ?? (Number(process.env.ORBITAL_SERVER_PORT) || 4444);
25
+ const clientPort = overrides?.clientPort ?? (Number(process.env.ORBITAL_CLIENT_PORT) || 4445);
26
+ // Auto-register current project if registry is empty
27
+ const globalConfig = loadGlobalConfig();
28
+ if (globalConfig.projects.length === 0 && overrides?.autoRegisterPath) {
29
+ registerProjectGlobal(overrides.autoRegisterPath);
30
+ log.info('Auto-registered current project', { path: overrides.autoRegisterPath });
51
31
  }
52
- const log = createLogger('server');
53
- const port = overrides?.port ?? config.serverPort;
54
- const workflowEngine = new WorkflowEngine(defaultWorkflow);
55
- // Generate shell manifest for bash hooks (config-driven lifecycle)
56
- const MANIFEST_PATH = path.join(config.configDir, 'workflow-manifest.sh');
57
- if (!fs.existsSync(config.configDir))
58
- fs.mkdirSync(config.configDir, { recursive: true });
59
- fs.writeFileSync(MANIFEST_PATH, workflowEngine.generateShellManifest(), 'utf-8');
60
- const ICEBOX_DIR = path.join(config.scopesDir, 'icebox');
61
- // Resolve path to the bundled default workflow config.
62
- const __selfDir2 = path.dirname(fileURLToPath(import.meta.url));
63
- const DEFAULT_CONFIG_PATH = path.resolve(__selfDir2, '../shared/default-workflow.json');
64
- // Ensure icebox directory exists for idea files
65
- if (!fs.existsSync(ICEBOX_DIR))
66
- fs.mkdirSync(ICEBOX_DIR, { recursive: true });
67
32
  const app = express();
68
33
  const httpServer = createServer(app);
69
34
  const io = new Server(httpServer, {
70
35
  cors: {
71
36
  origin: (origin, callback) => {
72
- // Allow all localhost origins (dev tool, not production)
73
37
  if (!origin || origin.startsWith('http://localhost:')) {
74
38
  callback(null, true);
75
39
  }
@@ -80,122 +44,70 @@ export async function startServer(overrides) {
80
44
  methods: ['GET', 'POST'],
81
45
  },
82
46
  });
83
- // Middleware
84
47
  app.use(express.json());
85
- // Initialize database
86
- const db = getDatabase();
87
- // Initialize services
88
- const scopeCache = new ScopeCache();
89
- const scopeService = new ScopeService(scopeCache, io, config.scopesDir, workflowEngine);
90
- const eventService = new EventService(db, io);
91
- const gateService = new GateService(db, io);
92
- const deployService = new DeployService(db, io);
93
- const sprintService = new SprintService(db, io, scopeService);
94
- const sprintOrchestrator = new SprintOrchestrator(db, io, sprintService, scopeService, workflowEngine);
95
- const batchOrchestrator = new BatchOrchestrator(db, io, sprintService, scopeService, workflowEngine);
96
- const readinessService = new ReadinessService(scopeService, gateService, workflowEngine, config.projectRoot);
97
- const workflowService = new WorkflowService(config.configDir, workflowEngine, config.scopesDir, DEFAULT_CONFIG_PATH);
98
- workflowService.setSocketServer(io);
99
- // Ensure in-memory engine reflects the actual active config (may differ from bundled default
100
- // if the user applied a custom preset)
101
- workflowEngine.reload(workflowService.getActive());
102
- const gitService = new GitService(config.projectRoot, scopeCache);
103
- const githubService = new GitHubService(config.projectRoot);
104
- // Wire active-group guard into scope service (blocks manual moves for scopes in active batches/sprints)
105
- scopeService.setActiveGroupCheck((scopeId) => sprintService.getActiveGroupForScope(scopeId));
106
- // ─── Event Wiring ──────────────────────────────────────────
107
- function inferScopeStatus(eventType, scopeId, data) {
108
- if (scopeId == null)
109
- return;
110
- const id = Number(scopeId);
111
- if (isNaN(id) || id <= 0)
112
- return;
113
- // Don't infer status for icebox idea cards
114
- const current = scopeService.getById(id);
115
- if (current?.status === 'icebox')
116
- return;
117
- const currentStatus = current?.status ?? '';
118
- const result = workflowEngine.inferStatus(eventType, currentStatus, data);
119
- if (result === null)
120
- return;
121
- // Handle dispatch resolution (AGENT_COMPLETED with outcome)
122
- if (typeof result === 'object' && 'dispatchResolution' in result) {
123
- resolveActiveDispatchesForScope(db, io, id, result.resolution);
124
- return;
125
- }
126
- scopeService.updateStatus(id, result, 'event');
127
- }
128
- eventService.onIngest((eventType, scopeId, data) => {
129
- // Handle SESSION_START: link PID to dispatch via dispatch_id env var
130
- if (eventType === 'SESSION_START' && typeof data.dispatch_id === 'string' && typeof data.pid === 'number') {
131
- linkPidToDispatch(db, data.dispatch_id, data.pid);
132
- log.info('SESSION_START: linked PID to dispatch', { pid: data.pid, dispatch_id: data.dispatch_id });
133
- return;
134
- }
135
- // Handle SESSION_END: resolve dispatches by dispatch_id (preferred) or PID (fallback)
136
- if (eventType === 'SESSION_END') {
137
- let count = 0;
138
- if (typeof data.dispatch_id === 'string') {
139
- count = resolveDispatchesByDispatchId(db, io, data.dispatch_id);
140
- if (count > 0) {
141
- log.info('SESSION_END: resolved dispatches', { count, dispatch_id: data.dispatch_id });
142
- }
143
- }
144
- // PID fallback for old hooks without dispatch_id
145
- if (count === 0 && typeof data.pid === 'number') {
146
- count = resolveDispatchesByPid(db, io, data.pid);
147
- if (count > 0) {
148
- log.info('SESSION_END: resolved dispatches by PID fallback', { count, pid: data.pid });
149
- }
48
+ // ─── Bind port early ──────────────────────────────────────
49
+ // Listen before async init so Vite's proxy doesn't get ECONNREFUSED
50
+ const actualPort = await new Promise((resolve, reject) => {
51
+ let attempt = 0;
52
+ const maxAttempts = 10;
53
+ httpServer.on('error', (err) => {
54
+ if (err.code === 'EADDRINUSE' && attempt < maxAttempts) {
55
+ attempt++;
56
+ httpServer.listen(port + attempt);
150
57
  }
151
- // Immediately resolve any batches/sprints whose session just ended,
152
- // rather than waiting for the next stale-check interval
153
- if (count > 0) {
154
- batchOrchestrator.resolveStaleBatches();
58
+ else {
59
+ reject(new Error(`Failed to start server: ${err.message}`));
155
60
  }
156
- return;
157
- }
158
- inferScopeStatus(eventType, scopeId, data);
159
- });
160
- scopeService.onStatusChange((scopeId, newStatus) => {
161
- if (newStatus === 'dev') {
162
- sprintOrchestrator.onScopeReachedDev(scopeId);
163
- }
164
- // Batch orchestrator tracks all status transitions (dev, staging, production)
165
- batchOrchestrator.onScopeStatusChanged(scopeId, newStatus);
61
+ });
62
+ httpServer.on('listening', () => {
63
+ const addr = httpServer.address();
64
+ resolve(typeof addr === 'object' && addr ? addr.port : port);
65
+ });
66
+ httpServer.listen(port);
166
67
  });
167
- scopeService.onStatusChange((scopeId, newStatus) => {
168
- if (workflowEngine.isTerminalStatus(newStatus)) {
169
- resolveActiveDispatchesForScope(db, io, scopeId, 'completed');
170
- }
68
+ // Initialize ProjectManager and boot all registered projects
69
+ const projectManager = new ProjectManager(io);
70
+ await projectManager.initializeAll();
71
+ // Seed global primitives if empty (lazy fallback for first launch)
72
+ const globalPrimitivesEmpty = ['agents', 'skills', 'hooks'].every(t => {
73
+ const dir = path.join(GLOBAL_PRIMITIVES_DIR, t);
74
+ return !fs.existsSync(dir) || fs.readdirSync(dir).filter(f => !f.startsWith('.')).length === 0;
171
75
  });
172
- // ─── Routes ────────────────────────────────────────────────
76
+ if (globalPrimitivesEmpty) {
77
+ seedGlobalPrimitives();
78
+ log.info('Seeded global primitives from package templates');
79
+ }
80
+ // Initialize SyncService and global watcher
81
+ const syncService = new SyncService();
82
+ const globalWatcher = startGlobalWatcher(syncService, io);
83
+ // ─── Routes ──────────────────────────────────────────────
84
+ // Health check
173
85
  app.get('/api/orbital/health', (_req, res) => {
174
86
  res.json({ status: 'ok', uptime: process.uptime(), timestamp: new Date().toISOString() });
175
87
  });
176
- // Serve dynamic config to the frontend
177
- app.get('/api/orbital/config', (_req, res) => {
178
- res.json({
179
- projectName: config.projectName,
180
- categories: config.categories,
181
- agents: config.agents,
182
- serverPort: config.serverPort,
183
- clientPort: config.clientPort,
184
- });
185
- });
186
- app.use('/api/orbital', createScopeRoutes({ db, io, scopeService, readinessService, projectRoot: config.projectRoot, engine: workflowEngine }));
187
- app.use('/api/orbital', createDataRoutes({ db, io, gateService, deployService, engine: workflowEngine, projectRoot: config.projectRoot, inferScopeStatus }));
188
- app.use('/api/orbital', createDispatchRoutes({ db, io, scopeService, projectRoot: config.projectRoot, engine: workflowEngine }));
189
- app.use('/api/orbital', createSprintRoutes({ sprintService, sprintOrchestrator, batchOrchestrator }));
190
- app.use('/api/orbital', createWorkflowRoutes({ workflowService, projectRoot: config.projectRoot }));
191
- app.use('/api/orbital', createConfigRoutes({ projectRoot: config.projectRoot, workflowService, io }));
192
- app.use('/api/orbital', createGitRoutes({ gitService, githubService, engine: workflowEngine }));
88
+ // Project management + sync routes (top-level)
89
+ app.use('/api/orbital', createSyncRoutes({ syncService, projectManager }));
193
90
  app.use('/api/orbital', createVersionRoutes({ io }));
194
- // ─── Static File Serving (production) ───────────────────────
195
- // Resolve the Vite-built frontend dist directory (server/ → ../dist).
91
+ // Per-project routes dynamic middleware that resolves :projectId
92
+ app.use('/api/orbital/projects/:projectId', (req, res, next) => {
93
+ const projectId = req.params.projectId;
94
+ const router = projectManager.getRouter(projectId);
95
+ if (!router) {
96
+ const ctx = projectManager.getContext(projectId);
97
+ if (!ctx)
98
+ return res.status(404).json({ error: `Project '${projectId}' not found` });
99
+ return res.status(503).json({ error: `Project '${projectId}' is offline` });
100
+ }
101
+ router(req, res, next);
102
+ });
103
+ // Aggregate endpoints (cross-project)
104
+ app.use('/api/orbital', createAggregateRoutes({ projectManager, io, syncService }));
105
+ // ─── Static File Serving ─────────────────────────────────
196
106
  const __selfDir = path.dirname(fileURLToPath(import.meta.url));
197
107
  const distDir = path.resolve(__selfDir, '../dist');
198
- if (fs.existsSync(path.join(distDir, 'index.html'))) {
108
+ const devMode = clientPort !== port;
109
+ const hasBuiltFrontend = !devMode && fs.existsSync(path.join(distDir, 'index.html'));
110
+ if (hasBuiltFrontend) {
199
111
  app.use(express.static(distDir));
200
112
  app.get('*', (req, res, next) => {
201
113
  if (req.path.startsWith('/api/') || req.path.startsWith('/socket.io'))
@@ -204,159 +116,89 @@ export async function startServer(overrides) {
204
116
  });
205
117
  }
206
118
  else {
207
- // Dev mode: redirect root to Vite dev server
208
- app.get('/', (_req, res) => res.redirect(`http://localhost:${config.clientPort}`));
119
+ app.get('/', (_req, res) => res.redirect(`http://localhost:${clientPort}`));
209
120
  }
210
- // ─── Socket.io ──────────────────────────────────────────────
121
+ // ─── Socket.io ───────────────────────────────────────────
211
122
  io.on('connection', (socket) => {
212
123
  log.debug('Client connected', { socketId: socket.id });
213
- socket.on('disconnect', () => {
214
- log.debug('Client disconnected', { socketId: socket.id });
124
+ socket.on('subscribe', (payload) => {
125
+ if (payload.scope === 'all') {
126
+ socket.join('all-projects');
127
+ }
128
+ else if (payload.projectId) {
129
+ socket.join(`project:${payload.projectId}`);
130
+ }
215
131
  });
216
- });
217
- // ─── Startup ───────────────────────────────────────────────
218
- // References for graceful shutdown
219
- let scopeWatcher;
220
- let eventWatcher;
221
- let batchRecoveryInterval;
222
- let staleCleanupInterval;
223
- let sessionSyncInterval;
224
- let gitPollInterval;
225
- const actualPort = await new Promise((resolve, reject) => {
226
- let attempt = 0;
227
- const maxAttempts = 10;
228
- httpServer.on('error', (err) => {
229
- if (err.code === 'EADDRINUSE' && attempt < maxAttempts) {
230
- attempt++;
231
- const nextPort = port + attempt;
232
- log.warn('Port in use, trying next', { tried: port + attempt - 1, next: nextPort });
233
- httpServer.listen(nextPort);
132
+ socket.on('unsubscribe', (payload) => {
133
+ if (payload.scope === 'all') {
134
+ socket.leave('all-projects');
234
135
  }
235
- else {
236
- reject(new Error(`Failed to start server: ${err.message}`));
136
+ else if (payload.projectId) {
137
+ socket.leave(`project:${payload.projectId}`);
237
138
  }
238
139
  });
239
- httpServer.on('listening', () => {
240
- const addr = httpServer.address();
241
- const listenPort = typeof addr === 'object' && addr ? addr.port : port;
242
- resolve(listenPort);
140
+ socket.on('disconnect', () => {
141
+ log.debug('Client disconnected', { socketId: socket.id });
243
142
  });
244
- httpServer.listen(port);
245
143
  });
246
- // ─── Post-listen initialization ────────────────────────────
247
- // Sync scopes from filesystem on startup (populates in-memory cache)
248
- const scopeCount = scopeService.syncFromFilesystem();
249
- // Resolve stale dispatch events (terminal scopes + age-based)
250
- const staleResolved = resolveStaleDispatches(db, io, scopeService, workflowEngine);
251
- if (staleResolved > 0) {
252
- log.info('Resolved stale dispatch events', { count: staleResolved });
253
- }
254
- // Write iTerm2 dispatch profiles (idempotent, fire-and-forget)
255
- ensureDynamicProfiles(workflowEngine);
256
- // Start file watchers
257
- scopeWatcher = startScopeWatcher(config.scopesDir, scopeService);
258
- eventWatcher = startEventWatcher(config.eventsDir, eventService);
259
- // Recover any active sprints/batches from before server restart
260
- sprintOrchestrator.recoverActiveSprints().catch(err => log.error('Sprint recovery failed', { error: err.message }));
261
- batchOrchestrator.recoverActiveBatches().catch(err => log.error('Batch recovery failed', { error: err.message }));
262
- // Resolve stale batches on startup (catches stuck dispatches from previous runs)
263
- const staleBatchesResolved = batchOrchestrator.resolveStaleBatches();
264
- if (staleBatchesResolved > 0) {
265
- log.info('Resolved stale batches', { count: staleBatchesResolved });
266
- }
267
- // Poll active batch PIDs every 30s for two-phase completion (B-1)
268
- batchRecoveryInterval = setInterval(() => {
269
- batchOrchestrator.recoverActiveBatches().catch(err => log.error('Batch recovery failed', { error: err.message }));
270
- }, 30_000);
271
- // Periodic stale dispatch + batch cleanup (crash recovery — catches SIGKILL'd sessions)
272
- staleCleanupInterval = setInterval(() => {
273
- const count = resolveStaleDispatches(db, io, scopeService, workflowEngine);
274
- if (count > 0) {
275
- log.info('Periodic cleanup: resolved stale dispatches', { count });
144
+ // ─── Error Handling Middleware ─────────────────────────────
145
+ // Catches unhandled errors thrown from route handlers.
146
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
147
+ app.use((err, _req, res, _next) => {
148
+ log.error('Unhandled route error', { error: err.message, stack: err.stack });
149
+ if (!res.headersSent) {
150
+ res.status(500).json({ ok: false, error: 'Internal server error' });
276
151
  }
277
- const batchCount = batchOrchestrator.resolveStaleBatches();
278
- if (batchCount > 0) {
279
- log.info('Periodic cleanup: resolved stale batches', { count: batchCount });
280
- }
281
- }, 30_000);
282
- // Sync frontmatter-derived sessions into DB (non-blocking)
283
- syncClaudeSessionsToDB(db, scopeService).then((count) => {
284
- log.info('Synced frontmatter sessions', { count });
285
- // Purge legacy pattern-matched rows (no action = old regex system)
286
- const purged = db.prepare("DELETE FROM sessions WHERE action IS NULL AND id LIKE 'claude-%'").run();
287
- if (purged.changes > 0) {
288
- log.info('Purged legacy pattern-matched session rows', { count: purged.changes });
289
- }
290
- }).catch(err => log.error('Session sync failed', { error: err.message }));
291
- // Re-sync every 5 minutes so new sessions appear without restart
292
- sessionSyncInterval = setInterval(() => {
293
- syncClaudeSessionsToDB(db, scopeService)
294
- .then((count) => {
295
- if (count > 0)
296
- io.emit('session:updated', { type: 'resync', count });
297
- })
298
- .catch(err => log.error('Session resync failed', { error: err.message }));
299
- }, 5 * 60 * 1000);
300
- // Poll git status every 10s — emit socket event on change
301
- let lastGitHash = '';
302
- gitPollInterval = setInterval(async () => {
303
- try {
304
- const hash = await gitService.getStatusHash();
305
- if (lastGitHash && hash !== lastGitHash) {
306
- gitService.clearCache();
307
- io.emit('git:status:changed');
308
- }
309
- lastGitHash = hash;
310
- }
311
- catch { /* ok */ }
312
- }, 10_000);
152
+ });
153
+ // ─── Startup Banner ──────────────────────────────────────
154
+ const projectList = projectManager.getProjectList();
155
+ const projectLines = projectList.map(p => `║ ${p.status === 'active' ? '●' : '○'} ${p.name.padEnd(20)} ${String(p.scopeCount).padStart(3)} scopes ${p.status.padEnd(8)} ║`).join('\n');
156
+ const dashboardPort = devMode ? clientPort : actualPort;
313
157
  // eslint-disable-next-line no-console
314
158
  console.log(`
315
159
  ╔══════════════════════════════════════════════════════╗
316
- ║ Orbital Command
317
- ║ ${config.projectName.padEnd(42)} ║
160
+ ║ Orbital Command — Central Server
318
161
  ║ ║
319
- ║ >>> Open: http://localhost:${actualPort} <<< ║
162
+ ║ >>> Open: http://localhost:${String(dashboardPort).padEnd(25)} <<<║
320
163
  ║ ║
321
164
  ╠══════════════════════════════════════════════════════╣
322
- ║ Scopes: ${String(scopeCount).padEnd(3)} loaded from filesystem ║
165
+ ${projectLines}
166
+ ╠══════════════════════════════════════════════════════╣
323
167
  ║ API: http://localhost:${actualPort}/api/orbital/* ║
324
168
  ║ Socket.io: ws://localhost:${actualPort} ║
169
+ ║ Home: ${ORBITAL_HOME.padEnd(39)} ║
325
170
  ╚══════════════════════════════════════════════════════╝
326
171
  `);
327
- // ─── Graceful Shutdown ─────────────────────────────────────
172
+ // ─── Graceful Shutdown ───────────────────────────────────
328
173
  let shuttingDown = false;
329
- function shutdown() {
174
+ async function shutdown() {
330
175
  if (shuttingDown)
331
- return Promise.resolve();
176
+ return;
332
177
  shuttingDown = true;
333
- log.info('Shutting down');
334
- scopeWatcher.close();
335
- eventWatcher.close();
336
- clearInterval(batchRecoveryInterval);
337
- clearInterval(staleCleanupInterval);
338
- clearInterval(sessionSyncInterval);
339
- clearInterval(gitPollInterval);
178
+ log.info('Shutting down central server');
179
+ if (globalWatcher)
180
+ await globalWatcher.close();
181
+ await projectManager.shutdownAll();
340
182
  return new Promise((resolve) => {
341
- const forceTimeout = setTimeout(() => {
342
- closeDatabase();
343
- resolve();
344
- }, 2000);
183
+ const forceTimeout = setTimeout(resolve, 2000);
345
184
  io.close(() => {
346
185
  clearTimeout(forceTimeout);
347
- closeDatabase();
348
186
  resolve();
349
187
  });
350
188
  });
351
189
  }
352
- return { app, io, db, workflowEngine, httpServer, shutdown };
190
+ return { app, io, projectManager, syncService, httpServer, shutdown };
353
191
  }
354
192
  // ─── Direct Execution (backward compat: tsx watch server/index.ts) ───
355
193
  const isDirectRun = process.argv[1] && (process.argv[1].endsWith('server/index.ts') ||
356
194
  process.argv[1].endsWith('server/index.js') ||
357
195
  process.argv[1].endsWith('server'));
358
196
  if (isDirectRun) {
359
- startServer().then(({ shutdown }) => {
197
+ const projectRoot = process.env.ORBITAL_PROJECT_ROOT || process.cwd();
198
+ startCentralServer({
199
+ port: Number(process.env.ORBITAL_SERVER_PORT) || 4444,
200
+ autoRegisterPath: projectRoot,
201
+ }).then(({ shutdown }) => {
360
202
  process.on('SIGINT', async () => {
361
203
  await shutdown();
362
204
  process.exit(0);