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
|
@@ -108,5 +108,82 @@ export function createGitRoutes({ gitService, githubService, engine }: GitRoutes
|
|
|
108
108
|
}
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
+
// ─── GitHub Auth Flow ──────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
router.post('/github/connect', async (req, res) => {
|
|
114
|
+
try {
|
|
115
|
+
const { method, token } = req.body as { method?: string; token?: string };
|
|
116
|
+
if (method === 'pat' && token) {
|
|
117
|
+
const result = await githubService.connectWithToken(token);
|
|
118
|
+
res.json(result);
|
|
119
|
+
} else {
|
|
120
|
+
const result = await githubService.connectOAuth();
|
|
121
|
+
res.json(result);
|
|
122
|
+
}
|
|
123
|
+
} catch (err) {
|
|
124
|
+
res.status(500).json({ error: 'Failed to connect', details: String(err) });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
router.get('/github/auth-status', async (_req, res) => {
|
|
129
|
+
try {
|
|
130
|
+
const status = await githubService.getAuthStatus();
|
|
131
|
+
res.json(status);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
res.status(500).json({ error: 'Failed to check auth', details: String(err) });
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
router.post('/github/disconnect', async (_req, res) => {
|
|
138
|
+
try {
|
|
139
|
+
const result = await githubService.disconnect();
|
|
140
|
+
res.json(result);
|
|
141
|
+
} catch (err) {
|
|
142
|
+
res.status(500).json({ error: 'Failed to disconnect', details: String(err) });
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// ─── GitHub CI Checks ──────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
router.get('/github/checks/:ref', async (req, res) => {
|
|
149
|
+
try {
|
|
150
|
+
const checks = await githubService.getCheckRuns(req.params.ref);
|
|
151
|
+
res.json(checks);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
res.status(500).json({ error: 'Failed to get checks', details: String(err) });
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// ─── Git Health Metrics ────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
router.get('/git/health', async (_req, res) => {
|
|
160
|
+
try {
|
|
161
|
+
// Get PR ages for health calculation
|
|
162
|
+
let prAges: number[] = [];
|
|
163
|
+
try {
|
|
164
|
+
const prs = await githubService.getOpenPRs();
|
|
165
|
+
const now = Date.now();
|
|
166
|
+
prAges = prs.map(pr => Math.round((now - new Date(pr.createdAt).getTime()) / (1000 * 60 * 60 * 24)));
|
|
167
|
+
} catch { /* ok — GitHub may not be connected */ }
|
|
168
|
+
|
|
169
|
+
const health = await gitService.getHealthMetrics(prAges);
|
|
170
|
+
res.json(health);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
res.status(500).json({ error: 'Failed to get health metrics', details: String(err) });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// ─── Git Activity Series ───────────────────────────────────
|
|
177
|
+
|
|
178
|
+
router.get('/git/activity', async (req, res) => {
|
|
179
|
+
try {
|
|
180
|
+
const days = Number(req.query.days) || 30;
|
|
181
|
+
const activity = await gitService.getActivitySeries(days);
|
|
182
|
+
res.json(activity);
|
|
183
|
+
} catch (err) {
|
|
184
|
+
res.status(500).json({ error: 'Failed to get activity', details: String(err) });
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
111
188
|
return router;
|
|
112
189
|
}
|
|
@@ -0,0 +1,388 @@
|
|
|
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
|
+
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import { execFileSync } from 'child_process';
|
|
9
|
+
import { Router } from 'express';
|
|
10
|
+
import {
|
|
11
|
+
loadManifest,
|
|
12
|
+
saveManifest,
|
|
13
|
+
hashFile,
|
|
14
|
+
computeFileStatus,
|
|
15
|
+
refreshFileStatuses,
|
|
16
|
+
summarizeManifest,
|
|
17
|
+
reverseRemapPath,
|
|
18
|
+
safeBackupFile,
|
|
19
|
+
safeCopyTemplate,
|
|
20
|
+
safeRestoreFile,
|
|
21
|
+
} from '../manifest.js';
|
|
22
|
+
import { validate } from '../validator.js';
|
|
23
|
+
import { computeUpdatePlan, loadRenameMap } from '../update-planner.js';
|
|
24
|
+
import { runInit, runUpdate } from '../init.js';
|
|
25
|
+
import { needsLegacyMigration, migrateFromLegacy } from '../migrate-legacy.js';
|
|
26
|
+
import type { Emitter } from '../project-emitter.js';
|
|
27
|
+
import { errMsg, isValidRelativePath } from '../utils/route-helpers.js';
|
|
28
|
+
|
|
29
|
+
// ─── Types ──────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
interface ManifestRouteDeps {
|
|
32
|
+
projectRoot: string;
|
|
33
|
+
templatesDir: string;
|
|
34
|
+
packageVersion: string;
|
|
35
|
+
io: Emitter;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
// ─── Route Factory ──────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
export function createManifestRoutes({
|
|
42
|
+
projectRoot,
|
|
43
|
+
templatesDir,
|
|
44
|
+
packageVersion,
|
|
45
|
+
io,
|
|
46
|
+
}: ManifestRouteDeps): Router {
|
|
47
|
+
const router = Router();
|
|
48
|
+
const claudeDir = path.join(projectRoot, '.claude');
|
|
49
|
+
|
|
50
|
+
// ─── GET /manifest/status — summary overview ────────────
|
|
51
|
+
|
|
52
|
+
router.get('/manifest/status', (_req, res) => {
|
|
53
|
+
try {
|
|
54
|
+
const manifest = loadManifest(projectRoot);
|
|
55
|
+
|
|
56
|
+
if (!manifest) {
|
|
57
|
+
return res.json({
|
|
58
|
+
success: true,
|
|
59
|
+
data: {
|
|
60
|
+
exists: false,
|
|
61
|
+
packageVersion,
|
|
62
|
+
installedVersion: '',
|
|
63
|
+
needsUpdate: true,
|
|
64
|
+
preset: '',
|
|
65
|
+
files: { total: 0, synced: 0, modified: 0, pinned: 0, userOwned: 0, byType: {} },
|
|
66
|
+
lastUpdated: '',
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
refreshFileStatuses(manifest, claudeDir);
|
|
72
|
+
const summary = summarizeManifest(manifest);
|
|
73
|
+
|
|
74
|
+
res.json({
|
|
75
|
+
success: true,
|
|
76
|
+
data: {
|
|
77
|
+
exists: true,
|
|
78
|
+
packageVersion,
|
|
79
|
+
installedVersion: manifest.packageVersion,
|
|
80
|
+
needsUpdate: manifest.packageVersion !== packageVersion,
|
|
81
|
+
preset: manifest.preset,
|
|
82
|
+
files: summary,
|
|
83
|
+
lastUpdated: manifest.updatedAt,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
} catch (err) {
|
|
87
|
+
res.status(500).json({ success: false, error: errMsg(err) });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// ─── GET /manifest/files — file inventory ───────────────
|
|
92
|
+
|
|
93
|
+
router.get('/manifest/files', (_req, res) => {
|
|
94
|
+
try {
|
|
95
|
+
const manifest = loadManifest(projectRoot);
|
|
96
|
+
if (!manifest) {
|
|
97
|
+
return res.json({ success: true, data: [] });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
refreshFileStatuses(manifest, claudeDir);
|
|
101
|
+
|
|
102
|
+
const files = Object.entries(manifest.files).map(([filePath, record]) => ({
|
|
103
|
+
path: filePath,
|
|
104
|
+
origin: record.origin,
|
|
105
|
+
status: record.status,
|
|
106
|
+
templateHash: record.templateHash,
|
|
107
|
+
installedHash: record.installedHash,
|
|
108
|
+
pinnedAt: record.pinnedAt,
|
|
109
|
+
pinnedReason: record.pinnedReason,
|
|
110
|
+
hasPrev: fs.existsSync(path.join(claudeDir, filePath + '.prev')),
|
|
111
|
+
}));
|
|
112
|
+
|
|
113
|
+
res.json({ success: true, data: files });
|
|
114
|
+
} catch (err) {
|
|
115
|
+
res.status(500).json({ success: false, error: errMsg(err) });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ─── GET /manifest/validate — run validation ────────────
|
|
120
|
+
|
|
121
|
+
router.get('/manifest/validate', (_req, res) => {
|
|
122
|
+
try {
|
|
123
|
+
const report = validate(projectRoot, packageVersion);
|
|
124
|
+
res.json({ success: true, data: report });
|
|
125
|
+
} catch (err) {
|
|
126
|
+
res.status(500).json({ success: false, error: errMsg(err) });
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// ─── POST /manifest/init — initialize manifest ───────────
|
|
131
|
+
|
|
132
|
+
router.post('/manifest/init', (_req, res) => {
|
|
133
|
+
try {
|
|
134
|
+
// If manifest already exists, just return success
|
|
135
|
+
if (loadManifest(projectRoot)) {
|
|
136
|
+
return res.json({ success: true, message: 'Already initialized' });
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// If legacy install exists, migrate it
|
|
140
|
+
if (needsLegacyMigration(projectRoot)) {
|
|
141
|
+
migrateFromLegacy(projectRoot, templatesDir, packageVersion);
|
|
142
|
+
io.emit('manifest:changed', { action: 'initialized' });
|
|
143
|
+
return res.json({ success: true });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// No existing install at all — run full init
|
|
147
|
+
runInit(projectRoot, { force: false });
|
|
148
|
+
io.emit('manifest:changed', { action: 'initialized' });
|
|
149
|
+
res.json({ success: true });
|
|
150
|
+
} catch (err) {
|
|
151
|
+
res.status(500).json({ success: false, error: errMsg(err) });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// ─── POST /manifest/update — run update or dry-run ──────
|
|
156
|
+
|
|
157
|
+
router.post('/manifest/update', (req, res) => {
|
|
158
|
+
const { dryRun = true } = req.body as { dryRun?: boolean };
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
// Ensure manifest exists (migrate legacy if needed)
|
|
162
|
+
let manifest = loadManifest(projectRoot);
|
|
163
|
+
if (!manifest && needsLegacyMigration(projectRoot)) {
|
|
164
|
+
migrateFromLegacy(projectRoot, templatesDir, packageVersion);
|
|
165
|
+
manifest = loadManifest(projectRoot);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!manifest) {
|
|
169
|
+
return res.status(400).json({ success: false, error: 'No manifest. Run orbital first.' });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (dryRun) {
|
|
173
|
+
refreshFileStatuses(manifest, claudeDir);
|
|
174
|
+
const renameMap = loadRenameMap(templatesDir, manifest.packageVersion, packageVersion);
|
|
175
|
+
const plan = computeUpdatePlan({
|
|
176
|
+
templatesDir,
|
|
177
|
+
claudeDir,
|
|
178
|
+
manifest,
|
|
179
|
+
newVersion: packageVersion,
|
|
180
|
+
renameMap,
|
|
181
|
+
});
|
|
182
|
+
return res.json({ success: true, data: plan });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Execute actual update
|
|
186
|
+
runUpdate(projectRoot, { dryRun: false });
|
|
187
|
+
io.emit('manifest:changed', { action: 'updated' });
|
|
188
|
+
res.json({ success: true });
|
|
189
|
+
} catch (err) {
|
|
190
|
+
res.status(500).json({ success: false, error: errMsg(err) });
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// ─── POST /manifest/pin — pin a file ───────────────────
|
|
195
|
+
|
|
196
|
+
router.post('/manifest/pin', (req, res) => {
|
|
197
|
+
const { file, reason } = req.body as { file: string; reason?: string };
|
|
198
|
+
if (!file || !isValidRelativePath(file)) {
|
|
199
|
+
return res.status(400).json({ success: false, error: 'Valid file path required' });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
const manifest = loadManifest(projectRoot);
|
|
204
|
+
if (!manifest) return res.status(400).json({ success: false, error: 'No manifest' });
|
|
205
|
+
|
|
206
|
+
const record = manifest.files[file];
|
|
207
|
+
if (!record) return res.status(404).json({ success: false, error: 'File not tracked' });
|
|
208
|
+
if (record.origin === 'user') return res.status(400).json({ success: false, error: 'Cannot pin user-owned file' });
|
|
209
|
+
|
|
210
|
+
record.status = 'pinned';
|
|
211
|
+
record.pinnedAt = new Date().toISOString();
|
|
212
|
+
if (reason) record.pinnedReason = reason;
|
|
213
|
+
|
|
214
|
+
saveManifest(projectRoot, manifest);
|
|
215
|
+
io.emit('manifest:changed', { action: 'pinned', file });
|
|
216
|
+
res.json({ success: true });
|
|
217
|
+
} catch (err) {
|
|
218
|
+
res.status(500).json({ success: false, error: errMsg(err) });
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// ─── POST /manifest/unpin — unpin a file ────────────────
|
|
223
|
+
|
|
224
|
+
router.post('/manifest/unpin', (req, res) => {
|
|
225
|
+
const { file } = req.body as { file: string };
|
|
226
|
+
if (!file || !isValidRelativePath(file)) {
|
|
227
|
+
return res.status(400).json({ success: false, error: 'Valid file path required' });
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
const manifest = loadManifest(projectRoot);
|
|
232
|
+
if (!manifest) return res.status(400).json({ success: false, error: 'No manifest' });
|
|
233
|
+
|
|
234
|
+
const record = manifest.files[file];
|
|
235
|
+
if (!record || record.status !== 'pinned') {
|
|
236
|
+
return res.status(400).json({ success: false, error: 'File is not pinned' });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Clear pinned state before recomputing status
|
|
240
|
+
record.status = 'synced';
|
|
241
|
+
delete record.pinnedAt;
|
|
242
|
+
delete record.pinnedReason;
|
|
243
|
+
|
|
244
|
+
const absPath = path.join(claudeDir, file);
|
|
245
|
+
if (fs.existsSync(absPath)) {
|
|
246
|
+
const currentHash = hashFile(absPath);
|
|
247
|
+
record.status = computeFileStatus(record, currentHash);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
saveManifest(projectRoot, manifest);
|
|
251
|
+
io.emit('manifest:changed', { action: 'unpinned', file });
|
|
252
|
+
res.json({ success: true });
|
|
253
|
+
} catch (err) {
|
|
254
|
+
res.status(500).json({ success: false, error: errMsg(err) });
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// ─── POST /manifest/reset — reset file to template ──────
|
|
259
|
+
|
|
260
|
+
router.post('/manifest/reset', (req, res) => {
|
|
261
|
+
const { file } = req.body as { file: string };
|
|
262
|
+
if (!file || !isValidRelativePath(file)) {
|
|
263
|
+
return res.status(400).json({ success: false, error: 'Valid file path required' });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const manifest = loadManifest(projectRoot);
|
|
268
|
+
if (!manifest) return res.status(400).json({ success: false, error: 'No manifest' });
|
|
269
|
+
|
|
270
|
+
const record = manifest.files[file];
|
|
271
|
+
if (!record || record.origin !== 'template') {
|
|
272
|
+
return res.status(400).json({ success: false, error: 'Not a template file' });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Resolve template source path
|
|
276
|
+
const templateRelPath = reverseRemapPath(file);
|
|
277
|
+
const templatePath = path.join(templatesDir, templateRelPath);
|
|
278
|
+
if (!fs.existsSync(templatePath)) {
|
|
279
|
+
return res.status(404).json({ success: false, error: 'Template file not found' });
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const localPath = path.join(claudeDir, file);
|
|
283
|
+
|
|
284
|
+
// Back up current version so user can revert (symlink-safe)
|
|
285
|
+
safeBackupFile(localPath);
|
|
286
|
+
|
|
287
|
+
// Copy template to destination (skips if symlink)
|
|
288
|
+
safeCopyTemplate(templatePath, localPath);
|
|
289
|
+
|
|
290
|
+
const newHash = hashFile(localPath);
|
|
291
|
+
record.status = 'synced';
|
|
292
|
+
record.templateHash = newHash;
|
|
293
|
+
record.installedHash = newHash;
|
|
294
|
+
delete record.pinnedAt;
|
|
295
|
+
delete record.pinnedReason;
|
|
296
|
+
|
|
297
|
+
saveManifest(projectRoot, manifest);
|
|
298
|
+
io.emit('manifest:changed', { action: 'reset', file });
|
|
299
|
+
res.json({ success: true });
|
|
300
|
+
} catch (err) {
|
|
301
|
+
res.status(500).json({ success: false, error: errMsg(err) });
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// ─── POST /manifest/revert — restore file from .prev backup ──
|
|
306
|
+
|
|
307
|
+
router.post('/manifest/revert', (req, res) => {
|
|
308
|
+
const { file } = req.body as { file: string };
|
|
309
|
+
if (!file || !isValidRelativePath(file)) {
|
|
310
|
+
return res.status(400).json({ success: false, error: 'Valid file path required' });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
const manifest = loadManifest(projectRoot);
|
|
315
|
+
if (!manifest) return res.status(400).json({ success: false, error: 'No manifest' });
|
|
316
|
+
|
|
317
|
+
const record = manifest.files[file];
|
|
318
|
+
if (!record) return res.status(404).json({ success: false, error: 'File not tracked' });
|
|
319
|
+
|
|
320
|
+
const localPath = path.join(claudeDir, file);
|
|
321
|
+
|
|
322
|
+
if (!safeRestoreFile(localPath)) {
|
|
323
|
+
return res.status(404).json({ success: false, error: 'No previous version available' });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Recompute status — file may now be a symlink or regular file
|
|
327
|
+
if (fs.existsSync(localPath)) {
|
|
328
|
+
const stat = fs.lstatSync(localPath);
|
|
329
|
+
if (stat.isSymbolicLink()) {
|
|
330
|
+
record.status = 'synced'; // restored symlink points at template
|
|
331
|
+
} else {
|
|
332
|
+
const currentHash = hashFile(localPath);
|
|
333
|
+
record.installedHash = currentHash;
|
|
334
|
+
record.status = computeFileStatus(record, currentHash);
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
record.status = 'missing';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
saveManifest(projectRoot, manifest);
|
|
341
|
+
io.emit('manifest:changed', { action: 'reverted', file });
|
|
342
|
+
res.json({ success: true });
|
|
343
|
+
} catch (err) {
|
|
344
|
+
res.status(500).json({ success: false, error: errMsg(err) });
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// ─── GET /manifest/diff — diff template vs local ─────────
|
|
349
|
+
|
|
350
|
+
router.get('/manifest/diff', (req, res) => {
|
|
351
|
+
const file = req.query.file as string;
|
|
352
|
+
if (!file || !isValidRelativePath(file)) {
|
|
353
|
+
return res.status(400).json({ success: false, error: 'Valid file path required' });
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
const templateRelPath = reverseRemapPath(file);
|
|
358
|
+
const rawTemplatePath = path.join(templatesDir, templateRelPath);
|
|
359
|
+
// Resolve symlinks so git diff compares file content, not symlink metadata
|
|
360
|
+
const templatePath = fs.existsSync(rawTemplatePath) ? fs.realpathSync(rawTemplatePath) : rawTemplatePath;
|
|
361
|
+
const localPath = path.join(claudeDir, file);
|
|
362
|
+
|
|
363
|
+
if (!fs.existsSync(templatePath)) {
|
|
364
|
+
return res.status(404).json({ success: false, error: 'Template file not found' });
|
|
365
|
+
}
|
|
366
|
+
if (!fs.existsSync(localPath)) {
|
|
367
|
+
return res.status(404).json({ success: false, error: 'Local file not found' });
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
let diff = '';
|
|
371
|
+
try {
|
|
372
|
+
diff = execFileSync('git', ['diff', '--no-index', '--', templatePath, localPath], {
|
|
373
|
+
encoding: 'utf-8',
|
|
374
|
+
});
|
|
375
|
+
} catch (e: unknown) {
|
|
376
|
+
// git diff exits 1 when files differ
|
|
377
|
+
const err = e as { stdout?: string };
|
|
378
|
+
diff = err.stdout || 'Files differ';
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
res.json({ success: true, data: { diff } });
|
|
382
|
+
} catch (err) {
|
|
383
|
+
res.status(500).json({ success: false, error: errMsg(err) });
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
return router;
|
|
388
|
+
}
|
|
@@ -1,27 +1,34 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
3
|
import type Database from 'better-sqlite3';
|
|
4
|
-
import type {
|
|
4
|
+
import type { Emitter } from '../project-emitter.js';
|
|
5
5
|
import type { ScopeService } from '../services/scope-service.js';
|
|
6
6
|
import type { ReadinessService } from '../services/readiness-service.js';
|
|
7
7
|
import type { WorkflowEngine } from '../../shared/workflow-engine.js';
|
|
8
|
-
import { launchInTerminal, escapeForAnsiC, buildSessionName, snapshotSessionPids, discoverNewSession, renameSession } from '../utils/terminal-launcher.js';
|
|
8
|
+
import { launchInTerminal, escapeForAnsiC, shellQuote, buildSessionName, snapshotSessionPids, discoverNewSession, renameSession } from '../utils/terminal-launcher.js';
|
|
9
9
|
import { resolveDispatchEvent, linkPidToDispatch } from '../utils/dispatch-utils.js';
|
|
10
|
-
import {
|
|
10
|
+
import { buildClaudeFlags, buildEnvVarPrefix } from '../utils/flag-builder.js';
|
|
11
|
+
import type { OrbitalConfig } from '../config.js';
|
|
11
12
|
import { createLogger } from '../utils/logger.js';
|
|
12
13
|
|
|
13
14
|
const log = createLogger('dispatch');
|
|
14
15
|
|
|
15
16
|
interface ScopeRouteDeps {
|
|
16
17
|
db: Database.Database;
|
|
17
|
-
io:
|
|
18
|
+
io: Emitter;
|
|
18
19
|
scopeService: ScopeService;
|
|
19
20
|
readinessService: ReadinessService;
|
|
20
21
|
projectRoot: string;
|
|
22
|
+
projectName: string;
|
|
21
23
|
engine: WorkflowEngine;
|
|
24
|
+
config: OrbitalConfig;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
function isValidSlug(slug: string): boolean {
|
|
28
|
+
return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(slug) && slug.length <= 80;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createScopeRoutes({ db, io, scopeService, readinessService, projectRoot, projectName, engine, config }: ScopeRouteDeps): Router {
|
|
25
32
|
const router = Router();
|
|
26
33
|
|
|
27
34
|
// ─── Scope CRUD ──────────────────────────────────────────
|
|
@@ -67,7 +74,7 @@ export function createScopeRoutes({ db, io, scopeService, readinessService, proj
|
|
|
67
74
|
|
|
68
75
|
router.patch('/scopes/:id', (req, res) => {
|
|
69
76
|
const id = Number(req.params.id);
|
|
70
|
-
const result = scopeService.
|
|
77
|
+
const result = scopeService.updateFields(id, req.body);
|
|
71
78
|
if (!result.ok) {
|
|
72
79
|
const code = result.code === 'NOT_FOUND' ? 404 : 400;
|
|
73
80
|
res.status(code).json({ error: result.error, code: result.code });
|
|
@@ -89,14 +96,15 @@ export function createScopeRoutes({ db, io, scopeService, readinessService, proj
|
|
|
89
96
|
res.status(201).json(idea);
|
|
90
97
|
});
|
|
91
98
|
|
|
92
|
-
router.patch('/ideas/:
|
|
93
|
-
const
|
|
99
|
+
router.patch('/ideas/:slug', (req, res) => {
|
|
100
|
+
const { slug } = req.params;
|
|
101
|
+
if (!isValidSlug(slug)) { res.status(400).json({ error: 'Invalid slug' }); return; }
|
|
94
102
|
const { title, description } = req.body as { title?: string; description?: string };
|
|
95
103
|
if (!title?.trim()) {
|
|
96
104
|
res.status(400).json({ error: 'title is required' });
|
|
97
105
|
return;
|
|
98
106
|
}
|
|
99
|
-
const updated = scopeService.updateIdeaFile(
|
|
107
|
+
const updated = scopeService.updateIdeaFile(slug, title.trim(), (description ?? '').trim());
|
|
100
108
|
if (!updated) {
|
|
101
109
|
res.status(404).json({ error: 'Idea not found' });
|
|
102
110
|
return;
|
|
@@ -104,9 +112,10 @@ export function createScopeRoutes({ db, io, scopeService, readinessService, proj
|
|
|
104
112
|
res.json({ ok: true });
|
|
105
113
|
});
|
|
106
114
|
|
|
107
|
-
router.delete('/ideas/:
|
|
108
|
-
const
|
|
109
|
-
|
|
115
|
+
router.delete('/ideas/:slug', (req, res) => {
|
|
116
|
+
const { slug } = req.params;
|
|
117
|
+
if (!isValidSlug(slug)) { res.status(400).json({ error: 'Invalid slug' }); return; }
|
|
118
|
+
const deleted = scopeService.deleteIdeaFile(slug);
|
|
110
119
|
if (!deleted) {
|
|
111
120
|
res.status(404).json({ error: 'Idea not found' });
|
|
112
121
|
return;
|
|
@@ -114,9 +123,13 @@ export function createScopeRoutes({ db, io, scopeService, readinessService, proj
|
|
|
114
123
|
res.json({ ok: true });
|
|
115
124
|
});
|
|
116
125
|
|
|
117
|
-
router.post('/ideas/:
|
|
118
|
-
const
|
|
119
|
-
|
|
126
|
+
router.post('/ideas/:slug/promote', async (req, res) => {
|
|
127
|
+
const { slug } = req.params;
|
|
128
|
+
if (!isValidSlug(slug)) { res.status(400).json({ error: 'Invalid slug' }); return; }
|
|
129
|
+
const entryPoint = engine.getEntryPoint();
|
|
130
|
+
const targets = engine.getValidTargets(entryPoint.id);
|
|
131
|
+
const promoteTarget = targets[0] ?? 'planning';
|
|
132
|
+
const result = scopeService.promoteIdea(slug, promoteTarget);
|
|
120
133
|
if (!result) {
|
|
121
134
|
res.status(404).json({ error: 'Idea not found' });
|
|
122
135
|
return;
|
|
@@ -125,9 +138,6 @@ export function createScopeRoutes({ db, io, scopeService, readinessService, proj
|
|
|
125
138
|
const scopeId = result.id;
|
|
126
139
|
|
|
127
140
|
// Read command from workflow edge config (user-overridable)
|
|
128
|
-
const entryPoint = engine.getEntryPoint();
|
|
129
|
-
const targets = engine.getValidTargets(entryPoint.id);
|
|
130
|
-
const promoteTarget = targets[0] ?? 'planning';
|
|
131
141
|
const edge = engine.findEdge(entryPoint.id, promoteTarget);
|
|
132
142
|
const edgeCommand = edge ? engine.buildCommand(edge, scopeId) : null;
|
|
133
143
|
const command = edgeCommand ?? `/scope-create ${String(scopeId).padStart(3, '0')}`;
|
|
@@ -151,7 +161,9 @@ export function createScopeRoutes({ db, io, scopeService, readinessService, proj
|
|
|
151
161
|
});
|
|
152
162
|
|
|
153
163
|
const escaped = escapeForAnsiC(command);
|
|
154
|
-
const
|
|
164
|
+
const flagsStr = buildClaudeFlags(config.claude.dispatchFlags);
|
|
165
|
+
const envPrefix = buildEnvVarPrefix(config.dispatch.envVars);
|
|
166
|
+
const fullCmd = `cd '${shellQuote(projectRoot)}' && ${envPrefix}ORBITAL_DISPATCH_ID='${shellQuote(eventId)}' claude ${flagsStr} $'${escaped}'`;
|
|
155
167
|
|
|
156
168
|
const promoteSessionName = buildSessionName({ scopeId, title: result.title, command });
|
|
157
169
|
const promoteBeforePids = snapshotSessionPids(projectRoot);
|
|
@@ -184,18 +196,15 @@ export function createScopeRoutes({ db, io, scopeService, readinessService, proj
|
|
|
184
196
|
}
|
|
185
197
|
surpriseInProgress = true;
|
|
186
198
|
|
|
187
|
-
const nextIdStart = scopeService.getNextIceboxId();
|
|
188
199
|
const today = new Date().toISOString().split('T')[0];
|
|
189
|
-
const idRange = Array.from({ length: 5 }, (_, i) => nextIdStart + i);
|
|
190
200
|
|
|
191
|
-
const prompt = `You are analyzing the ${
|
|
201
|
+
const prompt = `You are analyzing the ${projectName} codebase to suggest feature ideas. Your ONLY job is to create markdown files.
|
|
192
202
|
|
|
193
203
|
Create exactly 3 idea files in the scopes/icebox/ directory. Each file must use this EXACT format:
|
|
194
204
|
|
|
195
|
-
File: scopes/icebox/{
|
|
205
|
+
File: scopes/icebox/{kebab-slug}.md
|
|
196
206
|
|
|
197
207
|
---
|
|
198
|
-
id: {ID}
|
|
199
208
|
title: "{title}"
|
|
200
209
|
status: icebox
|
|
201
210
|
ghost: true
|
|
@@ -208,13 +217,12 @@ tags: []
|
|
|
208
217
|
|
|
209
218
|
{2-3 sentence description of the feature, what problem it solves, and a rough approach.}
|
|
210
219
|
|
|
211
|
-
Use these IDs: ${idRange[0]}, ${idRange[1]}, ${idRange[2]}
|
|
212
|
-
|
|
213
220
|
Rules:
|
|
214
221
|
- Focus on practical improvements: performance, UX, security, developer experience, monitoring, or reliability
|
|
215
222
|
- Be specific and actionable — not vague architectural rewrites
|
|
216
223
|
- Keep descriptions concise (2-3 sentences max)
|
|
217
|
-
- Filenames must be {
|
|
224
|
+
- Filenames must be {kebab-case-slug}.md (NO numeric prefix)
|
|
225
|
+
- Do NOT include an id field in frontmatter
|
|
218
226
|
- The ghost: true field is required in frontmatter
|
|
219
227
|
- Do NOT create any other files or make any other changes`;
|
|
220
228
|
|
|
@@ -244,9 +252,10 @@ Rules:
|
|
|
244
252
|
res.json({ ok: true, status: 'generating' });
|
|
245
253
|
});
|
|
246
254
|
|
|
247
|
-
router.post('/ideas/:
|
|
248
|
-
const
|
|
249
|
-
|
|
255
|
+
router.post('/ideas/:slug/approve', (req, res) => {
|
|
256
|
+
const { slug } = req.params;
|
|
257
|
+
if (!isValidSlug(slug)) { res.status(400).json({ error: 'Invalid slug' }); return; }
|
|
258
|
+
const approved = scopeService.approveGhostIdea(slug);
|
|
250
259
|
if (!approved) {
|
|
251
260
|
res.status(404).json({ error: 'Ghost idea not found' });
|
|
252
261
|
return;
|