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,319 @@
1
+ /**
2
+ * REST API routes for the manifest-based primitive management system.
3
+ * Exposes status, validation, update, pin/unpin, reset, and diff operations.
4
+ */
5
+ import path from 'path';
6
+ import fs from 'fs';
7
+ import { execFileSync } from 'child_process';
8
+ import { Router } from 'express';
9
+ import { loadManifest, saveManifest, hashFile, computeFileStatus, refreshFileStatuses, summarizeManifest, reverseRemapPath, safeBackupFile, safeCopyTemplate, safeRestoreFile, } from '../manifest.js';
10
+ import { validate } from '../validator.js';
11
+ import { computeUpdatePlan, loadRenameMap } from '../update-planner.js';
12
+ import { runInit, runUpdate } from '../init.js';
13
+ import { needsLegacyMigration, migrateFromLegacy } from '../migrate-legacy.js';
14
+ import { errMsg, isValidRelativePath } from '../utils/route-helpers.js';
15
+ // ─── Route Factory ──────────────────────────────────────────
16
+ export function createManifestRoutes({ projectRoot, templatesDir, packageVersion, io, }) {
17
+ const router = Router();
18
+ const claudeDir = path.join(projectRoot, '.claude');
19
+ // ─── GET /manifest/status — summary overview ────────────
20
+ router.get('/manifest/status', (_req, res) => {
21
+ try {
22
+ const manifest = loadManifest(projectRoot);
23
+ if (!manifest) {
24
+ return res.json({
25
+ success: true,
26
+ data: {
27
+ exists: false,
28
+ packageVersion,
29
+ installedVersion: '',
30
+ needsUpdate: true,
31
+ preset: '',
32
+ files: { total: 0, synced: 0, modified: 0, pinned: 0, userOwned: 0, byType: {} },
33
+ lastUpdated: '',
34
+ },
35
+ });
36
+ }
37
+ refreshFileStatuses(manifest, claudeDir);
38
+ const summary = summarizeManifest(manifest);
39
+ res.json({
40
+ success: true,
41
+ data: {
42
+ exists: true,
43
+ packageVersion,
44
+ installedVersion: manifest.packageVersion,
45
+ needsUpdate: manifest.packageVersion !== packageVersion,
46
+ preset: manifest.preset,
47
+ files: summary,
48
+ lastUpdated: manifest.updatedAt,
49
+ },
50
+ });
51
+ }
52
+ catch (err) {
53
+ res.status(500).json({ success: false, error: errMsg(err) });
54
+ }
55
+ });
56
+ // ─── GET /manifest/files — file inventory ───────────────
57
+ router.get('/manifest/files', (_req, res) => {
58
+ try {
59
+ const manifest = loadManifest(projectRoot);
60
+ if (!manifest) {
61
+ return res.json({ success: true, data: [] });
62
+ }
63
+ refreshFileStatuses(manifest, claudeDir);
64
+ const files = Object.entries(manifest.files).map(([filePath, record]) => ({
65
+ path: filePath,
66
+ origin: record.origin,
67
+ status: record.status,
68
+ templateHash: record.templateHash,
69
+ installedHash: record.installedHash,
70
+ pinnedAt: record.pinnedAt,
71
+ pinnedReason: record.pinnedReason,
72
+ hasPrev: fs.existsSync(path.join(claudeDir, filePath + '.prev')),
73
+ }));
74
+ res.json({ success: true, data: files });
75
+ }
76
+ catch (err) {
77
+ res.status(500).json({ success: false, error: errMsg(err) });
78
+ }
79
+ });
80
+ // ─── GET /manifest/validate — run validation ────────────
81
+ router.get('/manifest/validate', (_req, res) => {
82
+ try {
83
+ const report = validate(projectRoot, packageVersion);
84
+ res.json({ success: true, data: report });
85
+ }
86
+ catch (err) {
87
+ res.status(500).json({ success: false, error: errMsg(err) });
88
+ }
89
+ });
90
+ // ─── POST /manifest/init — initialize manifest ───────────
91
+ router.post('/manifest/init', (_req, res) => {
92
+ try {
93
+ // If manifest already exists, just return success
94
+ if (loadManifest(projectRoot)) {
95
+ return res.json({ success: true, message: 'Already initialized' });
96
+ }
97
+ // If legacy install exists, migrate it
98
+ if (needsLegacyMigration(projectRoot)) {
99
+ migrateFromLegacy(projectRoot, templatesDir, packageVersion);
100
+ io.emit('manifest:changed', { action: 'initialized' });
101
+ return res.json({ success: true });
102
+ }
103
+ // No existing install at all — run full init
104
+ runInit(projectRoot, { force: false });
105
+ io.emit('manifest:changed', { action: 'initialized' });
106
+ res.json({ success: true });
107
+ }
108
+ catch (err) {
109
+ res.status(500).json({ success: false, error: errMsg(err) });
110
+ }
111
+ });
112
+ // ─── POST /manifest/update — run update or dry-run ──────
113
+ router.post('/manifest/update', (req, res) => {
114
+ const { dryRun = true } = req.body;
115
+ try {
116
+ // Ensure manifest exists (migrate legacy if needed)
117
+ let manifest = loadManifest(projectRoot);
118
+ if (!manifest && needsLegacyMigration(projectRoot)) {
119
+ migrateFromLegacy(projectRoot, templatesDir, packageVersion);
120
+ manifest = loadManifest(projectRoot);
121
+ }
122
+ if (!manifest) {
123
+ return res.status(400).json({ success: false, error: 'No manifest. Run orbital first.' });
124
+ }
125
+ if (dryRun) {
126
+ refreshFileStatuses(manifest, claudeDir);
127
+ const renameMap = loadRenameMap(templatesDir, manifest.packageVersion, packageVersion);
128
+ const plan = computeUpdatePlan({
129
+ templatesDir,
130
+ claudeDir,
131
+ manifest,
132
+ newVersion: packageVersion,
133
+ renameMap,
134
+ });
135
+ return res.json({ success: true, data: plan });
136
+ }
137
+ // Execute actual update
138
+ runUpdate(projectRoot, { dryRun: false });
139
+ io.emit('manifest:changed', { action: 'updated' });
140
+ res.json({ success: true });
141
+ }
142
+ catch (err) {
143
+ res.status(500).json({ success: false, error: errMsg(err) });
144
+ }
145
+ });
146
+ // ─── POST /manifest/pin — pin a file ───────────────────
147
+ router.post('/manifest/pin', (req, res) => {
148
+ const { file, reason } = req.body;
149
+ if (!file || !isValidRelativePath(file)) {
150
+ return res.status(400).json({ success: false, error: 'Valid file path required' });
151
+ }
152
+ try {
153
+ const manifest = loadManifest(projectRoot);
154
+ if (!manifest)
155
+ return res.status(400).json({ success: false, error: 'No manifest' });
156
+ const record = manifest.files[file];
157
+ if (!record)
158
+ return res.status(404).json({ success: false, error: 'File not tracked' });
159
+ if (record.origin === 'user')
160
+ return res.status(400).json({ success: false, error: 'Cannot pin user-owned file' });
161
+ record.status = 'pinned';
162
+ record.pinnedAt = new Date().toISOString();
163
+ if (reason)
164
+ record.pinnedReason = reason;
165
+ saveManifest(projectRoot, manifest);
166
+ io.emit('manifest:changed', { action: 'pinned', file });
167
+ res.json({ success: true });
168
+ }
169
+ catch (err) {
170
+ res.status(500).json({ success: false, error: errMsg(err) });
171
+ }
172
+ });
173
+ // ─── POST /manifest/unpin — unpin a file ────────────────
174
+ router.post('/manifest/unpin', (req, res) => {
175
+ const { file } = req.body;
176
+ if (!file || !isValidRelativePath(file)) {
177
+ return res.status(400).json({ success: false, error: 'Valid file path required' });
178
+ }
179
+ try {
180
+ const manifest = loadManifest(projectRoot);
181
+ if (!manifest)
182
+ return res.status(400).json({ success: false, error: 'No manifest' });
183
+ const record = manifest.files[file];
184
+ if (!record || record.status !== 'pinned') {
185
+ return res.status(400).json({ success: false, error: 'File is not pinned' });
186
+ }
187
+ // Clear pinned state before recomputing status
188
+ record.status = 'synced';
189
+ delete record.pinnedAt;
190
+ delete record.pinnedReason;
191
+ const absPath = path.join(claudeDir, file);
192
+ if (fs.existsSync(absPath)) {
193
+ const currentHash = hashFile(absPath);
194
+ record.status = computeFileStatus(record, currentHash);
195
+ }
196
+ saveManifest(projectRoot, manifest);
197
+ io.emit('manifest:changed', { action: 'unpinned', file });
198
+ res.json({ success: true });
199
+ }
200
+ catch (err) {
201
+ res.status(500).json({ success: false, error: errMsg(err) });
202
+ }
203
+ });
204
+ // ─── POST /manifest/reset — reset file to template ──────
205
+ router.post('/manifest/reset', (req, res) => {
206
+ const { file } = req.body;
207
+ if (!file || !isValidRelativePath(file)) {
208
+ return res.status(400).json({ success: false, error: 'Valid file path required' });
209
+ }
210
+ try {
211
+ const manifest = loadManifest(projectRoot);
212
+ if (!manifest)
213
+ return res.status(400).json({ success: false, error: 'No manifest' });
214
+ const record = manifest.files[file];
215
+ if (!record || record.origin !== 'template') {
216
+ return res.status(400).json({ success: false, error: 'Not a template file' });
217
+ }
218
+ // Resolve template source path
219
+ const templateRelPath = reverseRemapPath(file);
220
+ const templatePath = path.join(templatesDir, templateRelPath);
221
+ if (!fs.existsSync(templatePath)) {
222
+ return res.status(404).json({ success: false, error: 'Template file not found' });
223
+ }
224
+ const localPath = path.join(claudeDir, file);
225
+ // Back up current version so user can revert (symlink-safe)
226
+ safeBackupFile(localPath);
227
+ // Copy template to destination (skips if symlink)
228
+ safeCopyTemplate(templatePath, localPath);
229
+ const newHash = hashFile(localPath);
230
+ record.status = 'synced';
231
+ record.templateHash = newHash;
232
+ record.installedHash = newHash;
233
+ delete record.pinnedAt;
234
+ delete record.pinnedReason;
235
+ saveManifest(projectRoot, manifest);
236
+ io.emit('manifest:changed', { action: 'reset', file });
237
+ res.json({ success: true });
238
+ }
239
+ catch (err) {
240
+ res.status(500).json({ success: false, error: errMsg(err) });
241
+ }
242
+ });
243
+ // ─── POST /manifest/revert — restore file from .prev backup ──
244
+ router.post('/manifest/revert', (req, res) => {
245
+ const { file } = req.body;
246
+ if (!file || !isValidRelativePath(file)) {
247
+ return res.status(400).json({ success: false, error: 'Valid file path required' });
248
+ }
249
+ try {
250
+ const manifest = loadManifest(projectRoot);
251
+ if (!manifest)
252
+ return res.status(400).json({ success: false, error: 'No manifest' });
253
+ const record = manifest.files[file];
254
+ if (!record)
255
+ return res.status(404).json({ success: false, error: 'File not tracked' });
256
+ const localPath = path.join(claudeDir, file);
257
+ if (!safeRestoreFile(localPath)) {
258
+ return res.status(404).json({ success: false, error: 'No previous version available' });
259
+ }
260
+ // Recompute status — file may now be a symlink or regular file
261
+ if (fs.existsSync(localPath)) {
262
+ const stat = fs.lstatSync(localPath);
263
+ if (stat.isSymbolicLink()) {
264
+ record.status = 'synced'; // restored symlink points at template
265
+ }
266
+ else {
267
+ const currentHash = hashFile(localPath);
268
+ record.installedHash = currentHash;
269
+ record.status = computeFileStatus(record, currentHash);
270
+ }
271
+ }
272
+ else {
273
+ record.status = 'missing';
274
+ }
275
+ saveManifest(projectRoot, manifest);
276
+ io.emit('manifest:changed', { action: 'reverted', file });
277
+ res.json({ success: true });
278
+ }
279
+ catch (err) {
280
+ res.status(500).json({ success: false, error: errMsg(err) });
281
+ }
282
+ });
283
+ // ─── GET /manifest/diff — diff template vs local ─────────
284
+ router.get('/manifest/diff', (req, res) => {
285
+ const file = req.query.file;
286
+ if (!file || !isValidRelativePath(file)) {
287
+ return res.status(400).json({ success: false, error: 'Valid file path required' });
288
+ }
289
+ try {
290
+ const templateRelPath = reverseRemapPath(file);
291
+ const rawTemplatePath = path.join(templatesDir, templateRelPath);
292
+ // Resolve symlinks so git diff compares file content, not symlink metadata
293
+ const templatePath = fs.existsSync(rawTemplatePath) ? fs.realpathSync(rawTemplatePath) : rawTemplatePath;
294
+ const localPath = path.join(claudeDir, file);
295
+ if (!fs.existsSync(templatePath)) {
296
+ return res.status(404).json({ success: false, error: 'Template file not found' });
297
+ }
298
+ if (!fs.existsSync(localPath)) {
299
+ return res.status(404).json({ success: false, error: 'Local file not found' });
300
+ }
301
+ let diff = '';
302
+ try {
303
+ diff = execFileSync('git', ['diff', '--no-index', '--', templatePath, localPath], {
304
+ encoding: 'utf-8',
305
+ });
306
+ }
307
+ catch (e) {
308
+ // git diff exits 1 when files differ
309
+ const err = e;
310
+ diff = err.stdout || 'Files differ';
311
+ }
312
+ res.json({ success: true, data: { diff } });
313
+ }
314
+ catch (err) {
315
+ res.status(500).json({ success: false, error: errMsg(err) });
316
+ }
317
+ });
318
+ return router;
319
+ }
@@ -1,11 +1,14 @@
1
1
  import { Router } from 'express';
2
2
  import { spawn } from 'child_process';
3
- import { launchInTerminal, escapeForAnsiC, buildSessionName, snapshotSessionPids, discoverNewSession, renameSession } from '../utils/terminal-launcher.js';
3
+ import { launchInTerminal, escapeForAnsiC, shellQuote, buildSessionName, snapshotSessionPids, discoverNewSession, renameSession } from '../utils/terminal-launcher.js';
4
4
  import { resolveDispatchEvent, linkPidToDispatch } from '../utils/dispatch-utils.js';
5
- import { getConfig } from '../config.js';
5
+ import { buildClaudeFlags, buildEnvVarPrefix } from '../utils/flag-builder.js';
6
6
  import { createLogger } from '../utils/logger.js';
7
7
  const log = createLogger('dispatch');
8
- export function createScopeRoutes({ db, io, scopeService, readinessService, projectRoot, engine }) {
8
+ function isValidSlug(slug) {
9
+ return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(slug) && slug.length <= 80;
10
+ }
11
+ export function createScopeRoutes({ db, io, scopeService, readinessService, projectRoot, projectName, engine, config }) {
9
12
  const router = Router();
10
13
  // ─── Scope CRUD ──────────────────────────────────────────
11
14
  router.get('/scopes', (_req, res) => {
@@ -45,7 +48,7 @@ export function createScopeRoutes({ db, io, scopeService, readinessService, proj
45
48
  });
46
49
  router.patch('/scopes/:id', (req, res) => {
47
50
  const id = Number(req.params.id);
48
- const result = scopeService.updateScopeFrontmatter(id, req.body);
51
+ const result = scopeService.updateFields(id, req.body);
49
52
  if (!result.ok) {
50
53
  const code = result.code === 'NOT_FOUND' ? 404 : 400;
51
54
  res.status(code).json({ error: result.error, code: result.code });
@@ -64,41 +67,53 @@ export function createScopeRoutes({ db, io, scopeService, readinessService, proj
64
67
  const idea = scopeService.createIdeaFile(title.trim(), (description ?? '').trim());
65
68
  res.status(201).json(idea);
66
69
  });
67
- router.patch('/ideas/:id', (req, res) => {
68
- const id = Number(req.params.id);
70
+ router.patch('/ideas/:slug', (req, res) => {
71
+ const { slug } = req.params;
72
+ if (!isValidSlug(slug)) {
73
+ res.status(400).json({ error: 'Invalid slug' });
74
+ return;
75
+ }
69
76
  const { title, description } = req.body;
70
77
  if (!title?.trim()) {
71
78
  res.status(400).json({ error: 'title is required' });
72
79
  return;
73
80
  }
74
- const updated = scopeService.updateIdeaFile(id, title.trim(), (description ?? '').trim());
81
+ const updated = scopeService.updateIdeaFile(slug, title.trim(), (description ?? '').trim());
75
82
  if (!updated) {
76
83
  res.status(404).json({ error: 'Idea not found' });
77
84
  return;
78
85
  }
79
86
  res.json({ ok: true });
80
87
  });
81
- router.delete('/ideas/:id', (req, res) => {
82
- const id = Number(req.params.id);
83
- const deleted = scopeService.deleteIdeaFile(id);
88
+ router.delete('/ideas/:slug', (req, res) => {
89
+ const { slug } = req.params;
90
+ if (!isValidSlug(slug)) {
91
+ res.status(400).json({ error: 'Invalid slug' });
92
+ return;
93
+ }
94
+ const deleted = scopeService.deleteIdeaFile(slug);
84
95
  if (!deleted) {
85
96
  res.status(404).json({ error: 'Idea not found' });
86
97
  return;
87
98
  }
88
99
  res.json({ ok: true });
89
100
  });
90
- router.post('/ideas/:id/promote', async (req, res) => {
91
- const ideaId = Number(req.params.id);
92
- const result = scopeService.promoteIdea(ideaId);
101
+ router.post('/ideas/:slug/promote', async (req, res) => {
102
+ const { slug } = req.params;
103
+ if (!isValidSlug(slug)) {
104
+ res.status(400).json({ error: 'Invalid slug' });
105
+ return;
106
+ }
107
+ const entryPoint = engine.getEntryPoint();
108
+ const targets = engine.getValidTargets(entryPoint.id);
109
+ const promoteTarget = targets[0] ?? 'planning';
110
+ const result = scopeService.promoteIdea(slug, promoteTarget);
93
111
  if (!result) {
94
112
  res.status(404).json({ error: 'Idea not found' });
95
113
  return;
96
114
  }
97
115
  const scopeId = result.id;
98
116
  // Read command from workflow edge config (user-overridable)
99
- const entryPoint = engine.getEntryPoint();
100
- const targets = engine.getValidTargets(entryPoint.id);
101
- const promoteTarget = targets[0] ?? 'planning';
102
117
  const edge = engine.findEdge(entryPoint.id, promoteTarget);
103
118
  const edgeCommand = edge ? engine.buildCommand(edge, scopeId) : null;
104
119
  const command = edgeCommand ?? `/scope-create ${String(scopeId).padStart(3, '0')}`;
@@ -117,7 +132,9 @@ export function createScopeRoutes({ db, io, scopeService, readinessService, proj
117
132
  timestamp: new Date().toISOString(),
118
133
  });
119
134
  const escaped = escapeForAnsiC(command);
120
- const fullCmd = `cd '${projectRoot}' && claude --dangerously-skip-permissions $'${escaped}'`;
135
+ const flagsStr = buildClaudeFlags(config.claude.dispatchFlags);
136
+ const envPrefix = buildEnvVarPrefix(config.dispatch.envVars);
137
+ const fullCmd = `cd '${shellQuote(projectRoot)}' && ${envPrefix}ORBITAL_DISPATCH_ID='${shellQuote(eventId)}' claude ${flagsStr} $'${escaped}'`;
121
138
  const promoteSessionName = buildSessionName({ scopeId, title: result.title, command });
122
139
  const promoteBeforePids = snapshotSessionPids(projectRoot);
123
140
  try {
@@ -146,17 +163,14 @@ export function createScopeRoutes({ db, io, scopeService, readinessService, proj
146
163
  return;
147
164
  }
148
165
  surpriseInProgress = true;
149
- const nextIdStart = scopeService.getNextIceboxId();
150
166
  const today = new Date().toISOString().split('T')[0];
151
- const idRange = Array.from({ length: 5 }, (_, i) => nextIdStart + i);
152
- const prompt = `You are analyzing the ${getConfig().projectName} codebase to suggest feature ideas. Your ONLY job is to create markdown files.
167
+ const prompt = `You are analyzing the ${projectName} codebase to suggest feature ideas. Your ONLY job is to create markdown files.
153
168
 
154
169
  Create exactly 3 idea files in the scopes/icebox/ directory. Each file must use this EXACT format:
155
170
 
156
- File: scopes/icebox/{ID}-{kebab-slug}.md
171
+ File: scopes/icebox/{kebab-slug}.md
157
172
 
158
173
  ---
159
- id: {ID}
160
174
  title: "{title}"
161
175
  status: icebox
162
176
  ghost: true
@@ -169,13 +183,12 @@ tags: []
169
183
 
170
184
  {2-3 sentence description of the feature, what problem it solves, and a rough approach.}
171
185
 
172
- Use these IDs: ${idRange[0]}, ${idRange[1]}, ${idRange[2]}
173
-
174
186
  Rules:
175
187
  - Focus on practical improvements: performance, UX, security, developer experience, monitoring, or reliability
176
188
  - Be specific and actionable — not vague architectural rewrites
177
189
  - Keep descriptions concise (2-3 sentences max)
178
- - Filenames must be {ID}-{kebab-case-slug}.md
190
+ - Filenames must be {kebab-case-slug}.md (NO numeric prefix)
191
+ - Do NOT include an id field in frontmatter
179
192
  - The ghost: true field is required in frontmatter
180
193
  - Do NOT create any other files or make any other changes`;
181
194
  const child = spawn('claude', ['-p', prompt, '--output-format', 'text'], {
@@ -199,9 +212,13 @@ Rules:
199
212
  });
200
213
  res.json({ ok: true, status: 'generating' });
201
214
  });
202
- router.post('/ideas/:id/approve', (req, res) => {
203
- const id = Number(req.params.id);
204
- const approved = scopeService.approveGhostIdea(id);
215
+ router.post('/ideas/:slug/approve', (req, res) => {
216
+ const { slug } = req.params;
217
+ if (!isValidSlug(slug)) {
218
+ res.status(400).json({ error: 'Invalid slug' });
219
+ return;
220
+ }
221
+ const approved = scopeService.approveGhostIdea(slug);
205
222
  if (!approved) {
206
223
  res.status(404).json({ error: 'Ghost idea not found' });
207
224
  return;
@@ -0,0 +1,134 @@
1
+ import { Router } from 'express';
2
+ import { isValidRelativePath } from '../utils/route-helpers.js';
3
+ export function createSyncRoutes({ syncService, projectManager }) {
4
+ const router = Router();
5
+ // ─── Sync State ─────────────────────────────────────────
6
+ /** GET /sync/state/:projectId — sync state for a specific project */
7
+ router.get('/sync/state/:projectId', (req, res) => {
8
+ const ctx = projectManager.getContext(req.params.projectId);
9
+ if (!ctx)
10
+ return res.status(404).json({ error: 'Project not found' });
11
+ const report = syncService.computeSyncState(ctx.id, ctx.config.projectRoot);
12
+ res.json(report);
13
+ });
14
+ /** GET /sync/global-state — matrix view across all projects */
15
+ router.get('/sync/global-state', (_req, res) => {
16
+ const report = syncService.computeGlobalSyncState();
17
+ res.json(report);
18
+ });
19
+ // ─── Override Operations ────────────────────────────────
20
+ /** POST /sync/override — create an override for a file in a project */
21
+ router.post('/sync/override', (req, res) => {
22
+ const { projectId, relativePath, reason } = req.body;
23
+ if (!projectId || !relativePath) {
24
+ return res.status(400).json({ error: 'projectId and relativePath required' });
25
+ }
26
+ if (!isValidRelativePath(relativePath)) {
27
+ return res.status(400).json({ error: 'Invalid relativePath' });
28
+ }
29
+ const ctx = projectManager.getContext(projectId);
30
+ if (!ctx)
31
+ return res.status(404).json({ error: 'Project not found' });
32
+ syncService.createOverride(ctx.config.projectRoot, relativePath, reason);
33
+ res.json({ success: true });
34
+ });
35
+ /** POST /sync/revert — revert an override back to global */
36
+ router.post('/sync/revert', (req, res) => {
37
+ const { projectId, relativePath } = req.body;
38
+ if (!projectId || !relativePath) {
39
+ return res.status(400).json({ error: 'projectId and relativePath required' });
40
+ }
41
+ if (!isValidRelativePath(relativePath)) {
42
+ return res.status(400).json({ error: 'Invalid relativePath' });
43
+ }
44
+ const ctx = projectManager.getContext(projectId);
45
+ if (!ctx)
46
+ return res.status(404).json({ error: 'Project not found' });
47
+ syncService.revertOverride(ctx.config.projectRoot, relativePath);
48
+ res.json({ success: true });
49
+ });
50
+ /** POST /sync/promote — promote a project override to global */
51
+ router.post('/sync/promote', (req, res) => {
52
+ const { projectId, relativePath } = req.body;
53
+ if (!projectId || !relativePath) {
54
+ return res.status(400).json({ error: 'projectId and relativePath required' });
55
+ }
56
+ if (!isValidRelativePath(relativePath)) {
57
+ return res.status(400).json({ error: 'Invalid relativePath' });
58
+ }
59
+ const ctx = projectManager.getContext(projectId);
60
+ if (!ctx)
61
+ return res.status(404).json({ error: 'Project not found' });
62
+ const result = syncService.promoteOverride(ctx.config.projectRoot, relativePath);
63
+ res.json({ success: true, ...result });
64
+ });
65
+ /** POST /sync/resolve-drift — resolve a drifted file */
66
+ router.post('/sync/resolve-drift', (req, res) => {
67
+ const { projectId, relativePath, resolution } = req.body;
68
+ if (!projectId || !relativePath || !resolution) {
69
+ return res.status(400).json({ error: 'projectId, relativePath, and resolution required' });
70
+ }
71
+ if (!isValidRelativePath(relativePath)) {
72
+ return res.status(400).json({ error: 'Invalid relativePath' });
73
+ }
74
+ const ctx = projectManager.getContext(projectId);
75
+ if (!ctx)
76
+ return res.status(404).json({ error: 'Project not found' });
77
+ syncService.resolveDrift(ctx.config.projectRoot, relativePath, resolution);
78
+ res.json({ success: true });
79
+ });
80
+ // ─── Impact Preview ─────────────────────────────────────
81
+ /** GET /sync/impact?path=<relativePath> — preview impact of a global change */
82
+ router.get('/sync/impact', (req, res) => {
83
+ const relativePath = req.query.path;
84
+ if (!relativePath) {
85
+ return res.status(400).json({ error: 'path query parameter required' });
86
+ }
87
+ if (!isValidRelativePath(relativePath)) {
88
+ return res.status(400).json({ error: 'Invalid path' });
89
+ }
90
+ const preview = syncService.getImpactPreview(relativePath);
91
+ res.json(preview);
92
+ });
93
+ // ─── Project Management ─────────────────────────────────
94
+ /** GET /projects — list all registered projects */
95
+ router.get('/projects', (req, res) => {
96
+ const include = req.query.include;
97
+ res.json(projectManager.getProjectList({
98
+ includeWorkflow: include?.includes('workflow'),
99
+ }));
100
+ });
101
+ /** POST /projects — register a new project */
102
+ router.post('/projects', async (req, res) => {
103
+ const { path: projectPath, name, color } = req.body;
104
+ if (!projectPath) {
105
+ return res.status(400).json({ error: 'path required' });
106
+ }
107
+ try {
108
+ const summary = await projectManager.addProject(projectPath, { name, color });
109
+ res.status(201).json(summary);
110
+ }
111
+ catch (err) {
112
+ res.status(500).json({ error: String(err) });
113
+ }
114
+ });
115
+ /** DELETE /projects/:id — unregister a project */
116
+ router.delete('/projects/:id', async (req, res) => {
117
+ const removed = await projectManager.removeProject(req.params.id);
118
+ if (!removed)
119
+ return res.status(404).json({ error: 'Project not found' });
120
+ res.json({ success: true });
121
+ });
122
+ /** PATCH /projects/:id — update project metadata */
123
+ router.patch('/projects/:id', async (req, res) => {
124
+ const { name, color, enabled } = req.body;
125
+ if (name !== undefined && !name.trim()) {
126
+ return res.status(400).json({ error: 'Name cannot be empty' });
127
+ }
128
+ const updated = await projectManager.updateProject(req.params.id, { name, color, enabled });
129
+ if (!updated)
130
+ return res.status(404).json({ error: 'Project not found' });
131
+ res.json(updated);
132
+ });
133
+ return router;
134
+ }
@@ -3,24 +3,10 @@ import { execFile } from 'child_process';
3
3
  import { promisify } from 'util';
4
4
  import path from 'path';
5
5
  import fs from 'fs';
6
- import { fileURLToPath } from 'url';
7
6
  import { createLogger } from '../utils/logger.js';
7
+ import { getOrbitalRoot } from '../utils/package-info.js';
8
8
  const log = createLogger('version');
9
9
  const execFileAsync = promisify(execFile);
10
- /** Resolve the root directory of the orbital-command package itself. */
11
- function getOrbitalRoot() {
12
- const __selfDir = path.dirname(fileURLToPath(import.meta.url));
13
- // Walk up until we find package.json (handles both dev and compiled paths)
14
- let dir = __selfDir;
15
- for (let i = 0; i < 6; i++) {
16
- if (fs.existsSync(path.join(dir, 'package.json'))) {
17
- return dir;
18
- }
19
- dir = path.dirname(dir);
20
- }
21
- // Fallback: assume dev layout (server/routes/ → 2 levels up)
22
- return path.resolve(__selfDir, '../..');
23
- }
24
10
  async function git(args, cwd, timeoutMs = 15_000) {
25
11
  const { stdout } = await execFileAsync('git', args, { cwd, timeout: timeoutMs });
26
12
  return stdout.trim();
@@ -2,6 +2,7 @@ import { Router } from 'express';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { parseCcHooks } from '../utils/cc-hooks-parser.js';
5
+ import { errMsg } from '../utils/route-helpers.js';
5
6
  export function createWorkflowRoutes({ workflowService, projectRoot }) {
6
7
  const router = Router();
7
8
  // GET /workflow — returns active config
@@ -121,6 +122,10 @@ export function createWorkflowRoutes({ workflowService, projectRoot }) {
121
122
  return;
122
123
  }
123
124
  const filePath = path.resolve(projectRoot, hook.target);
125
+ if (!filePath.startsWith(projectRoot + path.sep) && filePath !== projectRoot) {
126
+ res.status(400).json({ success: false, error: 'Path outside project root' });
127
+ return;
128
+ }
124
129
  const content = await readFile(filePath, 'utf-8');
125
130
  const lineCount = content.split('\n').length;
126
131
  res.json({ success: true, data: { hookId, filePath: hook.target, content, lineCount } });
@@ -155,6 +160,10 @@ export function createWorkflowRoutes({ workflowService, projectRoot }) {
155
160
  return;
156
161
  }
157
162
  const filePath = path.resolve(projectRoot, hookPath);
163
+ if (!filePath.startsWith(projectRoot + path.sep) && filePath !== projectRoot) {
164
+ res.status(400).json({ success: false, error: 'Path outside project root' });
165
+ return;
166
+ }
158
167
  const content = await readFile(filePath, 'utf-8');
159
168
  const lineCount = content.split('\n').length;
160
169
  res.json({ success: true, data: { filePath: hookPath, content, lineCount } });
@@ -180,6 +189,3 @@ export function createWorkflowRoutes({ workflowService, projectRoot }) {
180
189
  });
181
190
  return router;
182
191
  }
183
- function errMsg(err) {
184
- return err instanceof Error ? err.message : String(err);
185
- }