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.
- package/README.md +67 -42
- package/bin/commands/config.js +19 -0
- package/bin/commands/events.js +40 -0
- package/bin/commands/launch.js +126 -0
- package/bin/commands/manifest.js +283 -0
- package/bin/commands/registry.js +104 -0
- package/bin/commands/update.js +24 -0
- package/bin/lib/helpers.js +229 -0
- package/bin/orbital.js +147 -319
- package/dist/assets/Landing-CfQdHR0N.js +11 -0
- package/dist/assets/PrimitivesConfig-DThSipFy.js +32 -0
- package/dist/assets/QualityGates-B4kxM5UU.js +26 -0
- package/dist/assets/SessionTimeline-Bz1iZnmg.js +1 -0
- package/dist/assets/Settings-DLcZwbCT.js +12 -0
- package/dist/assets/SourceControl-BMNIz7Lt.js +36 -0
- package/dist/assets/WorkflowVisualizer-CxuSBOYu.js +69 -0
- package/dist/assets/arrow-down-DVPp6_qp.js +6 -0
- package/dist/assets/bot-NFaJBDn_.js +6 -0
- package/dist/assets/charts-LGLb8hyU.js +68 -0
- package/dist/assets/circle-x-IsFCkBZu.js +6 -0
- package/dist/assets/file-text-J1cebZXF.js +6 -0
- package/dist/assets/globe-WzeyHsUc.js +6 -0
- package/dist/assets/index-BdJ57EhC.css +1 -0
- package/dist/assets/index-o4ScMAuR.js +349 -0
- package/dist/assets/key-CKR8JJSj.js +6 -0
- package/dist/assets/minus-CHBsJyjp.js +6 -0
- package/dist/assets/radio-xqZaR-Uk.js +6 -0
- package/dist/assets/rocket-D_xvvNG6.js +6 -0
- package/dist/assets/shield-TdB1yv_a.js +6 -0
- package/dist/assets/ui-BmsSg9jU.js +53 -0
- package/dist/assets/useSocketListener-0L5yiN5i.js +1 -0
- package/dist/assets/useWorkflowEditor-CqeRWVQX.js +11 -0
- package/dist/assets/{vendor-Dzv9lrRc.js → vendor-Bqt8AJn2.js} +1 -1
- package/dist/assets/workflow-constants-Rw-GmgHZ.js +6 -0
- package/dist/assets/zap-C9wqYMpl.js +6 -0
- package/dist/favicon.svg +1 -0
- package/dist/index.html +6 -5
- package/dist/server/server/__tests__/data-routes.test.js +126 -0
- package/dist/server/server/__tests__/helpers/db.js +17 -0
- package/dist/server/server/__tests__/helpers/mock-emitter.js +8 -0
- package/dist/server/server/__tests__/scope-routes.test.js +138 -0
- package/dist/server/server/__tests__/sprint-routes.test.js +102 -0
- package/dist/server/server/__tests__/workflow-routes.test.js +107 -0
- package/dist/server/server/config-migrator.js +135 -0
- package/dist/server/server/config.js +51 -7
- package/dist/server/server/database.js +21 -28
- package/dist/server/server/global-config.js +143 -0
- package/dist/server/server/index.js +118 -276
- package/dist/server/server/init.js +243 -225
- package/dist/server/server/launch.js +29 -0
- package/dist/server/server/manifest-types.js +8 -0
- package/dist/server/server/manifest.js +454 -0
- package/dist/server/server/migrate-legacy.js +229 -0
- package/dist/server/server/parsers/event-parser.js +4 -1
- package/dist/server/server/parsers/event-parser.test.js +117 -0
- package/dist/server/server/parsers/scope-parser.js +74 -28
- package/dist/server/server/parsers/scope-parser.test.js +230 -0
- package/dist/server/server/project-context.js +265 -0
- package/dist/server/server/project-emitter.js +41 -0
- package/dist/server/server/project-manager.js +297 -0
- package/dist/server/server/routes/aggregate-routes.js +871 -0
- package/dist/server/server/routes/config-routes.js +41 -90
- package/dist/server/server/routes/data-routes.js +25 -123
- package/dist/server/server/routes/dispatch-routes.js +37 -15
- package/dist/server/server/routes/git-routes.js +74 -0
- package/dist/server/server/routes/manifest-routes.js +319 -0
- package/dist/server/server/routes/scope-routes.js +45 -28
- package/dist/server/server/routes/sync-routes.js +134 -0
- package/dist/server/server/routes/version-routes.js +1 -15
- package/dist/server/server/routes/workflow-routes.js +9 -3
- package/dist/server/server/schema.js +3 -0
- package/dist/server/server/services/batch-orchestrator.js +41 -17
- package/dist/server/server/services/claude-session-service.js +17 -14
- package/dist/server/server/services/config-service.js +10 -1
- package/dist/server/server/services/deploy-service.test.js +119 -0
- package/dist/server/server/services/event-service.js +64 -1
- package/dist/server/server/services/event-service.test.js +191 -0
- package/dist/server/server/services/gate-service.test.js +105 -0
- package/dist/server/server/services/git-service.js +108 -4
- package/dist/server/server/services/github-service.js +110 -2
- package/dist/server/server/services/readiness-service.test.js +190 -0
- package/dist/server/server/services/scope-cache.js +5 -1
- package/dist/server/server/services/scope-cache.test.js +142 -0
- package/dist/server/server/services/scope-service.js +222 -131
- package/dist/server/server/services/scope-service.test.js +137 -0
- package/dist/server/server/services/sprint-orchestrator.js +29 -15
- package/dist/server/server/services/sprint-service.js +23 -3
- package/dist/server/server/services/sprint-service.test.js +238 -0
- package/dist/server/server/services/sync-service.js +434 -0
- package/dist/server/server/services/sync-types.js +2 -0
- package/dist/server/server/services/workflow-service.js +26 -5
- package/dist/server/server/services/workflow-service.test.js +159 -0
- package/dist/server/server/settings-sync.js +284 -0
- package/dist/server/server/uninstall.js +195 -0
- package/dist/server/server/update-planner.js +279 -0
- package/dist/server/server/update.js +212 -0
- package/dist/server/server/utils/cc-hooks-parser.js +3 -0
- package/dist/server/server/utils/cc-hooks-parser.test.js +86 -0
- package/dist/server/server/utils/dispatch-utils.js +83 -24
- package/dist/server/server/utils/dispatch-utils.test.js +182 -0
- package/dist/server/server/utils/flag-builder.js +54 -0
- package/dist/server/server/utils/json-fields.js +14 -0
- package/dist/server/server/utils/json-fields.test.js +73 -0
- package/dist/server/server/utils/logger.js +37 -3
- package/dist/server/server/utils/package-info.js +30 -0
- package/dist/server/server/utils/route-helpers.js +47 -0
- package/dist/server/server/utils/route-helpers.test.js +115 -0
- package/dist/server/server/utils/terminal-launcher.js +79 -25
- package/dist/server/server/utils/worktree-manager.js +13 -4
- package/dist/server/server/validator.js +230 -0
- package/dist/server/server/watchers/event-watcher.js +28 -13
- package/dist/server/server/watchers/global-watcher.js +63 -0
- package/dist/server/server/watchers/scope-watcher.js +27 -12
- package/dist/server/server/wizard/config-editor.js +237 -0
- package/dist/server/server/wizard/detect.js +96 -0
- package/dist/server/server/wizard/doctor.js +115 -0
- package/dist/server/server/wizard/index.js +340 -0
- package/dist/server/server/wizard/phases/confirm.js +39 -0
- package/dist/server/server/wizard/phases/project-setup.js +90 -0
- package/dist/server/server/wizard/phases/setup-wizard.js +66 -0
- package/dist/server/server/wizard/phases/welcome.js +32 -0
- package/dist/server/server/wizard/phases/workflow-setup.js +22 -0
- package/dist/server/server/wizard/types.js +29 -0
- package/dist/server/server/wizard/ui.js +73 -0
- package/dist/server/shared/__fixtures__/workflow-configs.js +75 -0
- package/dist/server/shared/api-types.js +80 -1
- package/dist/server/shared/default-workflow.json +65 -0
- package/dist/server/shared/onboarding-tour.test.js +81 -0
- package/dist/server/shared/project-colors.js +24 -0
- package/dist/server/shared/workflow-config.test.js +84 -0
- package/dist/server/shared/workflow-engine.js +1 -1
- package/dist/server/shared/workflow-engine.test.js +302 -0
- package/dist/server/shared/workflow-normalizer.js +101 -0
- package/dist/server/shared/workflow-normalizer.test.js +100 -0
- package/dist/server/src/components/onboarding/tour-steps.js +84 -0
- package/package.json +34 -29
- package/schemas/orbital.config.schema.json +2 -5
- package/scripts/postinstall.js +18 -6
- package/scripts/release.sh +53 -0
- package/server/__tests__/data-routes.test.ts +151 -0
- package/server/__tests__/helpers/db.ts +19 -0
- package/server/__tests__/helpers/mock-emitter.ts +10 -0
- package/server/__tests__/scope-routes.test.ts +158 -0
- package/server/__tests__/sprint-routes.test.ts +118 -0
- package/server/__tests__/workflow-routes.test.ts +120 -0
- package/server/config-migrator.ts +160 -0
- package/server/config.ts +64 -12
- package/server/database.ts +22 -31
- package/server/global-config.ts +204 -0
- package/server/index.ts +139 -316
- package/server/init.ts +266 -234
- package/server/launch.ts +32 -0
- package/server/manifest-types.ts +145 -0
- package/server/manifest.ts +494 -0
- package/server/migrate-legacy.ts +290 -0
- package/server/parsers/event-parser.test.ts +135 -0
- package/server/parsers/event-parser.ts +4 -1
- package/server/parsers/scope-parser.test.ts +270 -0
- package/server/parsers/scope-parser.ts +79 -31
- package/server/project-context.ts +325 -0
- package/server/project-emitter.ts +50 -0
- package/server/project-manager.ts +368 -0
- package/server/routes/aggregate-routes.ts +968 -0
- package/server/routes/config-routes.ts +43 -85
- package/server/routes/data-routes.ts +34 -156
- package/server/routes/dispatch-routes.ts +46 -17
- package/server/routes/git-routes.ts +77 -0
- package/server/routes/manifest-routes.ts +388 -0
- package/server/routes/scope-routes.ts +39 -30
- package/server/routes/sync-routes.ts +175 -0
- package/server/routes/version-routes.ts +1 -16
- package/server/routes/workflow-routes.ts +9 -3
- package/server/schema.ts +3 -0
- package/server/services/batch-orchestrator.ts +41 -17
- package/server/services/claude-session-service.ts +16 -14
- package/server/services/config-service.ts +10 -1
- package/server/services/deploy-service.test.ts +145 -0
- package/server/services/deploy-service.ts +2 -2
- package/server/services/event-service.test.ts +242 -0
- package/server/services/event-service.ts +92 -3
- package/server/services/gate-service.test.ts +131 -0
- package/server/services/gate-service.ts +2 -2
- package/server/services/git-service.ts +137 -4
- package/server/services/github-service.ts +120 -2
- package/server/services/readiness-service.test.ts +217 -0
- package/server/services/scope-cache.test.ts +167 -0
- package/server/services/scope-cache.ts +4 -1
- package/server/services/scope-service.test.ts +169 -0
- package/server/services/scope-service.ts +224 -130
- package/server/services/sprint-orchestrator.ts +30 -15
- package/server/services/sprint-service.test.ts +271 -0
- package/server/services/sprint-service.ts +29 -5
- package/server/services/sync-service.ts +482 -0
- package/server/services/sync-types.ts +77 -0
- package/server/services/workflow-service.test.ts +190 -0
- package/server/services/workflow-service.ts +29 -9
- package/server/settings-sync.ts +359 -0
- package/server/uninstall.ts +214 -0
- package/server/update-planner.ts +346 -0
- package/server/update.ts +263 -0
- package/server/utils/cc-hooks-parser.test.ts +96 -0
- package/server/utils/cc-hooks-parser.ts +4 -0
- package/server/utils/dispatch-utils.test.ts +245 -0
- package/server/utils/dispatch-utils.ts +102 -30
- package/server/utils/flag-builder.ts +56 -0
- package/server/utils/json-fields.test.ts +83 -0
- package/server/utils/json-fields.ts +14 -0
- package/server/utils/logger.ts +40 -3
- package/server/utils/package-info.ts +32 -0
- package/server/utils/route-helpers.test.ts +144 -0
- package/server/utils/route-helpers.ts +50 -0
- package/server/utils/terminal-launcher.ts +85 -25
- package/server/utils/worktree-manager.ts +9 -4
- package/server/validator.ts +270 -0
- package/server/watchers/event-watcher.ts +24 -12
- package/server/watchers/global-watcher.ts +77 -0
- package/server/watchers/scope-watcher.ts +21 -9
- package/server/wizard/config-editor.ts +248 -0
- package/server/wizard/detect.ts +104 -0
- package/server/wizard/doctor.ts +114 -0
- package/server/wizard/index.ts +438 -0
- package/server/wizard/phases/confirm.ts +45 -0
- package/server/wizard/phases/project-setup.ts +106 -0
- package/server/wizard/phases/setup-wizard.ts +78 -0
- package/server/wizard/phases/welcome.ts +39 -0
- package/server/wizard/phases/workflow-setup.ts +28 -0
- package/server/wizard/types.ts +56 -0
- package/server/wizard/ui.ts +92 -0
- package/shared/__fixtures__/workflow-configs.ts +80 -0
- package/shared/api-types.ts +106 -0
- package/shared/onboarding-tour.test.ts +94 -0
- package/shared/project-colors.ts +24 -0
- package/shared/workflow-config.test.ts +111 -0
- package/shared/workflow-config.ts +7 -0
- package/shared/workflow-engine.test.ts +388 -0
- package/shared/workflow-engine.ts +1 -1
- package/shared/workflow-normalizer.test.ts +119 -0
- package/shared/workflow-normalizer.ts +118 -0
- package/templates/agents/QUICK-REFERENCE.md +1 -0
- package/templates/agents/README.md +1 -0
- package/templates/agents/SKILL-TRIGGERS.md +11 -0
- package/templates/agents/green-team/deep-dive.md +361 -0
- package/templates/hooks/end-session.sh +4 -1
- package/templates/hooks/init-session.sh +1 -0
- package/templates/hooks/orbital-emit.sh +2 -2
- package/templates/hooks/orbital-report-deploy.sh +4 -4
- package/templates/hooks/orbital-report-gates.sh +4 -4
- package/templates/hooks/orbital-scope-update.sh +1 -1
- package/templates/hooks/scope-commit-logger.sh +2 -2
- package/templates/hooks/scope-create-cleanup.sh +2 -2
- package/templates/hooks/scope-create-gate.sh +2 -5
- package/templates/hooks/scope-gate.sh +4 -6
- package/templates/hooks/scope-helpers.sh +28 -1
- package/templates/hooks/scope-lifecycle-gate.sh +14 -5
- package/templates/hooks/scope-prepare.sh +67 -12
- package/templates/hooks/scope-transition.sh +14 -6
- package/templates/hooks/time-tracker.sh +2 -5
- package/templates/migrations/renames.json +1 -0
- package/templates/orbital.config.json +8 -6
- package/{shared/default-workflow.json → templates/presets/default.json} +65 -0
- package/templates/presets/development.json +4 -4
- package/templates/presets/gitflow.json +7 -0
- package/templates/prompts/README.md +23 -0
- package/templates/prompts/deep-dive-audit.md +94 -0
- package/templates/quick/rules.md +56 -5
- package/templates/settings-hooks.json +1 -1
- package/templates/skills/git-commit/SKILL.md +27 -7
- package/templates/skills/git-dev/SKILL.md +13 -4
- package/templates/skills/git-main/SKILL.md +13 -3
- package/templates/skills/git-production/SKILL.md +9 -2
- package/templates/skills/git-staging/SKILL.md +11 -3
- package/templates/skills/scope-create/SKILL.md +17 -3
- package/templates/skills/scope-fix-review/SKILL.md +14 -7
- package/templates/skills/scope-implement/SKILL.md +15 -4
- package/templates/skills/scope-post-review/SKILL.md +77 -7
- package/templates/skills/scope-pre-review/SKILL.md +11 -4
- package/templates/skills/scope-verify/SKILL.md +5 -3
- package/templates/skills/test-code-review/SKILL.md +41 -33
- package/templates/skills/test-scaffold/SKILL.md +222 -0
- package/dist/assets/WorkflowVisualizer-BZ21PIIF.js +0 -84
- package/dist/assets/charts-D__PA1zp.js +0 -72
- package/dist/assets/index-D1G6i0nS.css +0 -1
- package/dist/assets/index-DpItvKpf.js +0 -419
- package/dist/assets/ui-BvF022GT.js +0 -53
- package/index.html +0 -15
- package/postcss.config.js +0 -6
- package/src/App.tsx +0 -33
- package/src/components/AgentBadge.tsx +0 -40
- package/src/components/BatchPreflightModal.tsx +0 -115
- package/src/components/CardDisplayToggle.tsx +0 -74
- package/src/components/ColumnHeaderActions.tsx +0 -55
- package/src/components/ColumnMenu.tsx +0 -99
- package/src/components/DeployHistory.tsx +0 -141
- package/src/components/DispatchModal.tsx +0 -164
- package/src/components/DispatchPopover.tsx +0 -139
- package/src/components/DragOverlay.tsx +0 -25
- package/src/components/DriftSidebar.tsx +0 -140
- package/src/components/EnvironmentStrip.tsx +0 -88
- package/src/components/ErrorBoundary.tsx +0 -62
- package/src/components/FilterChip.tsx +0 -105
- package/src/components/GateIndicator.tsx +0 -33
- package/src/components/IdeaDetailModal.tsx +0 -190
- package/src/components/IdeaFormDialog.tsx +0 -113
- package/src/components/KanbanColumn.tsx +0 -201
- package/src/components/MarkdownRenderer.tsx +0 -114
- package/src/components/NeonGrid.tsx +0 -128
- package/src/components/PromotionQueue.tsx +0 -89
- package/src/components/ScopeCard.tsx +0 -234
- package/src/components/ScopeDetailModal.tsx +0 -255
- package/src/components/ScopeFilterBar.tsx +0 -152
- package/src/components/SearchInput.tsx +0 -102
- package/src/components/SessionPanel.tsx +0 -335
- package/src/components/SprintContainer.tsx +0 -303
- package/src/components/SprintDependencyDialog.tsx +0 -78
- package/src/components/SprintPreflightModal.tsx +0 -138
- package/src/components/StatusBar.tsx +0 -168
- package/src/components/SwimCell.tsx +0 -67
- package/src/components/SwimLaneRow.tsx +0 -94
- package/src/components/SwimlaneBoardView.tsx +0 -108
- package/src/components/VersionBadge.tsx +0 -139
- package/src/components/ViewModeSelector.tsx +0 -114
- package/src/components/config/AgentChip.tsx +0 -53
- package/src/components/config/AgentCreateDialog.tsx +0 -321
- package/src/components/config/AgentEditor.tsx +0 -175
- package/src/components/config/DirectoryTree.tsx +0 -582
- package/src/components/config/FileEditor.tsx +0 -550
- package/src/components/config/HookChip.tsx +0 -50
- package/src/components/config/StageCard.tsx +0 -198
- package/src/components/config/TransitionZone.tsx +0 -173
- package/src/components/config/UnifiedWorkflowPipeline.tsx +0 -216
- package/src/components/config/WorkflowPipeline.tsx +0 -161
- package/src/components/source-control/BranchList.tsx +0 -93
- package/src/components/source-control/BranchPanel.tsx +0 -105
- package/src/components/source-control/CommitLog.tsx +0 -100
- package/src/components/source-control/CommitRow.tsx +0 -47
- package/src/components/source-control/GitHubPanel.tsx +0 -110
- package/src/components/source-control/GitHubSetupGuide.tsx +0 -52
- package/src/components/source-control/GitOverviewBar.tsx +0 -101
- package/src/components/source-control/PullRequestList.tsx +0 -69
- package/src/components/source-control/WorktreeList.tsx +0 -80
- package/src/components/ui/badge.tsx +0 -41
- package/src/components/ui/button.tsx +0 -55
- package/src/components/ui/card.tsx +0 -78
- package/src/components/ui/dialog.tsx +0 -94
- package/src/components/ui/popover.tsx +0 -33
- package/src/components/ui/scroll-area.tsx +0 -54
- package/src/components/ui/separator.tsx +0 -28
- package/src/components/ui/tabs.tsx +0 -52
- package/src/components/ui/toggle-switch.tsx +0 -35
- package/src/components/ui/tooltip.tsx +0 -27
- package/src/components/workflow/AddEdgeDialog.tsx +0 -217
- package/src/components/workflow/AddListDialog.tsx +0 -201
- package/src/components/workflow/ChecklistEditor.tsx +0 -239
- package/src/components/workflow/CommandPrefixManager.tsx +0 -118
- package/src/components/workflow/ConfigSettingsPanel.tsx +0 -189
- package/src/components/workflow/DirectionSelector.tsx +0 -133
- package/src/components/workflow/DispatchConfigPanel.tsx +0 -180
- package/src/components/workflow/EdgeDetailPanel.tsx +0 -236
- package/src/components/workflow/EdgePropertyEditor.tsx +0 -251
- package/src/components/workflow/EditToolbar.tsx +0 -138
- package/src/components/workflow/HookDetailPanel.tsx +0 -250
- package/src/components/workflow/HookExecutionLog.tsx +0 -24
- package/src/components/workflow/HookSourceModal.tsx +0 -129
- package/src/components/workflow/HooksDashboard.tsx +0 -363
- package/src/components/workflow/ListPropertyEditor.tsx +0 -251
- package/src/components/workflow/MigrationPreviewDialog.tsx +0 -237
- package/src/components/workflow/MovementRulesPanel.tsx +0 -188
- package/src/components/workflow/NodeDetailPanel.tsx +0 -245
- package/src/components/workflow/PresetSelector.tsx +0 -414
- package/src/components/workflow/SkillCommandBuilder.tsx +0 -174
- package/src/components/workflow/WorkflowEdgeComponent.tsx +0 -145
- package/src/components/workflow/WorkflowNode.tsx +0 -147
- package/src/components/workflow/graphLayout.ts +0 -186
- package/src/components/workflow/mergeHooks.ts +0 -85
- package/src/components/workflow/useEditHistory.ts +0 -88
- package/src/components/workflow/useWorkflowEditor.ts +0 -262
- package/src/components/workflow/validateConfig.ts +0 -70
- package/src/hooks/useActiveDispatches.ts +0 -198
- package/src/hooks/useBoardSettings.ts +0 -170
- package/src/hooks/useCardDisplay.ts +0 -57
- package/src/hooks/useCcHooks.ts +0 -24
- package/src/hooks/useConfigTree.ts +0 -51
- package/src/hooks/useEnforcementRules.ts +0 -46
- package/src/hooks/useEvents.ts +0 -59
- package/src/hooks/useFileEditor.ts +0 -165
- package/src/hooks/useGates.ts +0 -57
- package/src/hooks/useIdeaActions.ts +0 -53
- package/src/hooks/useKanbanDnd.ts +0 -410
- package/src/hooks/useOrbitalConfig.ts +0 -54
- package/src/hooks/usePipeline.ts +0 -47
- package/src/hooks/usePipelineData.ts +0 -338
- package/src/hooks/useReconnect.ts +0 -25
- package/src/hooks/useScopeFilters.ts +0 -125
- package/src/hooks/useScopeSessions.ts +0 -44
- package/src/hooks/useScopes.ts +0 -67
- package/src/hooks/useSearch.ts +0 -67
- package/src/hooks/useSettings.tsx +0 -187
- package/src/hooks/useSocket.ts +0 -25
- package/src/hooks/useSourceControl.ts +0 -105
- package/src/hooks/useSprintPreflight.ts +0 -55
- package/src/hooks/useSprints.ts +0 -154
- package/src/hooks/useStatusBarHighlight.ts +0 -18
- package/src/hooks/useSwimlaneBoardSettings.ts +0 -104
- package/src/hooks/useTheme.ts +0 -9
- package/src/hooks/useTransitionReadiness.ts +0 -53
- package/src/hooks/useVersion.ts +0 -155
- package/src/hooks/useViolations.ts +0 -65
- package/src/hooks/useWorkflow.tsx +0 -125
- package/src/hooks/useZoomModifier.ts +0 -19
- package/src/index.css +0 -797
- package/src/layouts/DashboardLayout.tsx +0 -113
- package/src/lib/collisionDetection.ts +0 -20
- package/src/lib/scope-fields.ts +0 -61
- package/src/lib/swimlane.ts +0 -146
- package/src/lib/utils.ts +0 -15
- package/src/main.tsx +0 -19
- package/src/socket.ts +0 -11
- package/src/types/index.ts +0 -497
- package/src/views/AgentFeed.tsx +0 -339
- package/src/views/DeployPipeline.tsx +0 -59
- package/src/views/EnforcementView.tsx +0 -378
- package/src/views/PrimitivesConfig.tsx +0 -500
- package/src/views/QualityGates.tsx +0 -1012
- package/src/views/ScopeBoard.tsx +0 -454
- package/src/views/SessionTimeline.tsx +0 -516
- package/src/views/Settings.tsx +0 -183
- package/src/views/SourceControl.tsx +0 -95
- package/src/views/WorkflowVisualizer.tsx +0 -382
- package/tailwind.config.js +0 -161
- package/tsconfig.json +0 -25
- package/vite.config.ts +0 -38
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { ScopeService } from './scope-service.js';
|
|
3
|
+
import { ScopeCache } from './scope-cache.js';
|
|
4
|
+
import { WorkflowEngine } from '../../shared/workflow-engine.js';
|
|
5
|
+
import { CONFIG_WITH_HOOKS } from '../../shared/__fixtures__/workflow-configs.js';
|
|
6
|
+
import { createMockEmitter } from '../__tests__/helpers/mock-emitter.js';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import os from 'os';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
describe('ScopeService', () => {
|
|
11
|
+
let tmpDir;
|
|
12
|
+
let cache;
|
|
13
|
+
let emitter;
|
|
14
|
+
let engine;
|
|
15
|
+
let service;
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'scope-svc-test-'));
|
|
18
|
+
cache = new ScopeCache();
|
|
19
|
+
emitter = createMockEmitter();
|
|
20
|
+
engine = new WorkflowEngine(CONFIG_WITH_HOOKS);
|
|
21
|
+
service = new ScopeService(cache, emitter, tmpDir, engine);
|
|
22
|
+
// Create status directories
|
|
23
|
+
for (const status of ['icebox', 'backlog', 'active', 'review', 'shipped']) {
|
|
24
|
+
fs.mkdirSync(path.join(tmpDir, status), { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
29
|
+
});
|
|
30
|
+
function writeScopeFile(status, filename, frontmatter, body = '') {
|
|
31
|
+
const filePath = path.join(tmpDir, status, filename);
|
|
32
|
+
const yamlLines = Object.entries(frontmatter).map(([k, v]) => {
|
|
33
|
+
if (Array.isArray(v))
|
|
34
|
+
return `${k}: [${v.join(', ')}]`;
|
|
35
|
+
return `${k}: ${v}`;
|
|
36
|
+
}).join('\n');
|
|
37
|
+
fs.writeFileSync(filePath, `---\n${yamlLines}\n---\n${body}\n`);
|
|
38
|
+
return filePath;
|
|
39
|
+
}
|
|
40
|
+
// ─── syncFromFilesystem() ─────────────────────────────────
|
|
41
|
+
describe('syncFromFilesystem()', () => {
|
|
42
|
+
it('loads all .md files into cache', () => {
|
|
43
|
+
writeScopeFile('backlog', '001-first.md', { title: 'First', status: 'backlog' });
|
|
44
|
+
writeScopeFile('active', '002-second.md', { title: 'Second', status: 'active' });
|
|
45
|
+
const count = service.syncFromFilesystem();
|
|
46
|
+
expect(count).toBe(2);
|
|
47
|
+
expect(cache.size).toBe(2);
|
|
48
|
+
});
|
|
49
|
+
it('returns 0 for empty directories', () => {
|
|
50
|
+
expect(service.syncFromFilesystem()).toBe(0);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
// ─── getAll() / getById() ─────────────────────────────────
|
|
54
|
+
describe('getAll() / getById()', () => {
|
|
55
|
+
it('delegates to cache', () => {
|
|
56
|
+
writeScopeFile('backlog', '001-test.md', { title: 'Test', status: 'backlog' });
|
|
57
|
+
service.syncFromFilesystem();
|
|
58
|
+
expect(service.getAll()).toHaveLength(1);
|
|
59
|
+
expect(service.getById(1)?.title).toBe('Test');
|
|
60
|
+
expect(service.getById(999)).toBeUndefined();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
// ─── updateStatus() ──────────────────────────────────────
|
|
64
|
+
describe('updateStatus()', () => {
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
writeScopeFile('backlog', '001-test.md', { title: 'Test', status: 'backlog' });
|
|
67
|
+
service.syncFromFilesystem();
|
|
68
|
+
});
|
|
69
|
+
it('validates transition via engine', () => {
|
|
70
|
+
// backlog → shipped is not a valid edge in CONFIG_WITH_HOOKS
|
|
71
|
+
const result = service.updateStatus(1, 'shipped', 'patch');
|
|
72
|
+
expect(result.ok).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
it('bulk-sync bypasses validation', () => {
|
|
75
|
+
const result = service.updateStatus(1, 'shipped', 'bulk-sync');
|
|
76
|
+
expect(result.ok).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
it('returns NOT_FOUND for unknown scope', () => {
|
|
79
|
+
const result = service.updateStatus(999, 'active', 'dispatch');
|
|
80
|
+
expect(result.ok).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
it('fires onStatusChange callbacks on successful transition', () => {
|
|
83
|
+
const callback = vi.fn();
|
|
84
|
+
service.onStatusChange(callback);
|
|
85
|
+
// Use bulk-sync to bypass edge validation — focuses on the callback mechanism
|
|
86
|
+
const result = service.updateStatus(1, 'active', 'bulk-sync');
|
|
87
|
+
if (result.ok) {
|
|
88
|
+
expect(callback).toHaveBeenCalledWith(1, 'active');
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
// ─── createIdeaFile() ─────────────────────────────────────
|
|
93
|
+
describe('createIdeaFile()', () => {
|
|
94
|
+
it('creates file in icebox directory', () => {
|
|
95
|
+
const result = service.createIdeaFile('My New Idea', 'A description of the idea');
|
|
96
|
+
expect(result.slug).toBeDefined();
|
|
97
|
+
expect(result.title).toBe('My New Idea');
|
|
98
|
+
// Verify file exists
|
|
99
|
+
const files = fs.readdirSync(path.join(tmpDir, 'icebox'));
|
|
100
|
+
expect(files.length).toBe(1);
|
|
101
|
+
expect(files[0]).toMatch(/\.md$/);
|
|
102
|
+
});
|
|
103
|
+
it('slugifies the title', () => {
|
|
104
|
+
const result = service.createIdeaFile('Some Feature Idea!', '');
|
|
105
|
+
expect(result.slug).toMatch(/^[a-z0-9-]+$/);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
// ─── deleteIdeaFile() ─────────────────────────────────────
|
|
109
|
+
describe('deleteIdeaFile()', () => {
|
|
110
|
+
it('removes the idea file', () => {
|
|
111
|
+
const { slug } = service.createIdeaFile('To Delete', '');
|
|
112
|
+
const result = service.deleteIdeaFile(slug);
|
|
113
|
+
expect(result).toBe(true);
|
|
114
|
+
const files = fs.readdirSync(path.join(tmpDir, 'icebox'));
|
|
115
|
+
expect(files).toHaveLength(0);
|
|
116
|
+
});
|
|
117
|
+
it('returns false for non-existent slug', () => {
|
|
118
|
+
expect(service.deleteIdeaFile('nonexistent-slug')).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
// ─── reconcileDirectories() ───────────────────────────────
|
|
122
|
+
describe('reconcileDirectories()', () => {
|
|
123
|
+
it('moves files to correct directory based on frontmatter', () => {
|
|
124
|
+
// Put a file in backlog but with status: active in frontmatter
|
|
125
|
+
writeScopeFile('backlog', '001-misplaced.md', { title: 'Misplaced', status: 'active' });
|
|
126
|
+
service.syncFromFilesystem();
|
|
127
|
+
const moved = service.reconcileDirectories();
|
|
128
|
+
expect(moved).toBeGreaterThanOrEqual(0);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
// ─── isSuppressed() ──────────────────────────────────────
|
|
132
|
+
describe('isSuppressed()', () => {
|
|
133
|
+
it('returns false by default', () => {
|
|
134
|
+
expect(service.isSuppressed('/some/path.md')).toBe(false);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { launchInCategorizedTerminal, escapeForAnsiC, buildSessionName, snapshotSessionPids, discoverNewSession, renameSession } from '../utils/terminal-launcher.js';
|
|
1
|
+
import { launchInCategorizedTerminal, escapeForAnsiC, shellQuote, buildSessionName, snapshotSessionPids, discoverNewSession, renameSession } from '../utils/terminal-launcher.js';
|
|
2
2
|
import { resolveDispatchEvent, linkPidToDispatch } from '../utils/dispatch-utils.js';
|
|
3
|
-
import {
|
|
3
|
+
import { buildClaudeFlags, buildEnvVarPrefix } from '../utils/flag-builder.js';
|
|
4
4
|
import { createLogger } from '../utils/logger.js';
|
|
5
5
|
const log = createLogger('sprint');
|
|
6
6
|
const LAUNCH_STAGGER_MS = 2000;
|
|
@@ -14,12 +14,16 @@ export class SprintOrchestrator {
|
|
|
14
14
|
sprintService;
|
|
15
15
|
scopeService;
|
|
16
16
|
engine;
|
|
17
|
-
|
|
17
|
+
projectRoot;
|
|
18
|
+
config;
|
|
19
|
+
constructor(db, io, sprintService, scopeService, engine, projectRoot, config) {
|
|
18
20
|
this.db = db;
|
|
19
21
|
this.io = io;
|
|
20
22
|
this.sprintService = sprintService;
|
|
21
23
|
this.scopeService = scopeService;
|
|
22
24
|
this.engine = engine;
|
|
25
|
+
this.projectRoot = projectRoot;
|
|
26
|
+
this.config = config;
|
|
23
27
|
}
|
|
24
28
|
/** Build execution layers using Kahn's topological sort */
|
|
25
29
|
buildExecutionLayers(sprintScopeIds) {
|
|
@@ -97,13 +101,14 @@ export class SprintOrchestrator {
|
|
|
97
101
|
const match = this.sprintService.findActiveSprintForScope(scopeId);
|
|
98
102
|
if (!match)
|
|
99
103
|
return;
|
|
100
|
-
|
|
104
|
+
// Batches are managed by BatchOrchestrator — don't dispatch individual scopes
|
|
101
105
|
const sprintId = match.sprint_id;
|
|
102
|
-
this.sprintService.updateScopeStatus(sprintId, scopeId, 'completed');
|
|
103
|
-
// Ensure sprint is in 'in_progress' state
|
|
104
106
|
const sprint = this.sprintService.getById(sprintId);
|
|
105
|
-
if (!sprint)
|
|
107
|
+
if (!sprint || sprint.group_type === 'batch')
|
|
106
108
|
return;
|
|
109
|
+
log.debug('Scope reached dev', { scopeId, sprintId });
|
|
110
|
+
this.sprintService.updateScopeStatus(sprintId, scopeId, 'completed');
|
|
111
|
+
// Ensure sprint is in 'in_progress' state
|
|
107
112
|
if (sprint.status === 'dispatched') {
|
|
108
113
|
this.sprintService.updateStatus(sprintId, 'in_progress');
|
|
109
114
|
}
|
|
@@ -168,7 +173,7 @@ export class SprintOrchestrator {
|
|
|
168
173
|
const sprint = this.sprintService.getById(sprintId);
|
|
169
174
|
if (!sprint)
|
|
170
175
|
return null;
|
|
171
|
-
const layers = sprint.layers ??
|
|
176
|
+
const layers = sprint.layers ?? this.buildExecutionLayers(sprint.scope_ids).layers;
|
|
172
177
|
const sprintSet = new Set(sprint.scope_ids);
|
|
173
178
|
const edges = [];
|
|
174
179
|
for (const scopeId of sprint.scope_ids) {
|
|
@@ -191,9 +196,14 @@ export class SprintOrchestrator {
|
|
|
191
196
|
// Capture current status before optimistic update (for rollback)
|
|
192
197
|
const currentScope = this.scopeService.getById(scopeId);
|
|
193
198
|
const previousStatus = currentScope?.status ?? 'implementing';
|
|
199
|
+
// Resolve command and target status from workflow engine
|
|
200
|
+
const sprint = this.sprintService.getById(sprintId);
|
|
201
|
+
const targetColumn = sprint?.target_column ?? 'backlog';
|
|
202
|
+
const edgeCommand = this.engine.getBatchCommand(targetColumn);
|
|
203
|
+
const targetStatus = this.engine.getBatchTargetStatus(targetColumn);
|
|
194
204
|
// Record DISPATCH event
|
|
195
205
|
const eventId = crypto.randomUUID();
|
|
196
|
-
const command = `/scope
|
|
206
|
+
const command = edgeCommand ?? `/scope-implement ${scopeId}`;
|
|
197
207
|
this.db.prepare(`INSERT INTO events (id, type, scope_id, session_id, agent, data, timestamp)
|
|
198
208
|
VALUES (?, 'DISPATCH', ?, NULL, 'sprint-orchestrator', ?, ?)`).run(eventId, scopeId, JSON.stringify({ command, sprint_id: sprintId, resolved: null }), new Date().toISOString());
|
|
199
209
|
this.io.emit('event:new', {
|
|
@@ -203,25 +213,29 @@ export class SprintOrchestrator {
|
|
|
203
213
|
timestamp: new Date().toISOString(),
|
|
204
214
|
});
|
|
205
215
|
// Update scope + sprint_scope status
|
|
206
|
-
|
|
216
|
+
if (targetStatus) {
|
|
217
|
+
this.scopeService.updateStatus(scopeId, targetStatus, 'dispatch');
|
|
218
|
+
}
|
|
207
219
|
this.sprintService.updateScopeStatus(sprintId, scopeId, 'dispatched');
|
|
208
220
|
// Build scope-aware session name and snapshot PIDs
|
|
209
221
|
const scopeRow = this.scopeService.getById(scopeId);
|
|
210
222
|
const sessionName = buildSessionName({ scopeId, title: scopeRow?.title, command });
|
|
211
|
-
const beforePids = snapshotSessionPids(
|
|
212
|
-
// Launch in iTerm — interactive TUI mode
|
|
223
|
+
const beforePids = snapshotSessionPids(this.projectRoot);
|
|
224
|
+
// Launch in iTerm — interactive TUI mode for full visibility
|
|
213
225
|
const escaped = escapeForAnsiC(command);
|
|
214
|
-
const
|
|
226
|
+
const flagsStr = buildClaudeFlags(this.config.claude.dispatchFlags);
|
|
227
|
+
const envPrefix = buildEnvVarPrefix(this.config.dispatch.envVars);
|
|
228
|
+
const fullCmd = `cd '${shellQuote(this.projectRoot)}' && ${envPrefix}ORBITAL_DISPATCH_ID='${shellQuote(eventId)}' claude ${flagsStr} $'${escaped}'`;
|
|
215
229
|
try {
|
|
216
230
|
await launchInCategorizedTerminal(command, fullCmd, sessionName);
|
|
217
231
|
// Fire-and-forget: discover session PID, link to dispatch, and rename
|
|
218
|
-
discoverNewSession(
|
|
232
|
+
discoverNewSession(this.projectRoot, beforePids)
|
|
219
233
|
.then((session) => {
|
|
220
234
|
if (!session)
|
|
221
235
|
return;
|
|
222
236
|
linkPidToDispatch(this.db, eventId, session.pid);
|
|
223
237
|
if (sessionName)
|
|
224
|
-
renameSession(
|
|
238
|
+
renameSession(this.projectRoot, session.sessionId, sessionName);
|
|
225
239
|
})
|
|
226
240
|
.catch(err => log.error('PID discovery failed', { error: err.message }));
|
|
227
241
|
}
|
|
@@ -47,7 +47,24 @@ export class SprintService {
|
|
|
47
47
|
else {
|
|
48
48
|
rows = this.db.prepare('SELECT * FROM sprints ORDER BY created_at DESC').all();
|
|
49
49
|
}
|
|
50
|
-
|
|
50
|
+
if (rows.length === 0)
|
|
51
|
+
return [];
|
|
52
|
+
// Batch-fetch all sprint_scopes in one query to avoid N+1
|
|
53
|
+
const sprintIds = rows.map(r => r.id);
|
|
54
|
+
const placeholders = sprintIds.map(() => '?').join(',');
|
|
55
|
+
const allScopeRows = this.db.prepare(`SELECT sprint_id, scope_id, layer, dispatch_status FROM sprint_scopes
|
|
56
|
+
WHERE sprint_id IN (${placeholders}) ORDER BY layer ASC, scope_id ASC`).all(...sprintIds);
|
|
57
|
+
// Group by sprint_id
|
|
58
|
+
const scopesBySprintId = new Map();
|
|
59
|
+
for (const ss of allScopeRows) {
|
|
60
|
+
let arr = scopesBySprintId.get(ss.sprint_id);
|
|
61
|
+
if (!arr) {
|
|
62
|
+
arr = [];
|
|
63
|
+
scopesBySprintId.set(ss.sprint_id, arr);
|
|
64
|
+
}
|
|
65
|
+
arr.push(ss);
|
|
66
|
+
}
|
|
67
|
+
return rows.map((row) => this.buildDetailFromScopes(row, scopesBySprintId.get(row.id) ?? []));
|
|
51
68
|
}
|
|
52
69
|
/** Get full sprint detail by ID */
|
|
53
70
|
getById(id) {
|
|
@@ -56,10 +73,10 @@ export class SprintService {
|
|
|
56
73
|
return null;
|
|
57
74
|
return this.buildDetail(row);
|
|
58
75
|
}
|
|
59
|
-
/** Delete a sprint (
|
|
76
|
+
/** Delete a sprint/batch (assembling, failed, or cancelled) */
|
|
60
77
|
delete(id) {
|
|
61
78
|
const row = this.db.prepare('SELECT status FROM sprints WHERE id = ?').get(id);
|
|
62
|
-
if (!row ||
|
|
79
|
+
if (!row || !['assembling', 'failed', 'cancelled', 'completed'].includes(row.status))
|
|
63
80
|
return false;
|
|
64
81
|
this.db.prepare('DELETE FROM sprint_scopes WHERE sprint_id = ?').run(id);
|
|
65
82
|
this.db.prepare('DELETE FROM sprints WHERE id = ?').run(id);
|
|
@@ -231,6 +248,9 @@ export class SprintService {
|
|
|
231
248
|
buildDetail(row) {
|
|
232
249
|
const ssRows = this.db.prepare(`SELECT scope_id, layer, dispatch_status FROM sprint_scopes
|
|
233
250
|
WHERE sprint_id = ? ORDER BY layer ASC, scope_id ASC`).all(row.id);
|
|
251
|
+
return this.buildDetailFromScopes(row, ssRows);
|
|
252
|
+
}
|
|
253
|
+
buildDetailFromScopes(row, ssRows) {
|
|
234
254
|
const progress = { pending: 0, in_progress: 0, completed: 0, failed: 0, skipped: 0 };
|
|
235
255
|
const scopes = [];
|
|
236
256
|
for (const ss of ssRows) {
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { SprintService } from './sprint-service.js';
|
|
3
|
+
import { createTestDb } from '../__tests__/helpers/db.js';
|
|
4
|
+
import { createMockEmitter } from '../__tests__/helpers/mock-emitter.js';
|
|
5
|
+
function makeScope(overrides) {
|
|
6
|
+
return {
|
|
7
|
+
title: `Scope ${overrides.id}`,
|
|
8
|
+
slug: undefined,
|
|
9
|
+
status: 'backlog',
|
|
10
|
+
priority: null,
|
|
11
|
+
effort_estimate: null,
|
|
12
|
+
category: null,
|
|
13
|
+
tags: [],
|
|
14
|
+
blocked_by: [],
|
|
15
|
+
blocks: [],
|
|
16
|
+
file_path: `/scopes/backlog/${String(overrides.id).padStart(3, '0')}-test.md`,
|
|
17
|
+
created_at: null,
|
|
18
|
+
updated_at: null,
|
|
19
|
+
raw_content: '',
|
|
20
|
+
sessions: {},
|
|
21
|
+
is_ghost: false,
|
|
22
|
+
favourite: false,
|
|
23
|
+
...overrides,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function createMockScopeService(scopes) {
|
|
27
|
+
return { getById: (id) => scopes.find(s => s.id === id) };
|
|
28
|
+
}
|
|
29
|
+
describe('SprintService', () => {
|
|
30
|
+
let db;
|
|
31
|
+
let cleanup;
|
|
32
|
+
let emitter;
|
|
33
|
+
let service;
|
|
34
|
+
const testScopes = [
|
|
35
|
+
makeScope({ id: 1, title: 'Scope 1', status: 'backlog' }),
|
|
36
|
+
makeScope({ id: 2, title: 'Scope 2', status: 'backlog' }),
|
|
37
|
+
makeScope({ id: 3, title: 'Scope 3', status: 'backlog', blocked_by: [99] }),
|
|
38
|
+
makeScope({ id: 4, title: 'Scope 4', status: 'implementing' }),
|
|
39
|
+
];
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
({ db, cleanup } = createTestDb());
|
|
42
|
+
// Add migration columns that SCHEMA_DDL may not include
|
|
43
|
+
try {
|
|
44
|
+
db.prepare("ALTER TABLE sprints ADD COLUMN target_column TEXT DEFAULT 'backlog'").run();
|
|
45
|
+
}
|
|
46
|
+
catch { /* already exists */ }
|
|
47
|
+
try {
|
|
48
|
+
db.prepare("ALTER TABLE sprints ADD COLUMN group_type TEXT DEFAULT 'sprint'").run();
|
|
49
|
+
}
|
|
50
|
+
catch { /* already exists */ }
|
|
51
|
+
try {
|
|
52
|
+
db.prepare("ALTER TABLE sprints ADD COLUMN dispatch_result TEXT DEFAULT '{}'").run();
|
|
53
|
+
}
|
|
54
|
+
catch { /* already exists */ }
|
|
55
|
+
emitter = createMockEmitter();
|
|
56
|
+
const mockScopeService = createMockScopeService(testScopes);
|
|
57
|
+
service = new SprintService(db, emitter, mockScopeService);
|
|
58
|
+
});
|
|
59
|
+
afterEach(() => {
|
|
60
|
+
cleanup?.();
|
|
61
|
+
});
|
|
62
|
+
// ─── create() ─────────────────────────────────────────────
|
|
63
|
+
describe('create()', () => {
|
|
64
|
+
it('creates sprint with default values', () => {
|
|
65
|
+
const sprint = service.create('Test Sprint');
|
|
66
|
+
expect(sprint.name).toBe('Test Sprint');
|
|
67
|
+
expect(sprint.status).toBe('assembling');
|
|
68
|
+
expect(sprint.concurrency_cap).toBe(5);
|
|
69
|
+
expect(sprint.group_type).toBe('sprint');
|
|
70
|
+
expect(sprint.target_column).toBe('backlog');
|
|
71
|
+
});
|
|
72
|
+
it('creates batch with custom options', () => {
|
|
73
|
+
const sprint = service.create('Test Batch', { group_type: 'batch', target_column: 'review' });
|
|
74
|
+
expect(sprint.group_type).toBe('batch');
|
|
75
|
+
expect(sprint.target_column).toBe('review');
|
|
76
|
+
});
|
|
77
|
+
it('emits sprint:created', () => {
|
|
78
|
+
service.create('Test Sprint');
|
|
79
|
+
expect(emitter.emit).toHaveBeenCalledWith('sprint:created', expect.objectContaining({ name: 'Test Sprint' }));
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
// ─── getAll() / getById() ─────────────────────────────────
|
|
83
|
+
describe('getAll() / getById()', () => {
|
|
84
|
+
it('returns all sprints', () => {
|
|
85
|
+
service.create('Sprint 1');
|
|
86
|
+
service.create('Sprint 2');
|
|
87
|
+
expect(service.getAll()).toHaveLength(2);
|
|
88
|
+
});
|
|
89
|
+
it('filters by status', () => {
|
|
90
|
+
service.create('Sprint 1');
|
|
91
|
+
const all = service.getAll('assembling');
|
|
92
|
+
expect(all).toHaveLength(1);
|
|
93
|
+
expect(service.getAll('dispatched')).toHaveLength(0);
|
|
94
|
+
});
|
|
95
|
+
it('returns null for unknown ID', () => {
|
|
96
|
+
expect(service.getById(999)).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
// ─── rename() / delete() ──────────────────────────────────
|
|
100
|
+
describe('rename()', () => {
|
|
101
|
+
it('renames an assembling sprint', () => {
|
|
102
|
+
const sprint = service.create('Original');
|
|
103
|
+
const result = service.rename(sprint.id, 'Renamed');
|
|
104
|
+
expect(result).toBe(true);
|
|
105
|
+
expect(service.getById(sprint.id).name).toBe('Renamed');
|
|
106
|
+
});
|
|
107
|
+
it('returns false for non-assembling sprint', () => {
|
|
108
|
+
const sprint = service.create('Sprint');
|
|
109
|
+
service.updateStatus(sprint.id, 'dispatched');
|
|
110
|
+
expect(service.rename(sprint.id, 'New Name')).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe('delete()', () => {
|
|
114
|
+
it('deletes sprint and emits event', () => {
|
|
115
|
+
const sprint = service.create('To Delete');
|
|
116
|
+
const result = service.delete(sprint.id);
|
|
117
|
+
expect(result).toBe(true);
|
|
118
|
+
expect(service.getById(sprint.id)).toBeNull();
|
|
119
|
+
expect(emitter.emit).toHaveBeenCalledWith('sprint:deleted', expect.objectContaining({ id: sprint.id }));
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
// ─── addScopes() ──────────────────────────────────────────
|
|
123
|
+
describe('addScopes()', () => {
|
|
124
|
+
it('adds scopes to assembling sprint', () => {
|
|
125
|
+
const sprint = service.create('Sprint');
|
|
126
|
+
const result = service.addScopes(sprint.id, [1, 2]);
|
|
127
|
+
expect(result).not.toBeNull();
|
|
128
|
+
expect(result.added).toEqual([1, 2]);
|
|
129
|
+
});
|
|
130
|
+
it('returns null for non-existent sprint', () => {
|
|
131
|
+
expect(service.addScopes(999, [1])).toBeNull();
|
|
132
|
+
});
|
|
133
|
+
it('reports unmet dependencies', () => {
|
|
134
|
+
const sprint = service.create('Sprint');
|
|
135
|
+
const result = service.addScopes(sprint.id, [3]); // scope 3 is blocked_by [99]
|
|
136
|
+
expect(result).not.toBeNull();
|
|
137
|
+
expect(result.unmet_dependencies.length).toBeGreaterThanOrEqual(0);
|
|
138
|
+
});
|
|
139
|
+
it('skips scopes that are already in the sprint', () => {
|
|
140
|
+
const sprint = service.create('Sprint');
|
|
141
|
+
service.addScopes(sprint.id, [1]);
|
|
142
|
+
const result = service.addScopes(sprint.id, [1, 2]);
|
|
143
|
+
expect(result).not.toBeNull();
|
|
144
|
+
expect(result.added).toEqual([2]);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
// ─── removeScopes() ──────────────────────────────────────
|
|
148
|
+
describe('removeScopes()', () => {
|
|
149
|
+
it('removes scopes from assembling sprint', () => {
|
|
150
|
+
const sprint = service.create('Sprint');
|
|
151
|
+
service.addScopes(sprint.id, [1, 2]);
|
|
152
|
+
const result = service.removeScopes(sprint.id, [1]);
|
|
153
|
+
expect(result).toBe(true);
|
|
154
|
+
const updated = service.getById(sprint.id);
|
|
155
|
+
expect(updated.scope_ids).not.toContain(1);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
// ─── updateStatus() ──────────────────────────────────────
|
|
159
|
+
describe('updateStatus()', () => {
|
|
160
|
+
it('transitions to dispatched and sets dispatched_at', () => {
|
|
161
|
+
const sprint = service.create('Sprint');
|
|
162
|
+
service.addScopes(sprint.id, [1]);
|
|
163
|
+
service.updateStatus(sprint.id, 'dispatched');
|
|
164
|
+
const updated = service.getById(sprint.id);
|
|
165
|
+
expect(updated.status).toBe('dispatched');
|
|
166
|
+
expect(updated.dispatched_at).not.toBeNull();
|
|
167
|
+
});
|
|
168
|
+
it('transitions to completed and sets completed_at', () => {
|
|
169
|
+
const sprint = service.create('Sprint');
|
|
170
|
+
service.updateStatus(sprint.id, 'completed');
|
|
171
|
+
const updated = service.getById(sprint.id);
|
|
172
|
+
expect(updated.status).toBe('completed');
|
|
173
|
+
expect(updated.completed_at).not.toBeNull();
|
|
174
|
+
});
|
|
175
|
+
it('emits sprint:updated on status change', () => {
|
|
176
|
+
const sprint = service.create('Sprint');
|
|
177
|
+
emitter.emit.mockClear();
|
|
178
|
+
service.updateStatus(sprint.id, 'dispatched');
|
|
179
|
+
expect(emitter.emit).toHaveBeenCalledWith('sprint:updated', expect.objectContaining({ status: 'dispatched' }));
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
// ─── updateScopeStatus() ─────────────────────────────────
|
|
183
|
+
describe('updateScopeStatus()', () => {
|
|
184
|
+
it('updates dispatch_status for a scope in sprint', () => {
|
|
185
|
+
const sprint = service.create('Sprint');
|
|
186
|
+
service.addScopes(sprint.id, [1]);
|
|
187
|
+
service.updateScopeStatus(sprint.id, 1, 'in_progress');
|
|
188
|
+
const updated = service.getById(sprint.id);
|
|
189
|
+
const scope = updated.scopes.find(s => s.scope_id === 1);
|
|
190
|
+
expect(scope?.dispatch_status).toBe('in_progress');
|
|
191
|
+
});
|
|
192
|
+
it('records error for failed scopes', () => {
|
|
193
|
+
const sprint = service.create('Sprint');
|
|
194
|
+
service.addScopes(sprint.id, [1]);
|
|
195
|
+
service.updateScopeStatus(sprint.id, 1, 'failed', 'Timeout');
|
|
196
|
+
const rows = db.prepare('SELECT error FROM sprint_scopes WHERE scope_id = 1').all();
|
|
197
|
+
expect(rows[0].error).toBe('Timeout');
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
// ─── setLayers() ──────────────────────────────────────────
|
|
201
|
+
describe('setLayers()', () => {
|
|
202
|
+
it('stores layer assignment', () => {
|
|
203
|
+
const sprint = service.create('Sprint');
|
|
204
|
+
service.addScopes(sprint.id, [1, 2]);
|
|
205
|
+
service.setLayers(sprint.id, [[1], [2]]);
|
|
206
|
+
const updated = service.getById(sprint.id);
|
|
207
|
+
expect(updated.layers).toEqual([[1], [2]]);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
// ─── findActiveSprintForScope() ───────────────────────────
|
|
211
|
+
describe('findActiveSprintForScope()', () => {
|
|
212
|
+
it('returns active sprint containing the scope', () => {
|
|
213
|
+
const sprint = service.create('Sprint');
|
|
214
|
+
service.addScopes(sprint.id, [1]);
|
|
215
|
+
service.updateStatus(sprint.id, 'dispatched');
|
|
216
|
+
const active = service.findActiveSprintForScope(1);
|
|
217
|
+
expect(active).not.toBeNull();
|
|
218
|
+
expect(active.sprint_id).toBe(sprint.id);
|
|
219
|
+
});
|
|
220
|
+
it('returns null/undefined when scope is not in any active sprint', () => {
|
|
221
|
+
expect(service.findActiveSprintForScope(99)).toBeFalsy();
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
// ─── getActiveGroupForScope() ─────────────────────────────
|
|
225
|
+
describe('getActiveGroupForScope()', () => {
|
|
226
|
+
it('returns group info for scope in active sprint', () => {
|
|
227
|
+
const sprint = service.create('Batch', { group_type: 'batch' });
|
|
228
|
+
service.addScopes(sprint.id, [1]);
|
|
229
|
+
service.updateStatus(sprint.id, 'dispatched');
|
|
230
|
+
const group = service.getActiveGroupForScope(1);
|
|
231
|
+
expect(group).not.toBeNull();
|
|
232
|
+
expect(group.group_type).toBe('batch');
|
|
233
|
+
});
|
|
234
|
+
it('returns null/undefined when scope is not in any active group', () => {
|
|
235
|
+
expect(service.getActiveGroupForScope(99)).toBeFalsy();
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
});
|