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