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