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,105 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { GateService } from './gate-service.js';
3
+ import { createTestDb } from '../__tests__/helpers/db.js';
4
+ import { createMockEmitter } from '../__tests__/helpers/mock-emitter.js';
5
+ describe('GateService', () => {
6
+ let db;
7
+ let cleanup;
8
+ let emitter;
9
+ let service;
10
+ beforeEach(() => {
11
+ ({ db, cleanup } = createTestDb());
12
+ emitter = createMockEmitter();
13
+ service = new GateService(db, emitter);
14
+ });
15
+ afterEach(() => {
16
+ cleanup?.();
17
+ });
18
+ // ─── record() ─────────────────────────────────────────────
19
+ describe('record()', () => {
20
+ it('inserts gate result and emits gate:updated', () => {
21
+ service.record({
22
+ scope_id: 1,
23
+ gate_name: 'type-check',
24
+ status: 'pass',
25
+ details: null,
26
+ duration_ms: 1200,
27
+ commit_sha: 'abc1234',
28
+ });
29
+ const rows = db.prepare('SELECT * FROM quality_gates').all();
30
+ expect(rows).toHaveLength(1);
31
+ expect(rows[0].gate_name).toBe('type-check');
32
+ expect(rows[0].status).toBe('pass');
33
+ expect(rows[0].duration_ms).toBe(1200);
34
+ expect(emitter.emit).toHaveBeenCalledWith('gate:updated', expect.objectContaining({
35
+ gate_name: 'type-check',
36
+ status: 'pass',
37
+ }));
38
+ });
39
+ it('sets run_at to current timestamp', () => {
40
+ service.record({
41
+ scope_id: null,
42
+ gate_name: 'lint',
43
+ status: 'fail',
44
+ details: 'ESLint errors',
45
+ duration_ms: null,
46
+ commit_sha: null,
47
+ });
48
+ const row = db.prepare('SELECT run_at FROM quality_gates').get();
49
+ expect(row.run_at).toMatch(/^\d{4}-\d{2}-\d{2}T/);
50
+ });
51
+ });
52
+ // ─── getLatestForScope() ──────────────────────────────────
53
+ describe('getLatestForScope()', () => {
54
+ it('returns latest gate per gate_name for scope', () => {
55
+ service.record({ scope_id: 1, gate_name: 'type-check', status: 'fail', details: null, duration_ms: null, commit_sha: null });
56
+ service.record({ scope_id: 1, gate_name: 'type-check', status: 'pass', details: null, duration_ms: null, commit_sha: null });
57
+ service.record({ scope_id: 1, gate_name: 'lint', status: 'pass', details: null, duration_ms: null, commit_sha: null });
58
+ const latest = service.getLatestForScope(1);
59
+ expect(latest).toHaveLength(2);
60
+ const typeCheck = latest.find(g => g.gate_name === 'type-check');
61
+ expect(typeCheck?.status).toBe('pass'); // latest one
62
+ });
63
+ it('returns empty for unknown scope', () => {
64
+ expect(service.getLatestForScope(999)).toEqual([]);
65
+ });
66
+ });
67
+ // ─── getLatestRun() ───────────────────────────────────────
68
+ describe('getLatestRun()', () => {
69
+ it('returns all gates from most recent run', () => {
70
+ service.record({ scope_id: null, gate_name: 'type-check', status: 'pass', details: null, duration_ms: null, commit_sha: null });
71
+ service.record({ scope_id: null, gate_name: 'lint', status: 'pass', details: null, duration_ms: null, commit_sha: null });
72
+ const run = service.getLatestRun();
73
+ expect(run.length).toBeGreaterThanOrEqual(2);
74
+ });
75
+ it('returns empty when no gates exist', () => {
76
+ expect(service.getLatestRun()).toEqual([]);
77
+ });
78
+ });
79
+ // ─── getTrend() ───────────────────────────────────────────
80
+ describe('getTrend()', () => {
81
+ it('returns gate history ordered by run_at DESC', () => {
82
+ service.record({ scope_id: null, gate_name: 'build', status: 'pass', details: null, duration_ms: 500, commit_sha: null });
83
+ service.record({ scope_id: null, gate_name: 'build', status: 'fail', details: null, duration_ms: 600, commit_sha: null });
84
+ const trend = service.getTrend(10);
85
+ expect(trend.length).toBe(2);
86
+ });
87
+ });
88
+ // ─── getStats() ───────────────────────────────────────────
89
+ describe('getStats()', () => {
90
+ it('returns aggregate pass/fail per gate_name', () => {
91
+ service.record({ scope_id: null, gate_name: 'type-check', status: 'pass', details: null, duration_ms: null, commit_sha: null });
92
+ service.record({ scope_id: null, gate_name: 'type-check', status: 'pass', details: null, duration_ms: null, commit_sha: null });
93
+ service.record({ scope_id: null, gate_name: 'type-check', status: 'fail', details: null, duration_ms: null, commit_sha: null });
94
+ const stats = service.getStats();
95
+ const typeCheck = stats.find(s => s.gate_name === 'type-check');
96
+ expect(typeCheck).toBeDefined();
97
+ expect(typeCheck.total).toBe(3);
98
+ expect(typeCheck.passed).toBe(2);
99
+ expect(typeCheck.failed).toBe(1);
100
+ });
101
+ it('returns empty array when no gates exist', () => {
102
+ expect(service.getStats()).toEqual([]);
103
+ });
104
+ });
105
+ });
@@ -1,7 +1,9 @@
1
1
  import { execFile as execFileCb } from 'child_process';
2
2
  import { promisify } from 'util';
3
3
  import { listWorktrees } from '../utils/worktree-manager.js';
4
+ import { createLogger } from '../utils/logger.js';
4
5
  const execFile = promisify(execFileCb);
6
+ const log = createLogger('git');
5
7
  const CACHE_TTL = 60_000; // 60 seconds
6
8
  function cached(cache, key) {
7
9
  const entry = cache.get(key);
@@ -115,7 +117,8 @@ export class GitService {
115
117
  try {
116
118
  raw = await this.git(args);
117
119
  }
118
- catch {
120
+ catch (err) {
121
+ log.debug('Commits query failed', { branch: branch ?? 'all', error: String(err) });
119
122
  return [];
120
123
  }
121
124
  const commits = [];
@@ -172,7 +175,8 @@ export class GitService {
172
175
  '--format=%(HEAD)|%(refname:short)|%(objectname:short)|%(committerdate:iso-strict)|%(subject)',
173
176
  ]);
174
177
  }
175
- catch {
178
+ catch (err) {
179
+ log.debug('Branch listing failed', { error: String(err) });
176
180
  return [];
177
181
  }
178
182
  const now = Date.now();
@@ -230,7 +234,8 @@ export class GitService {
230
234
  try {
231
235
  wts = await listWorktrees(this.projectRoot);
232
236
  }
233
- catch {
237
+ catch (err) {
238
+ log.warn('Failed to list worktrees', { error: String(err) });
234
239
  return [];
235
240
  }
236
241
  const results = [];
@@ -288,13 +293,76 @@ export class GitService {
288
293
  });
289
294
  pairs.push({ from, to, count: commits.length, commits });
290
295
  }
291
- catch {
296
+ catch (err) {
297
+ log.debug('Drift query failed', { from, to, error: String(err) });
292
298
  pairs.push({ from, to, count: 0, commits: [] });
293
299
  }
294
300
  }
295
301
  setCache(this.cache, cacheKey, pairs);
296
302
  return pairs;
297
303
  }
304
+ // ─── Activity Series ────────────────────────────────────────
305
+ async getActivitySeries(days = 30) {
306
+ const cacheKey = `activity:${days}`;
307
+ const hit = cached(this.cache, cacheKey);
308
+ if (hit)
309
+ return hit;
310
+ try {
311
+ const raw = await this.git(['log', `--since=${days}.days.ago`, '--format=%aI', '--all']);
312
+ const counts = new Map();
313
+ // Initialize all days to 0
314
+ const now = new Date();
315
+ for (let i = 0; i < days; i++) {
316
+ const d = new Date(now);
317
+ d.setDate(d.getDate() - i);
318
+ counts.set(d.toISOString().slice(0, 10), 0);
319
+ }
320
+ for (const line of raw.trim().split('\n')) {
321
+ if (!line)
322
+ continue;
323
+ const dateKey = line.slice(0, 10);
324
+ counts.set(dateKey, (counts.get(dateKey) ?? 0) + 1);
325
+ }
326
+ const series = [...counts.entries()]
327
+ .map(([date, count]) => ({ date, count }))
328
+ .sort((a, b) => a.date.localeCompare(b.date));
329
+ setCache(this.cache, cacheKey, series);
330
+ return series;
331
+ }
332
+ catch (err) {
333
+ log.debug('Activity series query failed', { days, error: String(err) });
334
+ return [];
335
+ }
336
+ }
337
+ // ─── Health Metrics ────────────────────────────────────────
338
+ async getHealthMetrics(githubPrAges) {
339
+ // Commits per week
340
+ let commitsPerWeek = 0;
341
+ try {
342
+ const raw = await this.git(['log', '--since=7.days.ago', '--oneline', '--all']);
343
+ commitsPerWeek = raw.trim().split('\n').filter(Boolean).length;
344
+ }
345
+ catch { /* ok */ }
346
+ // Stale branches
347
+ const branches = await this.getBranches();
348
+ const staleBranchCount = branches.filter(b => b.isStale && !b.isRemote).length;
349
+ // Avg PR age
350
+ let avgPrAgeDays = 0;
351
+ if (githubPrAges && githubPrAges.length > 0) {
352
+ avgPrAgeDays = Math.round(githubPrAges.reduce((a, b) => a + b, 0) / githubPrAges.length);
353
+ }
354
+ // Drift severity (reuse cached data if available)
355
+ const driftSeverity = 'clean';
356
+ // Grade computation: start at 100
357
+ let score = 100;
358
+ score -= Math.min(30, staleBranchCount * 5);
359
+ if (commitsPerWeek < 5)
360
+ score -= 10;
361
+ if (avgPrAgeDays > 3)
362
+ score -= Math.min(25, (avgPrAgeDays - 3) * 5);
363
+ const grade = score >= 90 ? 'A' : score >= 80 ? 'B' : score >= 70 ? 'C' : score >= 60 ? 'D' : 'F';
364
+ return { commitsPerWeek, avgPrAgeDays, staleBranchCount, driftSeverity, grade };
365
+ }
298
366
  // ─── Git Status Polling ────────────────────────────────────
299
367
  async getStatusHash() {
300
368
  const [head, dirty] = await Promise.all([
@@ -303,6 +371,42 @@ export class GitService {
303
371
  ]);
304
372
  return `${head.trim()}:${dirty.trim().length > 0 ? 'dirty' : 'clean'}`;
305
373
  }
374
+ // ─── Pipeline Drift ─────────────────────────────────────────
375
+ async getPipelineDrift() {
376
+ const cacheKey = 'pipeline-drift';
377
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
378
+ const hit = cached(this.cache, cacheKey);
379
+ if (hit)
380
+ return hit;
381
+ function parseDriftCommits(raw) {
382
+ if (!raw)
383
+ return [];
384
+ return raw.split('\n').map((line) => {
385
+ const [sha, date, message, author] = line.split('|');
386
+ return { sha, date, message: message ?? '', author: author ?? '' };
387
+ });
388
+ }
389
+ function parseHead(raw) {
390
+ const [sha, date, message] = raw.split('|');
391
+ return { sha: sha ?? '', date: date ?? '', message: message ?? '' };
392
+ }
393
+ const [devToStagingRaw, stagingToMainRaw, devHead, stagingHead, mainHead] = await Promise.all([
394
+ this.git(['log', 'origin/dev', '--not', 'origin/staging', '--reverse', '--format=%H|%aI|%s|%an']).catch(() => ''),
395
+ this.git(['log', 'origin/staging', '--not', 'origin/main', '--reverse', '--format=%H|%aI|%s|%an']).catch(() => ''),
396
+ this.git(['log', 'origin/dev', '-1', '--format=%H|%aI|%s']).catch(() => ''),
397
+ this.git(['log', 'origin/staging', '-1', '--format=%H|%aI|%s']).catch(() => ''),
398
+ this.git(['log', 'origin/main', '-1', '--format=%H|%aI|%s']).catch(() => ''),
399
+ ]);
400
+ const devToStaging = parseDriftCommits(devToStagingRaw);
401
+ const stagingToMain = parseDriftCommits(stagingToMainRaw);
402
+ const data = {
403
+ devToStaging: { count: devToStaging.length, commits: devToStaging, oldestDate: devToStaging[0]?.date ?? null },
404
+ stagingToMain: { count: stagingToMain.length, commits: stagingToMain, oldestDate: stagingToMain[0]?.date ?? null },
405
+ heads: { dev: parseHead(devHead), staging: parseHead(stagingHead), main: parseHead(mainHead) },
406
+ };
407
+ setCache(this.cache, cacheKey, data);
408
+ return data;
409
+ }
306
410
  clearCache() {
307
411
  this.cache.clear();
308
412
  }
@@ -1,4 +1,4 @@
1
- import { execFile as execFileCb } from 'child_process';
1
+ import { execFile as execFileCb, spawn } from 'child_process';
2
2
  import { promisify } from 'util';
3
3
  // Uses execFile (not exec) — safe against shell injection
4
4
  const execFile = promisify(execFileCb);
@@ -107,7 +107,7 @@ export class GitHubService {
107
107
  try {
108
108
  const raw = await this.gh([
109
109
  'pr', 'list', '--state', 'open', '--json',
110
- 'number,title,author,headRefName,baseRefName,state,url,createdAt',
110
+ 'number,title,author,headRefName,baseRefName,state,url,createdAt,updatedAt,reviewDecision',
111
111
  '--limit', '30',
112
112
  ]);
113
113
  const parsed = JSON.parse(raw);
@@ -133,6 +133,8 @@ export class GitHubService {
133
133
  url: String(pr.url ?? ''),
134
134
  createdAt: String(pr.createdAt ?? ''),
135
135
  scopeIds,
136
+ reviewDecision: pr.reviewDecision || null,
137
+ lastActivityAt: String(pr.updatedAt ?? pr.createdAt ?? ''),
136
138
  };
137
139
  });
138
140
  this.prCache = { data: prs, ts: Date.now() };
@@ -142,4 +144,110 @@ export class GitHubService {
142
144
  return [];
143
145
  }
144
146
  }
147
+ // ─── Auth Flow ─────────────────────────────────────────────
148
+ /** Start OAuth flow via gh CLI — opens browser. Client polls getAuthStatus(). */
149
+ async connectOAuth() {
150
+ return new Promise((resolve) => {
151
+ // spawn is safe here: no shell, args are hardcoded literals
152
+ const child = spawn('gh', ['auth', 'login', '--web', '--git-protocol', 'https'], {
153
+ cwd: this.projectRoot,
154
+ stdio: ['pipe', 'pipe', 'pipe'],
155
+ });
156
+ let stderr = '';
157
+ child.stderr?.on('data', (data) => { stderr += data.toString(); });
158
+ // Resolve quickly — the gh process runs in background, client polls auth status
159
+ setTimeout(() => {
160
+ resolve({ success: true });
161
+ }, 500);
162
+ child.on('error', () => {
163
+ resolve({ success: false, error: stderr || 'Failed to start auth flow' });
164
+ });
165
+ });
166
+ }
167
+ /** Authenticate using a Personal Access Token piped to gh stdin. */
168
+ async connectWithToken(token) {
169
+ try {
170
+ // spawn is safe: no shell, args are hardcoded literals, token via stdin (not args)
171
+ const child = spawn('gh', ['auth', 'login', '--with-token'], {
172
+ cwd: this.projectRoot,
173
+ stdio: ['pipe', 'pipe', 'pipe'],
174
+ });
175
+ return new Promise((resolve) => {
176
+ let stderr = '';
177
+ child.stderr?.on('data', (data) => { stderr += data.toString(); });
178
+ child.on('close', (code) => {
179
+ this.clearCaches();
180
+ if (code === 0) {
181
+ resolve({ success: true });
182
+ }
183
+ else {
184
+ resolve({ success: false, error: stderr || 'Authentication failed' });
185
+ }
186
+ });
187
+ child.on('error', () => {
188
+ resolve({ success: false, error: 'Failed to run gh auth' });
189
+ });
190
+ child.stdin?.write(token);
191
+ child.stdin?.end();
192
+ });
193
+ }
194
+ catch {
195
+ return { success: false, error: 'Failed to authenticate' };
196
+ }
197
+ }
198
+ /** Lightweight auth check — returns current user if authenticated. */
199
+ async getAuthStatus() {
200
+ try {
201
+ const whoami = await this.gh(['api', 'user', '--jq', '.login']);
202
+ const user = whoami.trim();
203
+ return user ? { authenticated: true, user } : { authenticated: false };
204
+ }
205
+ catch {
206
+ return { authenticated: false };
207
+ }
208
+ }
209
+ /** Log out of GitHub CLI. */
210
+ async disconnect() {
211
+ try {
212
+ // execFile is safe — no shell injection
213
+ await execFile('gh', ['auth', 'logout', '--hostname', 'github.com'], {
214
+ cwd: this.projectRoot,
215
+ timeout: 10_000,
216
+ env: { ...process.env, GH_PROMPT_DISABLED: '1' },
217
+ });
218
+ this.clearCaches();
219
+ return { success: true };
220
+ }
221
+ catch (err) {
222
+ this.clearCaches();
223
+ return { success: false, error: String(err) };
224
+ }
225
+ }
226
+ // ─── CI Checks ─────────────────────────────────────────────
227
+ /** Fetch GitHub Actions check runs for a commit ref. */
228
+ async getCheckRuns(ref) {
229
+ // Validate ref to prevent path traversal — only allow hex SHA and branch-like names
230
+ if (!/^[a-zA-Z0-9._/-]+$/.test(ref))
231
+ return [];
232
+ try {
233
+ const raw = await this.gh([
234
+ 'api', `repos/{owner}/{repo}/commits/${ref}/check-runs`,
235
+ '--jq', '.check_runs | map({name, status, conclusion, html_url})',
236
+ ]);
237
+ const parsed = JSON.parse(raw);
238
+ return parsed.map(c => ({
239
+ name: String(c.name ?? ''),
240
+ status: String(c.status ?? 'queued'),
241
+ conclusion: c.conclusion ? String(c.conclusion) : null,
242
+ url: String(c.html_url ?? ''),
243
+ }));
244
+ }
245
+ catch {
246
+ return [];
247
+ }
248
+ }
249
+ clearCaches() {
250
+ this.statusCache = null;
251
+ this.prCache = null;
252
+ }
145
253
  }
@@ -0,0 +1,190 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import { ReadinessService } from './readiness-service.js';
3
+ import { WorkflowEngine } from '../../shared/workflow-engine.js';
4
+ import { CONFIG_WITH_HOOKS } from '../../shared/__fixtures__/workflow-configs.js';
5
+ import fs from 'fs';
6
+ import os from 'os';
7
+ import path from 'path';
8
+ function makeScope(overrides) {
9
+ return {
10
+ title: `Scope ${overrides.id}`,
11
+ slug: undefined,
12
+ status: 'backlog',
13
+ priority: null,
14
+ effort_estimate: null,
15
+ category: null,
16
+ tags: [],
17
+ blocked_by: [],
18
+ blocks: [],
19
+ file_path: `/scopes/backlog/${String(overrides.id).padStart(3, '0')}-test.md`,
20
+ created_at: null,
21
+ updated_at: null,
22
+ raw_content: '# Test Scope\nSome content here.',
23
+ sessions: {},
24
+ is_ghost: false,
25
+ favourite: false,
26
+ ...overrides,
27
+ };
28
+ }
29
+ describe('ReadinessService', () => {
30
+ let engine;
31
+ let tmpDir;
32
+ let service;
33
+ let mockScopeService;
34
+ let mockGateService;
35
+ let scopes;
36
+ beforeEach(() => {
37
+ engine = new WorkflowEngine(CONFIG_WITH_HOOKS);
38
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'readiness-test-'));
39
+ scopes = [
40
+ makeScope({ id: 1, status: 'backlog', sessions: { implementScope: ['sess-1'] } }),
41
+ makeScope({ id: 2, status: 'active', sessions: { implementScope: ['sess-1'] }, blocked_by: [99] }),
42
+ makeScope({ id: 3, status: 'active', sessions: { implementScope: ['sess-1'] }, raw_content: '# Scope\n- [ ] Task 1\n- [x] Task 2' }),
43
+ makeScope({ id: 99, status: 'shipped' }), // terminal blocker
44
+ makeScope({ id: 100, status: 'backlog' }), // non-terminal blocker
45
+ ];
46
+ mockScopeService = { getById: (id) => scopes.find(s => s.id === id) };
47
+ mockGateService = {
48
+ getLatestForScope: vi.fn().mockReturnValue([]),
49
+ getLatestRun: vi.fn().mockReturnValue([]),
50
+ };
51
+ service = new ReadinessService(mockScopeService, mockGateService, engine, tmpDir);
52
+ });
53
+ afterEach(() => {
54
+ fs.rmSync(tmpDir, { recursive: true, force: true });
55
+ });
56
+ describe('getReadiness()', () => {
57
+ it('returns null for unknown scope', () => {
58
+ expect(service.getReadiness(999)).toBeNull();
59
+ });
60
+ it('returns readiness with transitions for known scope', () => {
61
+ const result = service.getReadiness(1);
62
+ expect(result).not.toBeNull();
63
+ expect(result.scope_id).toBe(1);
64
+ expect(result.transitions).toBeDefined();
65
+ expect(result.transitions.length).toBeGreaterThan(0);
66
+ });
67
+ it('includes hook statuses for each transition', () => {
68
+ const result = service.getReadiness(1);
69
+ const transition = result.transitions[0];
70
+ expect(transition.hooks).toBeDefined();
71
+ expect(Array.isArray(transition.hooks)).toBe(true);
72
+ });
73
+ });
74
+ describe('session-enforcer hook', () => {
75
+ it('passes when session key exists for target column', () => {
76
+ // Scope 1 is in backlog, transition to active requires implementScope session key
77
+ // Scope 1 has sessions.implementScope = ['sess-1']
78
+ const result = service.getReadiness(1);
79
+ const toActive = result.transitions.find(t => t.to === 'active');
80
+ if (toActive) {
81
+ const enforcer = toActive.hooks.find(h => h.id === 'session-enforcer');
82
+ // If session-enforcer is on this edge, it should look at the target session key
83
+ if (enforcer) {
84
+ // The scope has the required session, so it should pass
85
+ expect(['pass', 'unknown']).toContain(enforcer.status);
86
+ }
87
+ }
88
+ });
89
+ });
90
+ describe('blocker-check hook', () => {
91
+ it('passes when blocker is in terminal status', () => {
92
+ // Scope 2 blocked_by [99], scope 99 is shipped (terminal)
93
+ const result = service.getReadiness(2);
94
+ const transition = result.transitions.find(t => t.hooks.some(h => h.id === 'blocker-check'));
95
+ if (transition) {
96
+ const blockerHook = transition.hooks.find(h => h.id === 'blocker-check');
97
+ expect(blockerHook?.status).toBe('pass');
98
+ }
99
+ });
100
+ it('fails when blocker is not in terminal status', () => {
101
+ // Add a scope blocked by non-terminal scope
102
+ scopes.push(makeScope({ id: 5, status: 'active', blocked_by: [100], sessions: { implementScope: ['s'] } }));
103
+ const result = service.getReadiness(5);
104
+ const transition = result.transitions.find(t => t.hooks.some(h => h.id === 'blocker-check'));
105
+ if (transition) {
106
+ const blockerHook = transition.hooks.find(h => h.id === 'blocker-check');
107
+ expect(blockerHook?.status).toBe('fail');
108
+ }
109
+ });
110
+ });
111
+ describe('review-gate-check hook', () => {
112
+ it('passes when verdict file exists with PASS', () => {
113
+ const verdictDir = path.join(tmpDir, '.claude', 'review-verdicts');
114
+ fs.mkdirSync(verdictDir, { recursive: true });
115
+ fs.writeFileSync(path.join(verdictDir, '002.json'), JSON.stringify({ verdict: 'PASS' }));
116
+ // Scope 2 is in active, edge active→review has review-gate-check
117
+ const result = service.getReadiness(2);
118
+ const toReview = result.transitions.find(t => t.to === 'review');
119
+ if (toReview) {
120
+ const reviewHook = toReview.hooks.find(h => h.id === 'review-gate-check');
121
+ if (reviewHook) {
122
+ expect(reviewHook.status).toBe('pass');
123
+ }
124
+ }
125
+ });
126
+ it('fails when verdict file is missing', () => {
127
+ const result = service.getReadiness(2);
128
+ const toReview = result.transitions.find(t => t.to === 'review');
129
+ if (toReview) {
130
+ const reviewHook = toReview.hooks.find(h => h.id === 'review-gate-check');
131
+ if (reviewHook) {
132
+ expect(reviewHook.status).toBe('fail');
133
+ }
134
+ }
135
+ });
136
+ });
137
+ describe('completion-checklist hook', () => {
138
+ it('fails when scope has unchecked items', () => {
139
+ // Scope 3 has raw_content with "- [ ] Task 1" (unchecked)
140
+ const result = service.getReadiness(3);
141
+ for (const transition of result.transitions) {
142
+ const checklist = transition.hooks.find(h => h.id === 'completion-checklist');
143
+ if (checklist) {
144
+ expect(checklist.status).toBe('fail');
145
+ }
146
+ }
147
+ });
148
+ });
149
+ describe('scope-create-gate hook', () => {
150
+ it('passes when scope has title and content', () => {
151
+ const result = service.getReadiness(1);
152
+ for (const transition of result.transitions) {
153
+ const gate = transition.hooks.find(h => h.id === 'scope-create-gate');
154
+ if (gate) {
155
+ expect(gate.status).toBe('pass');
156
+ }
157
+ }
158
+ });
159
+ it('fails when scope has no content', () => {
160
+ scopes.push(makeScope({ id: 6, status: 'backlog', title: '', raw_content: '' }));
161
+ const result = service.getReadiness(6);
162
+ for (const transition of result.transitions) {
163
+ const gate = transition.hooks.find(h => h.id === 'scope-create-gate');
164
+ if (gate) {
165
+ expect(gate.status).toBe('fail');
166
+ }
167
+ }
168
+ });
169
+ });
170
+ describe('lifecycle/observer hooks', () => {
171
+ it('scope-transition always passes', () => {
172
+ const result = service.getReadiness(1);
173
+ for (const transition of result.transitions) {
174
+ const lifecycle = transition.hooks.find(h => h.id === 'scope-transition');
175
+ if (lifecycle) {
176
+ expect(lifecycle.status).toBe('pass');
177
+ }
178
+ }
179
+ });
180
+ it('dashboard-sync always passes', () => {
181
+ const result = service.getReadiness(1);
182
+ for (const transition of result.transitions) {
183
+ const observer = transition.hooks.find(h => h.id === 'dashboard-sync');
184
+ if (observer) {
185
+ expect(observer.status).toBe('pass');
186
+ }
187
+ }
188
+ });
189
+ });
190
+ });
@@ -52,7 +52,8 @@ export class ScopeCache {
52
52
  }
53
53
  /** Get the maximum raw scope number excluding icebox scopes (for next-ID generation).
54
54
  * Cache keys use encoded IDs (suffixed scopes like 047a → 1047, 075x → 9075),
55
- * but next-ID generation needs the raw scope number (047, 075, 087). */
55
+ * but next-ID generation needs the raw scope number (047, 075, 087).
56
+ * Skips IDs >= 500 to handle legacy icebox-origin files during migration. */
56
57
  maxNonIceboxId() {
57
58
  let max = 0;
58
59
  for (const [id, scope] of this.byId) {
@@ -60,6 +61,9 @@ export class ScopeCache {
60
61
  continue;
61
62
  // Decode: encoded IDs ≥1000 have a suffix offset — raw number is id % 1000
62
63
  const raw = id >= 1000 ? id % 1000 : id;
64
+ // Skip legacy icebox-origin IDs (500+) to prevent namespace pollution
65
+ if (raw >= 500)
66
+ continue;
63
67
  if (raw > max)
64
68
  max = raw;
65
69
  }