orbital-command 0.2.0 → 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 +640 -37
  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 -38
@@ -0,0 +1,119 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { WorkflowNormalizer, PHASE_COLUMNS, allEnginesMatch } from './workflow-normalizer.js';
3
+ import { WorkflowEngine } from './workflow-engine.js';
4
+ import { DEFAULT_CONFIG, MINIMAL_CONFIG } from './__fixtures__/workflow-configs.js';
5
+
6
+ describe('PHASE_COLUMNS', () => {
7
+ it('has 4 entries in correct order', () => {
8
+ expect(PHASE_COLUMNS).toHaveLength(4);
9
+ expect(PHASE_COLUMNS.map(c => c.phase)).toEqual(['queued', 'active', 'review', 'shipped']);
10
+ expect(PHASE_COLUMNS.map(c => c.order)).toEqual([0, 1, 2, 3]);
11
+ });
12
+ });
13
+
14
+ describe('WorkflowNormalizer', () => {
15
+ const engine = new WorkflowEngine(DEFAULT_CONFIG);
16
+ const normalizer = new WorkflowNormalizer(engine);
17
+
18
+ describe('phase inference with default config', () => {
19
+ it('entry point (icebox) maps to queued', () => {
20
+ expect(normalizer.getPhase('icebox')).toBe('queued');
21
+ });
22
+
23
+ it('planning group list maps to queued', () => {
24
+ expect(normalizer.getPhase('planning')).toBe('queued');
25
+ });
26
+
27
+ it('backlog (planning group with reviewScope session) maps to queued', () => {
28
+ expect(normalizer.getPhase('backlog')).toBe('queued');
29
+ });
30
+
31
+ it('implementing (sessionKey includes "implement") maps to active', () => {
32
+ expect(normalizer.getPhase('implementing')).toBe('active');
33
+ });
34
+
35
+ it('review (sessionKey includes "review") maps to review', () => {
36
+ expect(normalizer.getPhase('review')).toBe('review');
37
+ });
38
+
39
+ it('completed (sessionKey "commit") maps to review', () => {
40
+ expect(normalizer.getPhase('completed')).toBe('review');
41
+ });
42
+
43
+ it('main (terminal status with gitBranch) maps to shipped', () => {
44
+ expect(normalizer.getPhase('main')).toBe('shipped');
45
+ });
46
+ });
47
+
48
+ describe('getPhase()', () => {
49
+ it('returns queued for unknown list ID (default)', () => {
50
+ expect(normalizer.getPhase('nonexistent')).toBe('queued');
51
+ });
52
+ });
53
+
54
+ describe('getListsForPhase()', () => {
55
+ it('returns all lists for a given phase', () => {
56
+ const queued = normalizer.getListsForPhase('queued');
57
+ expect(queued.length).toBeGreaterThan(0);
58
+ for (const list of queued) {
59
+ expect(normalizer.getPhase(list.id)).toBe('queued');
60
+ }
61
+ });
62
+
63
+ it('shipped phase includes main', () => {
64
+ const shipped = normalizer.getListsForPhase('shipped');
65
+ expect(shipped.some(l => l.id === 'main')).toBe(true);
66
+ });
67
+ });
68
+
69
+ describe('getPhaseMap()', () => {
70
+ it('returns a map covering all lists', () => {
71
+ const map = normalizer.getPhaseMap();
72
+ const lists = engine.getLists();
73
+ for (const list of lists) {
74
+ expect(map.has(list.id)).toBe(true);
75
+ }
76
+ });
77
+ });
78
+
79
+ describe('resolveNormalizedTransition()', () => {
80
+ it('returns edges from a list to a target phase', () => {
81
+ // icebox → planning is forward, planning maps to queued
82
+ const edges = normalizer.resolveNormalizedTransition('icebox', 'queued');
83
+ expect(edges.length).toBeGreaterThan(0);
84
+ expect(edges[0].from).toBe('icebox');
85
+ });
86
+
87
+ it('returns empty when no edges match', () => {
88
+ // icebox has no edge to any shipped-phase list
89
+ const edges = normalizer.resolveNormalizedTransition('icebox', 'shipped');
90
+ expect(edges).toEqual([]);
91
+ });
92
+ });
93
+ });
94
+
95
+ describe('allEnginesMatch()', () => {
96
+ it('returns true for empty array', () => {
97
+ expect(allEnginesMatch([])).toBe(true);
98
+ });
99
+
100
+ it('returns true for single engine', () => {
101
+ expect(allEnginesMatch([new WorkflowEngine(DEFAULT_CONFIG)])).toBe(true);
102
+ });
103
+
104
+ it('returns true when all engines have same lists', () => {
105
+ const engines = [
106
+ new WorkflowEngine(DEFAULT_CONFIG),
107
+ new WorkflowEngine(DEFAULT_CONFIG),
108
+ ];
109
+ expect(allEnginesMatch(engines)).toBe(true);
110
+ });
111
+
112
+ it('returns false when engines differ', () => {
113
+ const engines = [
114
+ new WorkflowEngine(DEFAULT_CONFIG),
115
+ new WorkflowEngine(MINIMAL_CONFIG),
116
+ ];
117
+ expect(allEnginesMatch(engines)).toBe(false);
118
+ });
119
+ });
@@ -0,0 +1,118 @@
1
+ import type { Phase, WorkflowList, WorkflowEdge } from './workflow-config.js';
2
+ export type { Phase } from './workflow-config.js';
3
+ import type { WorkflowEngine } from './workflow-engine.js';
4
+
5
+ // ─── Phase Mapping ──────────────────────────────────────────
6
+
7
+ /** Infer a list's semantic phase from its properties. */
8
+ function inferPhase(list: WorkflowList, terminalStatuses: string[]): Phase {
9
+ // Explicit phase takes priority
10
+ if (list.phase) return list.phase;
11
+
12
+ // Terminal statuses or lists with a git branch → shipped
13
+ if (terminalStatuses.includes(list.id) || list.gitBranch) return 'shipped';
14
+
15
+ // Entry points and planning group → queued
16
+ if (list.isEntryPoint) return 'queued';
17
+ if (list.group === 'planning') return 'queued';
18
+
19
+ // Session key inference
20
+ if (list.sessionKey) {
21
+ if (list.sessionKey.toLowerCase().includes('implement')) return 'active';
22
+ if (list.sessionKey.toLowerCase().includes('review') ||
23
+ list.sessionKey.toLowerCase().includes('gate') ||
24
+ list.sessionKey.toLowerCase().includes('commit') ||
25
+ list.sessionKey.toLowerCase().includes('verify')) return 'review';
26
+ if (list.sessionKey.toLowerCase().includes('push') ||
27
+ list.sessionKey.toLowerCase().includes('deploy')) return 'shipped';
28
+ if (list.sessionKey.toLowerCase().includes('create') ||
29
+ list.sessionKey.toLowerCase().includes('scope')) return 'queued';
30
+ }
31
+
32
+ // Group-based inference
33
+ if (list.group === 'development') {
34
+ // Lists earlier in development → active, later → review
35
+ return list.order <= 3 ? 'active' : 'review';
36
+ }
37
+ if (list.group === 'active') return 'active';
38
+ if (list.group?.startsWith('deploy')) return 'shipped';
39
+ if (list.group === 'main') return 'shipped';
40
+
41
+ // Default: use order-based heuristic
42
+ return 'queued';
43
+ }
44
+
45
+ // ─── Normalizer ─────────────────────────────────────────────
46
+
47
+ /** Phase column definition for the All Projects board. */
48
+ export interface PhaseColumn {
49
+ phase: Phase;
50
+ label: string;
51
+ order: number;
52
+ }
53
+
54
+ /** The four normalized columns. */
55
+ export const PHASE_COLUMNS: readonly PhaseColumn[] = [
56
+ { phase: 'queued', label: 'Queued', order: 0 },
57
+ { phase: 'active', label: 'Active', order: 1 },
58
+ { phase: 'review', label: 'Review', order: 2 },
59
+ { phase: 'shipped', label: 'Shipped', order: 3 },
60
+ ] as const;
61
+
62
+ /**
63
+ * Maps workflow lists to semantic phases for the All Projects board.
64
+ *
65
+ * Each WorkflowEngine's lists get mapped to one of four phases.
66
+ * When all projects share the same workflow, the board shows full columns.
67
+ * When workflows differ, the board uses these normalized phases.
68
+ */
69
+ export class WorkflowNormalizer {
70
+ private phaseMap: Map<string, Phase>;
71
+
72
+ constructor(private engine: WorkflowEngine) {
73
+ this.phaseMap = new Map();
74
+ const lists = engine.getLists();
75
+ const terminalStatuses = engine.getConfig().terminalStatuses ?? [];
76
+
77
+ for (const list of lists) {
78
+ this.phaseMap.set(list.id, inferPhase(list, terminalStatuses));
79
+ }
80
+ }
81
+
82
+ /** Get the phase for a list/status ID. */
83
+ getPhase(listId: string): Phase {
84
+ return this.phaseMap.get(listId) ?? 'queued';
85
+ }
86
+
87
+ /** Get all lists that map to a given phase. */
88
+ getListsForPhase(phase: Phase): WorkflowList[] {
89
+ return this.engine.getLists().filter(l => this.getPhase(l.id) === phase);
90
+ }
91
+
92
+ /** Get the full phase map. */
93
+ getPhaseMap(): ReadonlyMap<string, Phase> {
94
+ return this.phaseMap;
95
+ }
96
+
97
+ /**
98
+ * Find valid workflow transitions from `currentListId` whose target
99
+ * maps to `targetPhase`. Used for DnD in the All Projects phase view.
100
+ */
101
+ resolveNormalizedTransition(currentListId: string, targetPhase: Phase): WorkflowEdge[] {
102
+ const config = this.engine.getConfig();
103
+ return config.edges.filter(edge =>
104
+ edge.from === currentListId && this.getPhase(edge.to) === targetPhase
105
+ );
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Check if all engines share the same workflow (same list IDs in same order).
111
+ * When true, the All Projects board can show full columns instead of phases.
112
+ */
113
+ export function allEnginesMatch(engines: WorkflowEngine[]): boolean {
114
+ if (engines.length <= 1) return true;
115
+
116
+ const firstLists = engines[0].getLists().map(l => l.id).join(',');
117
+ return engines.every(e => e.getLists().map(l => l.id).join(',') === firstLists);
118
+ }
@@ -12,7 +12,9 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12
12
  PID="$PPID"
13
13
 
14
14
  # Emit session end event to Orbital dashboard (non-blocking)
15
- SESSION_DATA="{\"pid\":$PID"
15
+ # normal_exit=true tells the server this was a clean shutdown, so dispatches
16
+ # should resolve as "completed" rather than "abandoned".
17
+ SESSION_DATA="{\"pid\":$PID,\"normal_exit\":true"
16
18
  # Include dispatch ID if this session was launched by Orbital dispatch
17
19
  if [ -n "$ORBITAL_DISPATCH_ID" ]; then
18
20
  SESSION_DATA="${SESSION_DATA},\"dispatch_id\":\"$ORBITAL_DISPATCH_ID\""
@@ -69,11 +69,11 @@ if [ -n "$SCOPE_ID" ]; then
69
69
  fi
70
70
  if [ -n "$AGENT" ]; then
71
71
  JQ_ARGS+=(--arg agent "$AGENT")
72
- JQ_EXPR=$(echo "$JQ_EXPR" | sed 's/}$/, agent: $agent}/')
72
+ JQ_EXPR="${JQ_EXPR%\}}, agent: \$agent}"
73
73
  fi
74
74
  if [ -n "$SESSION_ID" ]; then
75
75
  JQ_ARGS+=(--arg session_id "$SESSION_ID")
76
- JQ_EXPR=$(echo "$JQ_EXPR" | sed 's/}$/, session_id: $session_id}/')
76
+ JQ_EXPR="${JQ_EXPR%\}}, session_id: \$session_id}"
77
77
  fi
78
78
 
79
79
  jq -n "${JQ_ARGS[@]}" "$JQ_EXPR" > "$EVENT_FILE"
@@ -50,16 +50,16 @@ else
50
50
  JQ_ARGS=(--arg environment "$ENVIRONMENT" --arg status "$STATUS")
51
51
  JQ_EXPR='{environment: $environment, status: $status}'
52
52
 
53
- [ -n "$COMMIT_SHA" ] && JQ_ARGS+=(--arg commit_sha "$COMMIT_SHA") && JQ_EXPR=$(echo "$JQ_EXPR" | sed 's/}$/, commit_sha: $commit_sha}/')
54
- [ -n "$BRANCH" ] && JQ_ARGS+=(--arg branch "$BRANCH") && JQ_EXPR=$(echo "$JQ_EXPR" | sed 's/}$/, branch: $branch}/')
55
- [ -n "$PR_NUMBER" ] && JQ_ARGS+=(--argjson pr_number "$PR_NUMBER") && JQ_EXPR=$(echo "$JQ_EXPR" | sed 's/}$/, pr_number: $pr_number}/')
53
+ [ -n "$COMMIT_SHA" ] && JQ_ARGS+=(--arg commit_sha "$COMMIT_SHA") && JQ_EXPR="${JQ_EXPR%\}}, commit_sha: \$commit_sha}"
54
+ [ -n "$BRANCH" ] && JQ_ARGS+=(--arg branch "$BRANCH") && JQ_EXPR="${JQ_EXPR%\}}, branch: \$branch}"
55
+ [ -n "$PR_NUMBER" ] && JQ_ARGS+=(--argjson pr_number "$PR_NUMBER") && JQ_EXPR="${JQ_EXPR%\}}, pr_number: \$pr_number}"
56
56
 
57
57
  # Read health check URL from orbital.config.json if configured
58
58
  HEALTH_URL=""
59
59
  if command -v jq >/dev/null 2>&1 && [ -f "$PROJECT_ROOT/.claude/orbital.config.json" ]; then
60
60
  HEALTH_URL=$(jq -r ".healthChecks[\"$ENVIRONMENT\"] // empty" "$PROJECT_ROOT/.claude/orbital.config.json" 2>/dev/null)
61
61
  fi
62
- [ -n "$HEALTH_URL" ] && JQ_ARGS+=(--arg health_check_url "$HEALTH_URL") && JQ_EXPR=$(echo "$JQ_EXPR" | sed 's/}$/, health_check_url: $health_check_url}/')
62
+ [ -n "$HEALTH_URL" ] && JQ_ARGS+=(--arg health_check_url "$HEALTH_URL") && JQ_EXPR="${JQ_EXPR%\}}, health_check_url: \$health_check_url}"
63
63
 
64
64
  PAYLOAD=$(jq -n "${JQ_ARGS[@]}" "$JQ_EXPR")
65
65
 
@@ -25,10 +25,10 @@ SCOPE_ID="${ORBITAL_GATE_SCOPE_ID:-null}"
25
25
  JQ_ARGS=(--arg gate_name "$GATE_NAME" --arg status "$STATUS")
26
26
  JQ_EXPR='{gate_name: $gate_name, status: $status}'
27
27
 
28
- [ "$SCOPE_ID" != "null" ] && JQ_ARGS+=(--argjson scope_id "$SCOPE_ID") && JQ_EXPR=$(echo "$JQ_EXPR" | sed 's/}$/, scope_id: $scope_id}/')
29
- [ "$DURATION_MS" != "null" ] && JQ_ARGS+=(--argjson duration_ms "$DURATION_MS") && JQ_EXPR=$(echo "$JQ_EXPR" | sed 's/}$/, duration_ms: $duration_ms}/')
30
- [ -n "$COMMIT_SHA" ] && JQ_ARGS+=(--arg commit_sha "$COMMIT_SHA") && JQ_EXPR=$(echo "$JQ_EXPR" | sed 's/}$/, commit_sha: $commit_sha}/')
31
- [ -n "$DETAILS" ] && JQ_ARGS+=(--arg details "$DETAILS") && JQ_EXPR=$(echo "$JQ_EXPR" | sed 's/}$/, details: $details}/')
28
+ [ "$SCOPE_ID" != "null" ] && JQ_ARGS+=(--argjson scope_id "$SCOPE_ID") && JQ_EXPR="${JQ_EXPR%\}}, scope_id: \$scope_id}"
29
+ [ "$DURATION_MS" != "null" ] && JQ_ARGS+=(--argjson duration_ms "$DURATION_MS") && JQ_EXPR="${JQ_EXPR%\}}, duration_ms: \$duration_ms}"
30
+ [ -n "$COMMIT_SHA" ] && JQ_ARGS+=(--arg commit_sha "$COMMIT_SHA") && JQ_EXPR="${JQ_EXPR%\}}, commit_sha: \$commit_sha}"
31
+ [ -n "$DETAILS" ] && JQ_ARGS+=(--arg details "$DETAILS") && JQ_EXPR="${JQ_EXPR%\}}, details: \$details}"
32
32
 
33
33
  PAYLOAD=$(jq -n "${JQ_ARGS[@]}" "$JQ_EXPR")
34
34
 
@@ -15,7 +15,7 @@ set -e
15
15
  SCOPE_IDS="${1:?Usage: orbital-scope-update SCOPE_ID[,SCOPE_ID,...] STATUS}"
16
16
  STATUS="${2:?Usage: orbital-scope-update SCOPE_ID STATUS}"
17
17
 
18
- ORBITAL_API="http://localhost:4444/api/orbital"
18
+ ORBITAL_API="${ORBITAL_URL:-http://localhost:4444}/api/orbital"
19
19
 
20
20
  # Try the REST API first (fastest, real-time)
21
21
  if command -v curl &>/dev/null; then
@@ -1,8 +1,8 @@
1
1
  #!/bin/bash
2
2
  #
3
- # scope-create-cleanup.sh — PostToolUse:Write cleanup
3
+ # scope-create-cleanup.sh — PostToolUse:Write|Edit cleanup
4
4
  #
5
- # After a successful Write, checks if the written file is a scope document.
5
+ # After a successful Write or Edit, checks if the file is a scope document.
6
6
  # If so, removes the .scope-create-session marker to lift the write gate.
7
7
  #
8
8
  set -e
@@ -62,7 +62,6 @@ VIOLATION_DATA=$(jq -n --arg rule "scope-create-gate" --arg file "$FILE_PATH" --
62
62
  "$HOOK_DIR/orbital-emit.sh" VIOLATION "$VIOLATION_DATA" 2>/dev/null &
63
63
 
64
64
  # Resolve entry status from workflow manifest
65
- HOOK_DIR="$(dirname "$0")"
66
65
  source "$HOOK_DIR/scope-helpers.sh" 2>/dev/null || true
67
66
  ENTRY="${WORKFLOW_ENTRY_STATUS:-planning}"
68
67
 
@@ -176,6 +176,24 @@ append_session_uuid() {
176
176
  rm -f "${file}.lock"
177
177
  }
178
178
 
179
+ # Find icebox file by slug (e.g. "onboarding-flow" matches scopes/icebox/onboarding-flow.md)
180
+ # or legacy scopes/icebox/502-onboarding-flow.md)
181
+ find_scope_by_slug() {
182
+ local slug="$1"
183
+ local icebox_dir="$SCOPE_PROJECT_DIR/scopes/icebox"
184
+ [ -d "$icebox_dir" ] || return 1
185
+ # Try slug-only file first
186
+ if [ -f "$icebox_dir/${slug}.md" ]; then
187
+ echo "$icebox_dir/${slug}.md"
188
+ return 0
189
+ fi
190
+ # Fall back to legacy numeric-prefixed: {NNN}-{slug}.md
191
+ for f in "$icebox_dir"/*-"${slug}.md"; do
192
+ [ -f "$f" ] && echo "$f" && return 0
193
+ done
194
+ return 1
195
+ }
196
+
179
197
  # Find scope file by numeric ID across all directories
180
198
  find_scope_by_id() {
181
199
  local id="$1"
@@ -5,7 +5,7 @@
5
5
  # session recording, and gate cleanup into a single Bash call.
6
6
  #
7
7
  # Modes:
8
- # --promote ID Promote icebox idea to planning (renumber + move + scaffold)
8
+ # --promote SLUG|ID Promote icebox idea to planning (renumber + move + scaffold)
9
9
  # --scaffold ID Apply template to existing planning scope
10
10
  # --new Create brand new scope
11
11
  #
@@ -13,6 +13,7 @@
13
13
  # --title "..." Scope title (required for --new)
14
14
  # --desc "..." Description / problem statement
15
15
  # --category "..." Category tag (default: TBD)
16
+ # --effort "..." Effort estimate (default: TBD)
16
17
  #
17
18
  # Output: JSON to stdout
18
19
  # Errors: to stderr
@@ -28,6 +29,7 @@ SOURCE_ID=""
28
29
  TITLE=""
29
30
  DESCRIPTION=""
30
31
  CATEGORY="TBD"
32
+ EFFORT="TBD"
31
33
 
32
34
  while [[ $# -gt 0 ]]; do
33
35
  case "$1" in
@@ -37,12 +39,13 @@ while [[ $# -gt 0 ]]; do
37
39
  --title) TITLE="$2"; shift 2 ;;
38
40
  --desc) DESCRIPTION="$2"; shift 2 ;;
39
41
  --category) CATEGORY="$2"; shift 2 ;;
42
+ --effort) EFFORT="$2"; shift 2 ;;
40
43
  *) echo "Unknown argument: $1" >&2; exit 1 ;;
41
44
  esac
42
45
  done
43
46
 
44
47
  if [ -z "$MODE" ]; then
45
- echo "Usage: scope-prepare.sh --promote ID | --scaffold ID | --new --title \"...\"" >&2
48
+ echo "Usage: scope-prepare.sh --promote SLUG|ID | --scaffold ID | --new --title \"...\"" >&2
46
49
  exit 1
47
50
  fi
48
51
 
@@ -77,13 +80,18 @@ fi
77
80
 
78
81
  # ─── Resolve source file + extract metadata ─────────────────────
79
82
  OLD_FILE=""
83
+ FULL_BODY=""
80
84
  NOW_DATE=$(date +%Y-%m-%d)
81
85
  NOW_TIME=$(date +%H:%M)
82
86
  CREATED_DATE="$NOW_DATE"
83
87
 
84
88
  case "$MODE" in
85
89
  promote)
86
- OLD_FILE=$(find_scope_by_id "$SOURCE_ID")
90
+ # Try slug-based lookup first (new format), fall back to numeric ID (legacy)
91
+ OLD_FILE=$(find_scope_by_slug "$SOURCE_ID")
92
+ if [ -z "$OLD_FILE" ] || [ ! -f "$OLD_FILE" ]; then
93
+ OLD_FILE=$(find_scope_by_id "$SOURCE_ID")
94
+ fi
87
95
  if [ -z "$OLD_FILE" ] || [ ! -f "$OLD_FILE" ]; then
88
96
  echo "Error: Scope $SOURCE_ID not found" >&2
89
97
  exit 2
@@ -92,13 +100,18 @@ case "$MODE" in
92
100
  # Preserve original created date
93
101
  orig_created=$(get_frontmatter "$OLD_FILE" "created")
94
102
  [ -n "$orig_created" ] && CREATED_DATE="$orig_created"
95
- # Extract description from body (everything after frontmatter closing ---)
103
+ # Extract full body (everything after frontmatter closing ---) for template injection
104
+ FULL_BODY=$(awk 'BEGIN{fm=0} /^---$/{fm++; next} fm>=2{print}' "$OLD_FILE")
105
+ # Short description (first non-empty line) for JSON output
96
106
  if [ -z "$DESCRIPTION" ]; then
97
- DESCRIPTION=$(awk 'BEGIN{fm=0} /^---$/{fm++; next} fm>=2{print}' "$OLD_FILE" | head -20 | sed '/^$/d' | head -5)
107
+ DESCRIPTION=$(printf '%s' "$FULL_BODY" | sed '/^$/d' | head -1)
98
108
  fi
99
109
  # Get category if set
100
110
  orig_cat=$(get_frontmatter "$OLD_FILE" "category")
101
111
  [ -n "$orig_cat" ] && [ "$orig_cat" != "TBD" ] && CATEGORY="$orig_cat"
112
+ # Get effort if set
113
+ orig_effort=$(get_frontmatter "$OLD_FILE" "effort_estimate")
114
+ [ -n "$orig_effort" ] && [ "$orig_effort" != "TBD" ] && EFFORT="$orig_effort"
102
115
  ;;
103
116
 
104
117
  scaffold)
@@ -110,11 +123,15 @@ case "$MODE" in
110
123
  [ -z "$TITLE" ] && TITLE=$(get_frontmatter "$OLD_FILE" "title")
111
124
  orig_created=$(get_frontmatter "$OLD_FILE" "created")
112
125
  [ -n "$orig_created" ] && CREATED_DATE="$orig_created"
126
+ # Extract full body for template injection
127
+ FULL_BODY=$(awk 'BEGIN{fm=0} /^---$/{fm++; next} fm>=2{print}' "$OLD_FILE")
113
128
  if [ -z "$DESCRIPTION" ]; then
114
- DESCRIPTION=$(awk 'BEGIN{fm=0} /^---$/{fm++; next} fm>=2{print}' "$OLD_FILE" | head -20 | sed '/^$/d' | head -5)
129
+ DESCRIPTION=$(printf '%s' "$FULL_BODY" | sed '/^$/d' | head -1)
115
130
  fi
116
131
  orig_cat=$(get_frontmatter "$OLD_FILE" "category")
117
132
  [ -n "$orig_cat" ] && [ "$orig_cat" != "TBD" ] && CATEGORY="$orig_cat"
133
+ orig_effort=$(get_frontmatter "$OLD_FILE" "effort_estimate")
134
+ [ -n "$orig_effort" ] && [ "$orig_effort" != "TBD" ] && EFFORT="$orig_effort"
118
135
  ;;
119
136
  esac
120
137
 
@@ -130,6 +147,8 @@ compute_next_id() {
130
147
  local num
131
148
  num=$(basename "$f" | grep -oE '^[0-9]+' | sed 's/^0*//')
132
149
  [ -z "$num" ] && continue
150
+ # Skip legacy icebox-origin IDs (500+) to prevent namespace pollution
151
+ [ "$num" -ge 500 ] 2>/dev/null && continue
133
152
  [ "$num" -gt "$max_id" ] 2>/dev/null && max_id=$num
134
153
  done
135
154
  done
@@ -199,6 +218,7 @@ sed \
199
218
  -e "s/^id: NNN/id: $PADDED_ID/" \
200
219
  -e "s/^title: \"Scope Title\"/title: \"$ESCAPED_TITLE\"/" \
201
220
  -e "s/^category: \"TBD\".*/category: \"$CATEGORY\"/" \
221
+ -e "s/^effort_estimate: \"TBD\".*/effort_estimate: \"$EFFORT\"/" \
202
222
  -e "s/^created: YYYY-MM-DD/created: $CREATED_DATE/" \
203
223
  -e "s/^updated: YYYY-MM-DD/updated: $NOW_DATE/" \
204
224
  -e "s/^sessions: {}.*/$SESSIONS_REPLACEMENT/" \
@@ -209,6 +229,29 @@ sed \
209
229
  -e "s/\[What prompted this exploration\]/$ESCAPED_DESC/" \
210
230
  "$TEMPLATE" > "$NEW_FILE"
211
231
 
232
+ # ─── Inject full body into Overview section ────────────────────
233
+ # When promoting/scaffolding, preserve the original idea body in the
234
+ # SPECIFICATION > Overview section instead of losing it to the template.
235
+ if [ -n "$FULL_BODY" ] && [ "$(printf '%s' "$FULL_BODY" | sed '/^[[:space:]]*$/d' | wc -l)" -gt 0 ]; then
236
+ BODY_TMP=$(mktemp)
237
+ printf '%s\n' "$FULL_BODY" > "$BODY_TMP"
238
+
239
+ awk -v bodyfile="$BODY_TMP" '
240
+ /^### Overview/ {
241
+ print
242
+ print ""
243
+ while ((getline line < bodyfile) > 0) print line
244
+ # Skip the original placeholder lines (blank + [Problem statement...])
245
+ getline # blank line after ### Overview
246
+ getline # [Problem statement...] line
247
+ next
248
+ }
249
+ { print }
250
+ ' "$NEW_FILE" > "${NEW_FILE}.tmp" && mv "${NEW_FILE}.tmp" "$NEW_FILE"
251
+
252
+ rm -f "$BODY_TMP"
253
+ fi
254
+
212
255
  # ─── Cleanup old file (promote only) ────────────────────────────
213
256
  if [ "$MODE" = "promote" ] && [ -n "$OLD_FILE" ] && [ "$OLD_FILE" != "$NEW_FILE" ]; then
214
257
  rm -f "$OLD_FILE"
@@ -223,22 +266,34 @@ rm -f "$MARKER"
223
266
  "{\"scope_file\":\"$NEW_FILE\",\"id\":$SCOPE_ID,\"mode\":\"$MODE\"}" \
224
267
  --scope "$SCOPE_ID" --session "$SESSION_ID" 2>/dev/null &
225
268
 
269
+ # ─── Read available categories from config ─────────────────────
270
+ AVAILABLE_CATEGORIES=""
271
+ CONFIG_FILE="$SCOPE_PROJECT_DIR/.claude/orbital.config.json"
272
+ if command -v jq >/dev/null 2>&1 && [ -f "$CONFIG_FILE" ]; then
273
+ AVAILABLE_CATEGORIES=$(jq -r '.categories // [] | join(", ")' "$CONFIG_FILE" 2>/dev/null)
274
+ fi
275
+ EFFORT_BUCKETS="<1H, 1-4H, 4H+"
276
+
226
277
  # ─── JSON output ────────────────────────────────────────────────
227
278
  # Compute relative path
228
279
  REL_PATH="${NEW_FILE#"$SCOPE_PROJECT_DIR/"}"
229
280
 
230
281
  # Manual JSON construction (no jq dependency)
231
- printf '{"id":"%s","path":"%s","title":"%s","description":"%s","session_id":"%s","category":"%s","mode":"%s"}\n' \
282
+ printf '{"id":"%s","path":"%s","title":"%s","description":"%s","session_id":"%s","category":"%s","effort":"%s","mode":"%s","available_categories":"%s","effort_buckets":"%s"}\n' \
232
283
  "$PADDED_ID" \
233
284
  "$REL_PATH" \
234
285
  "$(printf '%s' "$TITLE" | sed 's/"/\\"/g')" \
235
- "$(printf '%s' "$DESCRIPTION" | head -1 | sed 's/"/\\"/g')" \
286
+ "$(printf '%s' "$DESCRIPTION" | sed 's/"/\\"/g')" \
236
287
  "$SESSION_ID" \
237
288
  "$CATEGORY" \
238
- "$MODE"
289
+ "$EFFORT" \
290
+ "$MODE" \
291
+ "$AVAILABLE_CATEGORIES" \
292
+ "$EFFORT_BUCKETS"
239
293
 
240
294
  # Print reminder to stderr (visible to Claude but not parsed as JSON)
241
295
  echo "" >&2
242
- echo "Scope document written. Write gate lifted." >&2
243
- echo "Remember: STOP here. Implementation is a separate session:" >&2
296
+ echo "Scope document scaffolded. Write gate lifted." >&2
297
+ echo "Now proceed to Step 2: categorize and estimate effort." >&2
298
+ echo "After specification is complete, STOP. Implementation is a separate session:" >&2
244
299
  echo " /scope-implement $PADDED_ID" >&2
@@ -0,0 +1 @@
1
+ {}
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://orbital-command.dev/schemas/orbital.config.schema.json",
2
+ "$schema": "https://raw.githubusercontent.com/SakaraLabs/orbital-command/main/schemas/orbital.config.schema.json",
3
3
  "projectName": "My Project",
4
4
  "scopesDir": "scopes",
5
5
  "eventsDir": ".claude/orbital-events",
@@ -31,5 +31,10 @@
31
31
  { "id": "frontend-designer", "label": "Frontend Designer", "emoji": "\ud83c\udfa8", "color": "#EC4899" },
32
32
  { "id": "architect", "label": "Architect", "emoji": "\ud83c\udfd7\ufe0f", "color": "#536dfe" },
33
33
  { "id": "rules-enforcer", "label": "Rules Enforcer", "emoji": "\ud83d\udccb", "color": "#6B7280" }
34
- ]
34
+ ],
35
+ "telemetry": {
36
+ "enabled": true,
37
+ "url": "https://orbital-telemetry.mike-calle.workers.dev/ingest/a7f3x9k2m1",
38
+ "headers": {}
39
+ }
35
40
  }
@@ -76,7 +76,7 @@
76
76
  ]
77
77
  },
78
78
  {
79
- "matcher": "Write",
79
+ "matcher": "Write|Edit",
80
80
  "hooks": [
81
81
  { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/scope-create-cleanup.sh", "_orbital": true }
82
82
  ]
@@ -40,8 +40,7 @@ Find scopes in `scopes/review/` that have a passing verdict:
40
40
  1. List files in `scopes/review/*.md`
41
41
  2. For each, extract the scope number and check `.claude/review-verdicts/{NNN}.json`
42
42
  3. If verdict exists and `verdict === "PASS"`:
43
- - `mv scopes/review/{file} scopes/completed/`
44
- - Update frontmatter: `status: completed`
43
+ - Transition: `bash .claude/hooks/scope-transition.sh --from review --to completed --scope {NNN}`
45
44
  - Update DASHBOARD: `📦 **Status**: Committed`
46
45
  4. If scope is in `scopes/review/` with **no** passing verdict:
47
46
  - Warn: "Scope {NNN} is in review but hasn't passed the review gate."
@@ -59,14 +58,20 @@ git commit -m "type(scope): description"
59
58
  - Follow conventional commit format
60
59
  - Do NOT push or create PRs — those are separate skills
61
60
 
62
- ### Step 4: Signal Completion
61
+ ### Step 4: Signal Completion (REQUIRED)
63
62
 
64
- If working on a dispatched scope, emit the agent completion event:
63
+ **Always emit after a successful commit** this is not optional:
65
64
 
66
65
  ```bash
66
+ # With a scope:
67
67
  bash .claude/hooks/orbital-emit.sh AGENT_COMPLETED '{"outcome":"success","action":"save"}' --scope "{NNN}"
68
+
69
+ # Without a scope (general commit):
70
+ bash .claude/hooks/orbital-emit.sh AGENT_COMPLETED '{"outcome":"success","action":"save"}'
68
71
  ```
69
72
 
73
+ The `--scope` flag is optional. Omit it when committing work that isn't tied to a specific scope.
74
+
70
75
  ## Quick Reference
71
76
 
72
77
  | User Says | Action |
@@ -42,8 +42,7 @@ git status --porcelain
42
42
 
43
43
  Find scopes in `scopes/completed/`:
44
44
  1. For each scope file in `scopes/completed/*.md`:
45
- - `mv scopes/completed/{file} scopes/dev/`
46
- - Update frontmatter: `status: dev`
45
+ - Transition: `bash .claude/hooks/scope-transition.sh --from completed --to dev --scope {NNN}`
47
46
  - Update DASHBOARD: `🔀 **Status**: Merged to Dev`
48
47
 
49
48
  If BATCH_SCOPE_IDS is set, only transition those specific scopes.
@@ -68,10 +67,16 @@ git checkout "$FEATURE_BRANCH"
68
67
 
69
68
  If merge conflicts occur, resolve them before continuing.
70
69
 
71
- ### Step 4: Signal Completion
70
+ ### Step 4: Signal Completion (REQUIRED)
71
+
72
+ **Always emit after a successful merge** — this is not optional:
72
73
 
73
74
  ```bash
75
+ # With a scope:
74
76
  bash .claude/hooks/orbital-emit.sh AGENT_COMPLETED '{"outcome":"success","action":"pr_dev"}' --scope "{NNN}"
77
+
78
+ # Without a scope:
79
+ bash .claude/hooks/orbital-emit.sh AGENT_COMPLETED '{"outcome":"success","action":"pr_dev"}'
75
80
  ```
76
81
 
77
82
  ## Output