orbital-command 0.1.4 → 0.3.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 (380) hide show
  1. package/bin/orbital.js +676 -53
  2. package/dist/assets/PrimitivesConfig-CrmQXYh4.js +32 -0
  3. package/dist/assets/QualityGates-BbasOsF3.js +21 -0
  4. package/dist/assets/SessionTimeline-CGeJsVvy.js +1 -0
  5. package/dist/assets/Settings-oiM496mc.js +12 -0
  6. package/dist/assets/SourceControl-B1fP2nJL.js +41 -0
  7. package/dist/assets/WorkflowVisualizer-CWLYf-f0.js +74 -0
  8. package/dist/assets/arrow-down-CPy85_J6.js +6 -0
  9. package/dist/assets/charts-DbDg0Psc.js +68 -0
  10. package/dist/assets/circle-x-Cwz6ZQDV.js +6 -0
  11. package/dist/assets/file-text-C46Xr65c.js +6 -0
  12. package/dist/assets/formatDistanceToNow-BMqsSP44.js +1 -0
  13. package/dist/assets/globe-Cn2yNZUD.js +6 -0
  14. package/dist/assets/index-Aj4sV8Al.css +1 -0
  15. package/dist/assets/index-Bc9dK3MW.js +354 -0
  16. package/dist/assets/key-OPaNTWJ5.js +6 -0
  17. package/dist/assets/minus-GMsbpKym.js +6 -0
  18. package/dist/assets/shield-DwAFkDYI.js +6 -0
  19. package/dist/assets/ui-BmsSg9jU.js +53 -0
  20. package/dist/assets/useWorkflowEditor-BJkTX_NR.js +16 -0
  21. package/dist/assets/{vendor-Dzv9lrRc.js → vendor-Bqt8AJn2.js} +1 -1
  22. package/dist/assets/zap-DfbUoOty.js +11 -0
  23. package/dist/favicon.svg +1 -0
  24. package/dist/index.html +6 -5
  25. package/dist/server/server/__tests__/data-routes.test.js +124 -0
  26. package/dist/server/server/__tests__/helpers/db.js +17 -0
  27. package/dist/server/server/__tests__/helpers/mock-emitter.js +8 -0
  28. package/dist/server/server/__tests__/scope-routes.test.js +137 -0
  29. package/dist/server/server/__tests__/sprint-routes.test.js +102 -0
  30. package/dist/server/server/__tests__/workflow-routes.test.js +107 -0
  31. package/dist/server/server/config-migrator.js +138 -0
  32. package/dist/server/server/config.js +17 -2
  33. package/dist/server/server/database.js +27 -12
  34. package/dist/server/server/global-config.js +143 -0
  35. package/dist/server/server/index.js +882 -252
  36. package/dist/server/server/init.js +579 -194
  37. package/dist/server/server/launch.js +29 -0
  38. package/dist/server/server/manifest-types.js +8 -0
  39. package/dist/server/server/manifest.js +454 -0
  40. package/dist/server/server/migrate-legacy.js +229 -0
  41. package/dist/server/server/parsers/event-parser.test.js +117 -0
  42. package/dist/server/server/parsers/scope-parser.js +74 -28
  43. package/dist/server/server/parsers/scope-parser.test.js +230 -0
  44. package/dist/server/server/project-context.js +255 -0
  45. package/dist/server/server/project-emitter.js +41 -0
  46. package/dist/server/server/project-manager.js +297 -0
  47. package/dist/server/server/routes/config-routes.js +1 -3
  48. package/dist/server/server/routes/data-routes.js +22 -110
  49. package/dist/server/server/routes/dispatch-routes.js +15 -9
  50. package/dist/server/server/routes/git-routes.js +74 -0
  51. package/dist/server/server/routes/manifest-routes.js +319 -0
  52. package/dist/server/server/routes/scope-routes.js +37 -23
  53. package/dist/server/server/routes/sync-routes.js +134 -0
  54. package/dist/server/server/routes/version-routes.js +1 -15
  55. package/dist/server/server/routes/workflow-routes.js +9 -3
  56. package/dist/server/server/schema.js +2 -0
  57. package/dist/server/server/services/batch-orchestrator.js +26 -16
  58. package/dist/server/server/services/claude-session-service.js +17 -14
  59. package/dist/server/server/services/deploy-service.test.js +119 -0
  60. package/dist/server/server/services/event-service.js +64 -1
  61. package/dist/server/server/services/event-service.test.js +191 -0
  62. package/dist/server/server/services/gate-service.test.js +105 -0
  63. package/dist/server/server/services/git-service.js +108 -4
  64. package/dist/server/server/services/github-service.js +110 -2
  65. package/dist/server/server/services/readiness-service.test.js +190 -0
  66. package/dist/server/server/services/scope-cache.js +5 -1
  67. package/dist/server/server/services/scope-cache.test.js +142 -0
  68. package/dist/server/server/services/scope-service.js +217 -126
  69. package/dist/server/server/services/scope-service.test.js +137 -0
  70. package/dist/server/server/services/sprint-orchestrator.js +7 -6
  71. package/dist/server/server/services/sprint-service.js +21 -1
  72. package/dist/server/server/services/sprint-service.test.js +238 -0
  73. package/dist/server/server/services/sync-service.js +434 -0
  74. package/dist/server/server/services/sync-types.js +2 -0
  75. package/dist/server/server/services/telemetry-service.js +143 -0
  76. package/dist/server/server/services/workflow-service.js +26 -5
  77. package/dist/server/server/services/workflow-service.test.js +159 -0
  78. package/dist/server/server/settings-sync.js +284 -0
  79. package/dist/server/server/update-planner.js +279 -0
  80. package/dist/server/server/utils/cc-hooks-parser.js +3 -0
  81. package/dist/server/server/utils/cc-hooks-parser.test.js +86 -0
  82. package/dist/server/server/utils/dispatch-utils.js +77 -20
  83. package/dist/server/server/utils/dispatch-utils.test.js +182 -0
  84. package/dist/server/server/utils/logger.js +37 -3
  85. package/dist/server/server/utils/package-info.js +30 -0
  86. package/dist/server/server/utils/route-helpers.js +10 -0
  87. package/dist/server/server/utils/terminal-launcher.js +79 -25
  88. package/dist/server/server/utils/worktree-manager.js +13 -4
  89. package/dist/server/server/validator.js +230 -0
  90. package/dist/server/server/watchers/global-watcher.js +63 -0
  91. package/dist/server/server/watchers/scope-watcher.js +27 -12
  92. package/dist/server/server/wizard/config-editor.js +237 -0
  93. package/dist/server/server/wizard/detect.js +96 -0
  94. package/dist/server/server/wizard/doctor.js +115 -0
  95. package/dist/server/server/wizard/index.js +155 -0
  96. package/dist/server/server/wizard/phases/confirm.js +39 -0
  97. package/dist/server/server/wizard/phases/project-setup.js +90 -0
  98. package/dist/server/server/wizard/phases/setup-wizard.js +66 -0
  99. package/dist/server/server/wizard/phases/welcome.js +35 -0
  100. package/dist/server/server/wizard/phases/workflow-setup.js +22 -0
  101. package/dist/server/server/wizard/types.js +29 -0
  102. package/dist/server/server/wizard/ui.js +74 -0
  103. package/dist/server/shared/__fixtures__/workflow-configs.js +75 -0
  104. package/dist/server/shared/default-workflow.json +65 -0
  105. package/dist/server/shared/onboarding-tour.test.js +81 -0
  106. package/dist/server/shared/project-colors.js +24 -0
  107. package/dist/server/shared/workflow-config.test.js +84 -0
  108. package/dist/server/shared/workflow-engine.test.js +302 -0
  109. package/dist/server/shared/workflow-normalizer.js +101 -0
  110. package/dist/server/shared/workflow-normalizer.test.js +100 -0
  111. package/dist/server/src/components/onboarding/tour-steps.js +84 -0
  112. package/package.json +20 -15
  113. package/schemas/orbital.config.schema.json +16 -1
  114. package/scripts/postinstall.js +55 -7
  115. package/server/__tests__/data-routes.test.ts +149 -0
  116. package/server/__tests__/helpers/db.ts +19 -0
  117. package/server/__tests__/helpers/mock-emitter.ts +10 -0
  118. package/server/__tests__/scope-routes.test.ts +157 -0
  119. package/server/__tests__/sprint-routes.test.ts +118 -0
  120. package/server/__tests__/workflow-routes.test.ts +120 -0
  121. package/server/config-migrator.ts +163 -0
  122. package/server/config.ts +26 -2
  123. package/server/database.ts +35 -18
  124. package/server/global-config.ts +200 -0
  125. package/server/index.ts +975 -287
  126. package/server/init.ts +625 -182
  127. package/server/launch.ts +32 -0
  128. package/server/manifest-types.ts +145 -0
  129. package/server/manifest.ts +494 -0
  130. package/server/migrate-legacy.ts +290 -0
  131. package/server/parsers/event-parser.test.ts +135 -0
  132. package/server/parsers/scope-parser.test.ts +270 -0
  133. package/server/parsers/scope-parser.ts +79 -31
  134. package/server/project-context.ts +309 -0
  135. package/server/project-emitter.ts +50 -0
  136. package/server/project-manager.ts +369 -0
  137. package/server/routes/config-routes.ts +3 -5
  138. package/server/routes/data-routes.ts +28 -141
  139. package/server/routes/dispatch-routes.ts +19 -11
  140. package/server/routes/git-routes.ts +77 -0
  141. package/server/routes/manifest-routes.ts +388 -0
  142. package/server/routes/scope-routes.ts +29 -25
  143. package/server/routes/sync-routes.ts +175 -0
  144. package/server/routes/version-routes.ts +1 -16
  145. package/server/routes/workflow-routes.ts +9 -3
  146. package/server/schema.ts +2 -0
  147. package/server/services/batch-orchestrator.ts +24 -16
  148. package/server/services/claude-session-service.ts +16 -14
  149. package/server/services/deploy-service.test.ts +145 -0
  150. package/server/services/deploy-service.ts +2 -2
  151. package/server/services/event-service.test.ts +242 -0
  152. package/server/services/event-service.ts +92 -3
  153. package/server/services/gate-service.test.ts +131 -0
  154. package/server/services/gate-service.ts +2 -2
  155. package/server/services/git-service.ts +137 -4
  156. package/server/services/github-service.ts +120 -2
  157. package/server/services/readiness-service.test.ts +217 -0
  158. package/server/services/scope-cache.test.ts +167 -0
  159. package/server/services/scope-cache.ts +4 -1
  160. package/server/services/scope-service.test.ts +169 -0
  161. package/server/services/scope-service.ts +220 -126
  162. package/server/services/sprint-orchestrator.ts +7 -7
  163. package/server/services/sprint-service.test.ts +271 -0
  164. package/server/services/sprint-service.ts +27 -3
  165. package/server/services/sync-service.ts +482 -0
  166. package/server/services/sync-types.ts +77 -0
  167. package/server/services/telemetry-service.ts +195 -0
  168. package/server/services/workflow-service.test.ts +190 -0
  169. package/server/services/workflow-service.ts +29 -9
  170. package/server/settings-sync.ts +359 -0
  171. package/server/update-planner.ts +346 -0
  172. package/server/utils/cc-hooks-parser.test.ts +96 -0
  173. package/server/utils/cc-hooks-parser.ts +4 -0
  174. package/server/utils/dispatch-utils.test.ts +245 -0
  175. package/server/utils/dispatch-utils.ts +97 -27
  176. package/server/utils/logger.ts +40 -3
  177. package/server/utils/package-info.ts +32 -0
  178. package/server/utils/route-helpers.ts +12 -0
  179. package/server/utils/terminal-launcher.ts +85 -25
  180. package/server/utils/worktree-manager.ts +9 -4
  181. package/server/validator.ts +270 -0
  182. package/server/watchers/global-watcher.ts +77 -0
  183. package/server/watchers/scope-watcher.ts +21 -9
  184. package/server/wizard/config-editor.ts +248 -0
  185. package/server/wizard/detect.ts +104 -0
  186. package/server/wizard/doctor.ts +114 -0
  187. package/server/wizard/index.ts +187 -0
  188. package/server/wizard/phases/confirm.ts +45 -0
  189. package/server/wizard/phases/project-setup.ts +106 -0
  190. package/server/wizard/phases/setup-wizard.ts +78 -0
  191. package/server/wizard/phases/welcome.ts +43 -0
  192. package/server/wizard/phases/workflow-setup.ts +28 -0
  193. package/server/wizard/types.ts +56 -0
  194. package/server/wizard/ui.ts +93 -0
  195. package/shared/__fixtures__/workflow-configs.ts +80 -0
  196. package/shared/default-workflow.json +65 -0
  197. package/shared/onboarding-tour.test.ts +94 -0
  198. package/shared/project-colors.ts +24 -0
  199. package/shared/workflow-config.test.ts +111 -0
  200. package/shared/workflow-config.ts +7 -0
  201. package/shared/workflow-engine.test.ts +388 -0
  202. package/shared/workflow-normalizer.test.ts +119 -0
  203. package/shared/workflow-normalizer.ts +118 -0
  204. package/templates/hooks/end-session.sh +3 -1
  205. package/templates/hooks/orbital-emit.sh +2 -2
  206. package/templates/hooks/orbital-report-deploy.sh +4 -4
  207. package/templates/hooks/orbital-report-gates.sh +4 -4
  208. package/templates/hooks/orbital-scope-update.sh +1 -1
  209. package/templates/hooks/scope-create-cleanup.sh +2 -2
  210. package/templates/hooks/scope-create-gate.sh +0 -1
  211. package/templates/hooks/scope-helpers.sh +18 -0
  212. package/templates/hooks/scope-prepare.sh +66 -11
  213. package/templates/migrations/renames.json +1 -0
  214. package/templates/orbital.config.json +7 -2
  215. package/templates/settings-hooks.json +1 -1
  216. package/templates/skills/git-commit/SKILL.md +9 -4
  217. package/templates/skills/git-dev/SKILL.md +8 -3
  218. package/templates/skills/git-main/SKILL.md +8 -2
  219. package/templates/skills/git-production/SKILL.md +6 -2
  220. package/templates/skills/git-staging/SKILL.md +8 -3
  221. package/templates/skills/scope-create/SKILL.md +17 -3
  222. package/templates/skills/scope-fix-review/SKILL.md +6 -3
  223. package/templates/skills/scope-implement/SKILL.md +4 -1
  224. package/templates/skills/scope-post-review/SKILL.md +63 -5
  225. package/templates/skills/scope-pre-review/SKILL.md +5 -2
  226. package/templates/skills/scope-verify/SKILL.md +5 -3
  227. package/templates/skills/test-code-review/SKILL.md +41 -33
  228. package/templates/skills/test-scaffold/SKILL.md +222 -0
  229. package/dist/assets/WorkflowVisualizer-BZ21PIIF.js +0 -84
  230. package/dist/assets/charts-D__PA1zp.js +0 -72
  231. package/dist/assets/index-D1G6i0nS.css +0 -1
  232. package/dist/assets/index-DpItvKpf.js +0 -419
  233. package/dist/assets/ui-BvF022GT.js +0 -53
  234. package/index.html +0 -15
  235. package/postcss.config.js +0 -6
  236. package/src/App.tsx +0 -33
  237. package/src/components/AgentBadge.tsx +0 -40
  238. package/src/components/BatchPreflightModal.tsx +0 -115
  239. package/src/components/CardDisplayToggle.tsx +0 -74
  240. package/src/components/ColumnHeaderActions.tsx +0 -55
  241. package/src/components/ColumnMenu.tsx +0 -99
  242. package/src/components/DeployHistory.tsx +0 -141
  243. package/src/components/DispatchModal.tsx +0 -164
  244. package/src/components/DispatchPopover.tsx +0 -139
  245. package/src/components/DragOverlay.tsx +0 -25
  246. package/src/components/DriftSidebar.tsx +0 -140
  247. package/src/components/EnvironmentStrip.tsx +0 -88
  248. package/src/components/ErrorBoundary.tsx +0 -62
  249. package/src/components/FilterChip.tsx +0 -105
  250. package/src/components/GateIndicator.tsx +0 -33
  251. package/src/components/IdeaDetailModal.tsx +0 -190
  252. package/src/components/IdeaFormDialog.tsx +0 -113
  253. package/src/components/KanbanColumn.tsx +0 -201
  254. package/src/components/MarkdownRenderer.tsx +0 -114
  255. package/src/components/NeonGrid.tsx +0 -128
  256. package/src/components/PromotionQueue.tsx +0 -89
  257. package/src/components/ScopeCard.tsx +0 -234
  258. package/src/components/ScopeDetailModal.tsx +0 -255
  259. package/src/components/ScopeFilterBar.tsx +0 -152
  260. package/src/components/SearchInput.tsx +0 -102
  261. package/src/components/SessionPanel.tsx +0 -335
  262. package/src/components/SprintContainer.tsx +0 -303
  263. package/src/components/SprintDependencyDialog.tsx +0 -78
  264. package/src/components/SprintPreflightModal.tsx +0 -138
  265. package/src/components/StatusBar.tsx +0 -168
  266. package/src/components/SwimCell.tsx +0 -67
  267. package/src/components/SwimLaneRow.tsx +0 -94
  268. package/src/components/SwimlaneBoardView.tsx +0 -108
  269. package/src/components/VersionBadge.tsx +0 -139
  270. package/src/components/ViewModeSelector.tsx +0 -114
  271. package/src/components/config/AgentChip.tsx +0 -53
  272. package/src/components/config/AgentCreateDialog.tsx +0 -321
  273. package/src/components/config/AgentEditor.tsx +0 -175
  274. package/src/components/config/DirectoryTree.tsx +0 -582
  275. package/src/components/config/FileEditor.tsx +0 -550
  276. package/src/components/config/HookChip.tsx +0 -50
  277. package/src/components/config/StageCard.tsx +0 -198
  278. package/src/components/config/TransitionZone.tsx +0 -173
  279. package/src/components/config/UnifiedWorkflowPipeline.tsx +0 -216
  280. package/src/components/config/WorkflowPipeline.tsx +0 -161
  281. package/src/components/source-control/BranchList.tsx +0 -93
  282. package/src/components/source-control/BranchPanel.tsx +0 -105
  283. package/src/components/source-control/CommitLog.tsx +0 -100
  284. package/src/components/source-control/CommitRow.tsx +0 -47
  285. package/src/components/source-control/GitHubPanel.tsx +0 -110
  286. package/src/components/source-control/GitHubSetupGuide.tsx +0 -52
  287. package/src/components/source-control/GitOverviewBar.tsx +0 -101
  288. package/src/components/source-control/PullRequestList.tsx +0 -69
  289. package/src/components/source-control/WorktreeList.tsx +0 -80
  290. package/src/components/ui/badge.tsx +0 -41
  291. package/src/components/ui/button.tsx +0 -55
  292. package/src/components/ui/card.tsx +0 -78
  293. package/src/components/ui/dialog.tsx +0 -94
  294. package/src/components/ui/popover.tsx +0 -33
  295. package/src/components/ui/scroll-area.tsx +0 -54
  296. package/src/components/ui/separator.tsx +0 -28
  297. package/src/components/ui/tabs.tsx +0 -52
  298. package/src/components/ui/toggle-switch.tsx +0 -35
  299. package/src/components/ui/tooltip.tsx +0 -27
  300. package/src/components/workflow/AddEdgeDialog.tsx +0 -217
  301. package/src/components/workflow/AddListDialog.tsx +0 -201
  302. package/src/components/workflow/ChecklistEditor.tsx +0 -239
  303. package/src/components/workflow/CommandPrefixManager.tsx +0 -118
  304. package/src/components/workflow/ConfigSettingsPanel.tsx +0 -189
  305. package/src/components/workflow/DirectionSelector.tsx +0 -133
  306. package/src/components/workflow/DispatchConfigPanel.tsx +0 -180
  307. package/src/components/workflow/EdgeDetailPanel.tsx +0 -236
  308. package/src/components/workflow/EdgePropertyEditor.tsx +0 -251
  309. package/src/components/workflow/EditToolbar.tsx +0 -138
  310. package/src/components/workflow/HookDetailPanel.tsx +0 -250
  311. package/src/components/workflow/HookExecutionLog.tsx +0 -24
  312. package/src/components/workflow/HookSourceModal.tsx +0 -129
  313. package/src/components/workflow/HooksDashboard.tsx +0 -363
  314. package/src/components/workflow/ListPropertyEditor.tsx +0 -251
  315. package/src/components/workflow/MigrationPreviewDialog.tsx +0 -237
  316. package/src/components/workflow/MovementRulesPanel.tsx +0 -188
  317. package/src/components/workflow/NodeDetailPanel.tsx +0 -245
  318. package/src/components/workflow/PresetSelector.tsx +0 -414
  319. package/src/components/workflow/SkillCommandBuilder.tsx +0 -174
  320. package/src/components/workflow/WorkflowEdgeComponent.tsx +0 -145
  321. package/src/components/workflow/WorkflowNode.tsx +0 -147
  322. package/src/components/workflow/graphLayout.ts +0 -186
  323. package/src/components/workflow/mergeHooks.ts +0 -85
  324. package/src/components/workflow/useEditHistory.ts +0 -88
  325. package/src/components/workflow/useWorkflowEditor.ts +0 -262
  326. package/src/components/workflow/validateConfig.ts +0 -70
  327. package/src/hooks/useActiveDispatches.ts +0 -198
  328. package/src/hooks/useBoardSettings.ts +0 -170
  329. package/src/hooks/useCardDisplay.ts +0 -57
  330. package/src/hooks/useCcHooks.ts +0 -24
  331. package/src/hooks/useConfigTree.ts +0 -51
  332. package/src/hooks/useEnforcementRules.ts +0 -46
  333. package/src/hooks/useEvents.ts +0 -59
  334. package/src/hooks/useFileEditor.ts +0 -165
  335. package/src/hooks/useGates.ts +0 -57
  336. package/src/hooks/useIdeaActions.ts +0 -53
  337. package/src/hooks/useKanbanDnd.ts +0 -410
  338. package/src/hooks/useOrbitalConfig.ts +0 -54
  339. package/src/hooks/usePipeline.ts +0 -47
  340. package/src/hooks/usePipelineData.ts +0 -338
  341. package/src/hooks/useReconnect.ts +0 -25
  342. package/src/hooks/useScopeFilters.ts +0 -125
  343. package/src/hooks/useScopeSessions.ts +0 -44
  344. package/src/hooks/useScopes.ts +0 -67
  345. package/src/hooks/useSearch.ts +0 -67
  346. package/src/hooks/useSettings.tsx +0 -187
  347. package/src/hooks/useSocket.ts +0 -25
  348. package/src/hooks/useSourceControl.ts +0 -105
  349. package/src/hooks/useSprintPreflight.ts +0 -55
  350. package/src/hooks/useSprints.ts +0 -154
  351. package/src/hooks/useStatusBarHighlight.ts +0 -18
  352. package/src/hooks/useSwimlaneBoardSettings.ts +0 -104
  353. package/src/hooks/useTheme.ts +0 -9
  354. package/src/hooks/useTransitionReadiness.ts +0 -53
  355. package/src/hooks/useVersion.ts +0 -155
  356. package/src/hooks/useViolations.ts +0 -65
  357. package/src/hooks/useWorkflow.tsx +0 -125
  358. package/src/hooks/useZoomModifier.ts +0 -19
  359. package/src/index.css +0 -797
  360. package/src/layouts/DashboardLayout.tsx +0 -113
  361. package/src/lib/collisionDetection.ts +0 -20
  362. package/src/lib/scope-fields.ts +0 -61
  363. package/src/lib/swimlane.ts +0 -146
  364. package/src/lib/utils.ts +0 -15
  365. package/src/main.tsx +0 -19
  366. package/src/socket.ts +0 -11
  367. package/src/types/index.ts +0 -497
  368. package/src/views/AgentFeed.tsx +0 -339
  369. package/src/views/DeployPipeline.tsx +0 -59
  370. package/src/views/EnforcementView.tsx +0 -378
  371. package/src/views/PrimitivesConfig.tsx +0 -500
  372. package/src/views/QualityGates.tsx +0 -1012
  373. package/src/views/ScopeBoard.tsx +0 -454
  374. package/src/views/SessionTimeline.tsx +0 -516
  375. package/src/views/Settings.tsx +0 -183
  376. package/src/views/SourceControl.tsx +0 -95
  377. package/src/views/WorkflowVisualizer.tsx +0 -382
  378. package/tailwind.config.js +0 -161
  379. package/tsconfig.json +0 -25
  380. package/vite.config.ts +0 -49
@@ -0,0 +1,369 @@
1
+ import fs from 'fs';
2
+ import { Router } from 'express';
3
+ import type { Server } from 'socket.io';
4
+ import { ProjectEmitter } from './project-emitter.js';
5
+ import { createProjectContext } from './project-context.js';
6
+ import type { ProjectContext, ProjectStatus } from './project-context.js';
7
+ import {
8
+ loadGlobalConfig,
9
+ registerProject,
10
+ unregisterProject,
11
+ updateProject,
12
+ findProject,
13
+ } from './global-config.js';
14
+ import type { ProjectRegistration } from './global-config.js';
15
+ import { createScopeRoutes } from './routes/scope-routes.js';
16
+ import { createDataRoutes } from './routes/data-routes.js';
17
+ import { createDispatchRoutes } from './routes/dispatch-routes.js';
18
+ import { createSprintRoutes } from './routes/sprint-routes.js';
19
+ import { createWorkflowRoutes } from './routes/workflow-routes.js';
20
+ import { createConfigRoutes } from './routes/config-routes.js';
21
+ import { createGitRoutes } from './routes/git-routes.js';
22
+ import { createManifestRoutes } from './routes/manifest-routes.js';
23
+ import { createTelemetryRoutes } from './services/telemetry-service.js';
24
+ import { TEMPLATES_DIR } from './init.js';
25
+ import { getPackageVersion } from './utils/package-info.js';
26
+ import { resolveActiveDispatchesForScope } from './utils/dispatch-utils.js';
27
+ import { createLogger } from './utils/logger.js';
28
+
29
+ const log = createLogger('project-manager');
30
+
31
+ // ─── Types ──────────────────────────────────────────────────
32
+
33
+ export interface ProjectSummary {
34
+ id: string;
35
+ name: string;
36
+ path: string;
37
+ color: string;
38
+ status: ProjectStatus;
39
+ enabled: boolean;
40
+ scopeCount: number;
41
+ error?: string;
42
+ }
43
+
44
+ // ─── Manager ────────────────────────────────────────────────
45
+
46
+ export class ProjectManager {
47
+ private contexts = new Map<string, ProjectContext>();
48
+ private routers = new Map<string, Router>();
49
+ private healthCheckInterval: ReturnType<typeof setInterval> | null = null;
50
+
51
+ constructor(private io: Server) {}
52
+
53
+ // ─── Initialization ─────────────────────────────────────
54
+
55
+ /** Initialize all enabled projects from the registry. */
56
+ async initializeAll(): Promise<void> {
57
+ const config = loadGlobalConfig();
58
+ const enabledProjects = config.projects.filter(p => p.enabled);
59
+
60
+ log.info('Initializing projects', { count: enabledProjects.length });
61
+
62
+ for (const reg of enabledProjects) {
63
+ try {
64
+ await this.initializeProject(reg);
65
+ } catch (err) {
66
+ log.error('Failed to initialize project', {
67
+ id: reg.id,
68
+ path: reg.path,
69
+ error: String(err),
70
+ });
71
+ }
72
+ }
73
+
74
+ // Start periodic health checks
75
+ this.healthCheckInterval = setInterval(() => this.checkHealth(), 60_000);
76
+ }
77
+
78
+ /** Initialize a single project from its registration. */
79
+ async initializeProject(reg: ProjectRegistration): Promise<ProjectContext | null> {
80
+ // Verify directory exists
81
+ if (!fs.existsSync(reg.path)) {
82
+ log.warn('Project directory not found, marking offline', { id: reg.id, path: reg.path });
83
+ // Store a placeholder context to track status
84
+ return null;
85
+ }
86
+
87
+ const emitter = new ProjectEmitter(this.io, reg.id);
88
+
89
+ try {
90
+ const ctx = await createProjectContext(reg.id, reg.path, emitter);
91
+ this.contexts.set(reg.id, ctx);
92
+
93
+ // Build and cache the project's router
94
+ const router = this.buildProjectRouter(ctx);
95
+ this.routers.set(reg.id, router);
96
+
97
+ log.info('Project initialized', {
98
+ id: reg.id,
99
+ name: reg.name,
100
+ scopeCount: ctx.scopeService.getAll().length,
101
+ });
102
+
103
+ return ctx;
104
+ } catch (err) {
105
+ log.error('Project initialization failed', { id: reg.id, error: String(err) });
106
+ return null;
107
+ }
108
+ }
109
+
110
+ // ─── Context Access ─────────────────────────────────────
111
+
112
+ /** Get a project context by ID. */
113
+ getContext(id: string): ProjectContext | undefined {
114
+ return this.contexts.get(id);
115
+ }
116
+
117
+ /** Get all active contexts. */
118
+ getAllContexts(): Map<string, ProjectContext> {
119
+ return this.contexts;
120
+ }
121
+
122
+ /** Get the router for a project. */
123
+ getRouter(id: string): Router | undefined {
124
+ return this.routers.get(id);
125
+ }
126
+
127
+ /** Get all project routers. */
128
+ getAllRouters(): Map<string, Router> {
129
+ return this.routers;
130
+ }
131
+
132
+ // ─── Project List ───────────────────────────────────────
133
+
134
+ /** Get summary of all registered projects with live status. */
135
+ getProjectList(options?: { includeWorkflow?: boolean }): (ProjectSummary & { workflow?: unknown })[] {
136
+ const config = loadGlobalConfig();
137
+ return config.projects.map(reg => {
138
+ const ctx = this.contexts.get(reg.id);
139
+ const summary: ProjectSummary & { workflow?: unknown } = {
140
+ id: reg.id,
141
+ name: reg.name,
142
+ path: reg.path,
143
+ color: reg.color,
144
+ status: ctx?.status ?? 'offline' as ProjectStatus,
145
+ enabled: reg.enabled,
146
+ scopeCount: ctx ? ctx.scopeService.getAll().length : 0,
147
+ error: ctx?.error,
148
+ };
149
+ if (options?.includeWorkflow && ctx) {
150
+ summary.workflow = ctx.workflowService.getActive();
151
+ }
152
+ return summary;
153
+ });
154
+ }
155
+
156
+ // ─── Registration ───────────────────────────────────────
157
+
158
+ /** Register and initialize a new project. */
159
+ async addProject(projectRoot: string, options?: { name?: string; color?: string }): Promise<ProjectSummary> {
160
+ const reg = registerProject(projectRoot, options);
161
+ const ctx = await this.initializeProject(reg);
162
+
163
+ // Notify all clients
164
+ this.io.emit('project:registered', {
165
+ id: reg.id,
166
+ name: reg.name,
167
+ path: reg.path,
168
+ color: reg.color,
169
+ });
170
+
171
+ return {
172
+ id: reg.id,
173
+ name: reg.name,
174
+ path: reg.path,
175
+ color: reg.color,
176
+ status: ctx?.status ?? 'offline',
177
+ enabled: reg.enabled,
178
+ scopeCount: ctx ? ctx.scopeService.getAll().length : 0,
179
+ };
180
+ }
181
+
182
+ /** Unregister a project and shut down its context. */
183
+ async removeProject(idOrPath: string): Promise<boolean> {
184
+ // Find the registration before removing
185
+ const config = loadGlobalConfig();
186
+ const reg = config.projects.find(p => p.id === idOrPath || p.path === idOrPath);
187
+ if (!reg) return false;
188
+
189
+ // Shut down context
190
+ await this.shutdownProject(reg.id);
191
+
192
+ // Remove from registry
193
+ unregisterProject(idOrPath);
194
+
195
+ // Notify clients
196
+ this.io.emit('project:unregistered', { id: reg.id });
197
+
198
+ return true;
199
+ }
200
+
201
+ /** Update project metadata (name, color, enabled). */
202
+ async updateProject(
203
+ id: string,
204
+ updates: Partial<Pick<ProjectRegistration, 'name' | 'color' | 'enabled'>>,
205
+ ): Promise<ProjectRegistration | null> {
206
+ const result = updateProject(id, updates);
207
+ if (!result) return null;
208
+
209
+ // If disabling, shut down the context
210
+ if (updates.enabled === false) {
211
+ await this.shutdownProject(id);
212
+ }
213
+
214
+ // If enabling, initialize the context
215
+ if (updates.enabled === true && !this.contexts.has(id)) {
216
+ const reg = findProject(id);
217
+ if (reg) await this.initializeProject(reg);
218
+ }
219
+
220
+ // Notify clients of metadata change
221
+ this.io.emit('project:updated', { id, ...updates });
222
+
223
+ return result;
224
+ }
225
+
226
+ // ─── Lifecycle ──────────────────────────────────────────
227
+
228
+ /** Shut down a single project's context. */
229
+ async shutdownProject(id: string): Promise<void> {
230
+ const ctx = this.contexts.get(id);
231
+ if (ctx) {
232
+ await ctx.shutdown();
233
+ this.contexts.delete(id);
234
+ this.routers.delete(id);
235
+ log.info('Project shut down', { id });
236
+ }
237
+ }
238
+
239
+ /** Shut down all projects and stop health checks. */
240
+ async shutdownAll(): Promise<void> {
241
+ if (this.healthCheckInterval) {
242
+ clearInterval(this.healthCheckInterval);
243
+ this.healthCheckInterval = null;
244
+ }
245
+
246
+ const shutdowns = [...this.contexts.entries()].map(([id, ctx]) =>
247
+ ctx.shutdown().catch(err =>
248
+ log.error('Error shutting down project', { id, error: String(err) }),
249
+ ),
250
+ );
251
+ await Promise.all(shutdowns);
252
+ this.contexts.clear();
253
+ this.routers.clear();
254
+ log.info('All projects shut down');
255
+ }
256
+
257
+ // ─── Health Checks ──────────────────────────────────────
258
+
259
+ /** Periodic health check — detect projects that have gone offline or come back. */
260
+ private async checkHealth(): Promise<void> {
261
+ const config = loadGlobalConfig();
262
+
263
+ for (const reg of config.projects) {
264
+ if (!reg.enabled) continue;
265
+
266
+ const ctx = this.contexts.get(reg.id);
267
+ const dirExists = fs.existsSync(reg.path);
268
+
269
+ if (ctx && !dirExists) {
270
+ // Project went offline
271
+ log.warn('Project directory disappeared', { id: reg.id, path: reg.path });
272
+ await this.shutdownProject(reg.id);
273
+ this.io.emit('project:status:changed', { id: reg.id, status: 'offline' });
274
+ } else if (!ctx && dirExists) {
275
+ // Project came back online (or failed to initialize previously — retry)
276
+ log.info('Attempting to initialize project', { id: reg.id, path: reg.path });
277
+ try {
278
+ await this.initializeProject(reg);
279
+ this.io.emit('project:status:changed', { id: reg.id, status: 'active' });
280
+ } catch (err) {
281
+ log.warn('Project initialization retry failed', { id: reg.id, error: String(err) });
282
+ }
283
+ }
284
+ }
285
+ }
286
+
287
+ // ─── Route Building ─────────────────────────────────────
288
+
289
+ /** Build an Express Router with all per-project routes for a context. */
290
+ /** Build an Express Router with all per-project routes.
291
+ * Note: createVersionRoutes is intentionally omitted — version/update endpoints
292
+ * are global (they update the Orbital Command package), not per-project. */
293
+ private buildProjectRouter(ctx: ProjectContext): Router {
294
+ const router = Router();
295
+ const { db, emitter, config, scopeService, eventService, gateService, deployService,
296
+ sprintService, sprintOrchestrator, batchOrchestrator,
297
+ readinessService, workflowService, workflowEngine,
298
+ gitService, githubService, telemetryService } = ctx;
299
+
300
+ // Scope status inference function (same logic as index.ts)
301
+ function inferScopeStatus(
302
+ eventType: string,
303
+ scopeId: unknown,
304
+ data: Record<string, unknown>,
305
+ ): void {
306
+ if (scopeId == null) return;
307
+ const id = Number(scopeId);
308
+ if (isNaN(id) || id <= 0) return;
309
+ const current = scopeService.getById(id);
310
+ if (current?.status === 'icebox') return;
311
+ const currentStatus = current?.status ?? '';
312
+ const result = workflowEngine.inferStatus(eventType, currentStatus, data);
313
+ if (result === null) return;
314
+ if (typeof result === 'object' && 'dispatchResolution' in result) {
315
+ resolveActiveDispatchesForScope(db, emitter, id, result.resolution as 'completed' | 'failed');
316
+ return;
317
+ }
318
+ scopeService.updateStatus(id, result, 'event');
319
+ }
320
+
321
+ // Project config endpoint
322
+ router.get('/config', (_req, res) => {
323
+ res.json({
324
+ projectName: config.projectName,
325
+ categories: config.categories,
326
+ agents: config.agents,
327
+ serverPort: config.serverPort,
328
+ clientPort: config.clientPort,
329
+ });
330
+ });
331
+
332
+ // Mount all route groups
333
+ router.use(createScopeRoutes({
334
+ db, io: emitter, scopeService, readinessService,
335
+ projectRoot: config.projectRoot, projectName: config.projectName,
336
+ engine: workflowEngine,
337
+ }));
338
+ router.use(createDataRoutes({
339
+ db, io: emitter, eventService, gateService, deployService, gitService,
340
+ engine: workflowEngine, projectRoot: config.projectRoot,
341
+ inferScopeStatus,
342
+ }));
343
+ router.use(createDispatchRoutes({
344
+ db, io: emitter, scopeService,
345
+ projectRoot: config.projectRoot, engine: workflowEngine,
346
+ }));
347
+ router.use(createSprintRoutes({
348
+ sprintService, sprintOrchestrator, batchOrchestrator,
349
+ }));
350
+ router.use(createWorkflowRoutes({
351
+ workflowService, projectRoot: config.projectRoot,
352
+ }));
353
+ router.use(createConfigRoutes({
354
+ projectRoot: config.projectRoot, workflowService, io: emitter,
355
+ }));
356
+ router.use(createGitRoutes({
357
+ gitService, githubService, engine: workflowEngine,
358
+ }));
359
+ router.use(createManifestRoutes({
360
+ projectRoot: config.projectRoot,
361
+ templatesDir: TEMPLATES_DIR,
362
+ packageVersion: getPackageVersion(),
363
+ io: emitter,
364
+ }));
365
+ router.use(createTelemetryRoutes({ telemetryService }));
366
+
367
+ return router;
368
+ }
369
+ }
@@ -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 { errMsg } 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 {
@@ -177,6 +178,3 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
177
178
  return router;
178
179
  }
179
180
 
180
- function errMsg(err: unknown): string {
181
- return err instanceof Error ? err.message : String(err);
182
- }
@@ -3,25 +3,20 @@ 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 { createLogger } from '../utils/logger.js';
13
16
 
14
- const execFileAsync = promisify(execFile);
15
-
16
- // ─── Types & Helpers ────────────────────────────────────────
17
+ const log = createLogger('server');
17
18
 
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
- }
19
+ const execFileAsync = promisify(execFile);
25
20
 
26
21
  const JSON_FIELDS = ['tags', 'blocked_by', 'blocks', 'data', 'discoveries', 'next_steps', 'details'];
27
22
 
@@ -37,100 +32,32 @@ function parseJsonFields(row: Row): Row {
37
32
  return parsed;
38
33
  }
39
34
 
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
- }
47
-
48
- function parseHead(raw: string): BranchHead {
49
- const [sha, date, message] = raw.split('|');
50
- return { sha: sha ?? '', date: date ?? '', message: message ?? '' };
51
- }
52
-
53
35
  // ─── Route Factory ──────────────────────────────────────────
54
36
 
55
37
  interface DataRouteDeps {
56
38
  db: Database.Database;
57
- io: Server;
39
+ io: Emitter;
40
+ eventService: EventService;
58
41
  gateService: GateService;
59
42
  deployService: DeployService;
43
+ gitService: GitService;
60
44
  engine: WorkflowEngine;
61
45
  projectRoot: string;
62
46
  inferScopeStatus: (type: string, scopeId: unknown, data: Record<string, unknown>) => void;
63
47
  }
64
48
 
65
49
  export function createDataRoutes({
66
- db, io, gateService, deployService, engine, projectRoot, inferScopeStatus,
50
+ db, io, eventService, gateService, deployService, gitService, engine, projectRoot, inferScopeStatus,
67
51
  }: DataRouteDeps): Router {
68
52
  const router = Router();
69
53
 
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
54
  // ─── Event Routes ──────────────────────────────────────────
118
55
 
119
56
  router.get('/events', (req, res) => {
120
57
  const limit = Number(req.query.limit) || 50;
121
58
  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[];
59
+ const scopeId = req.query.scope_id ? Number(req.query.scope_id) : undefined;
60
+ const events = eventService.getFiltered({ limit, type, scopeId }) as unknown as Row[];
134
61
  res.json(events.map(parseJsonFields));
135
62
  });
136
63
 
@@ -167,23 +94,9 @@ export function createDataRoutes({
167
94
 
168
95
  router.get('/events/violations/summary', (_req, res) => {
169
96
  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 {
97
+ res.json(eventService.getViolationSummary());
98
+ } catch (err) {
99
+ log.error('Violations summary failed', { error: String(err) });
187
100
  res.status(500).json({ error: 'Failed to query violations summary' });
188
101
  }
189
102
  });
@@ -204,19 +117,7 @@ export function createDataRoutes({
204
117
  }
205
118
  }
206
119
 
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]));
120
+ const { violations: violationMap, overrides: overrideMap } = eventService.getViolationStatsByRule();
220
121
 
221
122
  // Build summary counts
222
123
  const summary = { guards: 0, gates: 0, lifecycle: 0, observers: 0 };
@@ -239,7 +140,8 @@ export function createDataRoutes({
239
140
  }));
240
141
 
241
142
  res.json({ summary, rules, totalEdges: allEdges.length });
242
- } catch {
143
+ } catch (err) {
144
+ log.error('Enforcement rules failed', { error: String(err) });
243
145
  res.status(500).json({ error: 'Failed to query enforcement rules' });
244
146
  }
245
147
  });
@@ -248,14 +150,10 @@ export function createDataRoutes({
248
150
 
249
151
  router.get('/events/violations/trend', (req, res) => {
250
152
  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 {
153
+ const days = Math.max(1, Math.min(365, Number(req.query.days) || 30));
154
+ res.json(eventService.getViolationTrend(days));
155
+ } catch (err) {
156
+ log.error('Violation trends failed', { error: String(err) });
259
157
  res.status(500).json({ error: 'Failed to query violation trends' });
260
158
  }
261
159
  });
@@ -330,8 +228,7 @@ export function createDataRoutes({
330
228
 
331
229
  router.get('/pipeline/drift', async (_req, res) => {
332
230
  try {
333
- const drift = await computeDrift();
334
- res.json(drift);
231
+ res.json(await gitService.getPipelineDrift());
335
232
  } catch (err) {
336
233
  res.status(500).json({ error: 'Failed to compute drift', details: String(err) });
337
234
  }
@@ -339,19 +236,9 @@ export function createDataRoutes({
339
236
 
340
237
  router.get('/deployments/frequency', (_req, res) => {
341
238
  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 {
239
+ res.json(eventService.getDeployFrequency());
240
+ } catch (err) {
241
+ log.error('Deploy frequency query failed', { error: String(err) });
355
242
  res.status(500).json({ error: 'Failed to query deployment frequency' });
356
243
  }
357
244
  });
@@ -417,7 +304,7 @@ export function createDataRoutes({
417
304
  let stats: SessionStats | null = null;
418
305
 
419
306
  if (parsed.claude_session_id && typeof parsed.claude_session_id === 'string') {
420
- const claudeSessions = await getClaudeSessions();
307
+ const claudeSessions = await getClaudeSessions(undefined, projectRoot);
421
308
  const match = claudeSessions.find(s => s.id === parsed.claude_session_id);
422
309
  if (match) {
423
310
  meta = {
@@ -429,7 +316,7 @@ export function createDataRoutes({
429
316
  lastActiveAt: match.lastActiveAt,
430
317
  };
431
318
  }
432
- stats = getSessionStats(parsed.claude_session_id as string);
319
+ stats = getSessionStats(parsed.claude_session_id as string, projectRoot);
433
320
  }
434
321
 
435
322
  if (!content) {