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
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Emitter } from '../project-emitter.js';
|
|
3
3
|
import { ConfigService, isValidPrimitiveType } from '../services/config-service.js';
|
|
4
4
|
import type { ConfigPrimitiveType } from '../services/config-service.js';
|
|
5
5
|
import type { WorkflowService } from '../services/workflow-service.js';
|
|
6
|
+
import { catchRoute, inferErrorStatus } from '../utils/route-helpers.js';
|
|
6
7
|
|
|
7
8
|
interface ConfigRouteDeps {
|
|
8
9
|
projectRoot: string;
|
|
9
10
|
workflowService: WorkflowService;
|
|
10
|
-
io:
|
|
11
|
+
io: Emitter;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function createConfigRoutes({ projectRoot, workflowService: _workflowService, io }: ConfigRouteDeps): Router {
|
|
@@ -24,21 +25,17 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
// GET /config/:type/tree — directory tree with frontmatter
|
|
27
|
-
router.get('/config/:type/tree', (req, res) => {
|
|
28
|
+
router.get('/config/:type/tree', catchRoute((req, res) => {
|
|
28
29
|
const type = parseType(req.params.type, res);
|
|
29
30
|
if (!type) return;
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
} catch (err) {
|
|
36
|
-
res.status(500).json({ success: false, error: errMsg(err) });
|
|
37
|
-
}
|
|
38
|
-
});
|
|
32
|
+
const basePath = configService.getBasePath(type);
|
|
33
|
+
const tree = configService.scanDirectory(basePath);
|
|
34
|
+
res.json({ success: true, data: tree });
|
|
35
|
+
}));
|
|
39
36
|
|
|
40
37
|
// GET /config/:type/file?path=<relative> — file content
|
|
41
|
-
router.get('/config/:type/file', (req, res) => {
|
|
38
|
+
router.get('/config/:type/file', catchRoute((req, res) => {
|
|
42
39
|
const type = parseType(req.params.type, res);
|
|
43
40
|
if (!type) return;
|
|
44
41
|
|
|
@@ -48,19 +45,13 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
|
|
|
48
45
|
return;
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} catch (err) {
|
|
56
|
-
const msg = errMsg(err);
|
|
57
|
-
const status = msg.includes('traversal') ? 403 : msg.includes('ENOENT') || msg.includes('not found') ? 404 : 500;
|
|
58
|
-
res.status(status).json({ success: false, error: msg });
|
|
59
|
-
}
|
|
60
|
-
});
|
|
48
|
+
const basePath = configService.getBasePath(type);
|
|
49
|
+
const content = configService.readFile(basePath, filePath);
|
|
50
|
+
res.json({ success: true, data: { path: filePath, content } });
|
|
51
|
+
}, inferErrorStatus));
|
|
61
52
|
|
|
62
53
|
// PUT /config/:type/file — save file { path, content }
|
|
63
|
-
router.put('/config/:type/file', (req, res) => {
|
|
54
|
+
router.put('/config/:type/file', catchRoute((req, res) => {
|
|
64
55
|
const type = parseType(req.params.type, res);
|
|
65
56
|
if (!type) return;
|
|
66
57
|
|
|
@@ -70,20 +61,14 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
|
|
|
70
61
|
return;
|
|
71
62
|
}
|
|
72
63
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
} catch (err) {
|
|
79
|
-
const msg = errMsg(err);
|
|
80
|
-
const status = msg.includes('traversal') ? 403 : msg.includes('not found') ? 404 : 500;
|
|
81
|
-
res.status(status).json({ success: false, error: msg });
|
|
82
|
-
}
|
|
83
|
-
});
|
|
64
|
+
const basePath = configService.getBasePath(type);
|
|
65
|
+
configService.writeFile(basePath, filePath, content);
|
|
66
|
+
io.emit(`config:${type}:changed`, { action: 'updated', path: filePath });
|
|
67
|
+
res.json({ success: true });
|
|
68
|
+
}, inferErrorStatus));
|
|
84
69
|
|
|
85
70
|
// POST /config/:type/file — create file { path, content }
|
|
86
|
-
router.post('/config/:type/file', (req, res) => {
|
|
71
|
+
router.post('/config/:type/file', catchRoute((req, res) => {
|
|
87
72
|
const type = parseType(req.params.type, res);
|
|
88
73
|
if (!type) return;
|
|
89
74
|
|
|
@@ -93,20 +78,14 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
|
|
|
93
78
|
return;
|
|
94
79
|
}
|
|
95
80
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
} catch (err) {
|
|
102
|
-
const msg = errMsg(err);
|
|
103
|
-
const status = msg.includes('traversal') ? 403 : msg.includes('already exists') ? 409 : 500;
|
|
104
|
-
res.status(status).json({ success: false, error: msg });
|
|
105
|
-
}
|
|
106
|
-
});
|
|
81
|
+
const basePath = configService.getBasePath(type);
|
|
82
|
+
configService.createFile(basePath, filePath, content);
|
|
83
|
+
io.emit(`config:${type}:changed`, { action: 'created', path: filePath });
|
|
84
|
+
res.status(201).json({ success: true });
|
|
85
|
+
}, inferErrorStatus));
|
|
107
86
|
|
|
108
87
|
// DELETE /config/:type/file?path=<relative> — delete file
|
|
109
|
-
router.delete('/config/:type/file', (req, res) => {
|
|
88
|
+
router.delete('/config/:type/file', catchRoute((req, res) => {
|
|
110
89
|
const type = parseType(req.params.type, res);
|
|
111
90
|
if (!type) return;
|
|
112
91
|
|
|
@@ -116,20 +95,14 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
|
|
|
116
95
|
return;
|
|
117
96
|
}
|
|
118
97
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
} catch (err) {
|
|
125
|
-
const msg = errMsg(err);
|
|
126
|
-
const status = msg.includes('traversal') ? 403 : msg.includes('not found') ? 404 : msg.includes('directory') ? 400 : 500;
|
|
127
|
-
res.status(status).json({ success: false, error: msg });
|
|
128
|
-
}
|
|
129
|
-
});
|
|
98
|
+
const basePath = configService.getBasePath(type);
|
|
99
|
+
configService.deleteFile(basePath, filePath);
|
|
100
|
+
io.emit(`config:${type}:changed`, { action: 'deleted', path: filePath });
|
|
101
|
+
res.json({ success: true });
|
|
102
|
+
}, inferErrorStatus));
|
|
130
103
|
|
|
131
104
|
// POST /config/:type/rename — rename { oldPath, newPath }
|
|
132
|
-
router.post('/config/:type/rename', (req, res) => {
|
|
105
|
+
router.post('/config/:type/rename', catchRoute((req, res) => {
|
|
133
106
|
const type = parseType(req.params.type, res);
|
|
134
107
|
if (!type) return;
|
|
135
108
|
|
|
@@ -139,20 +112,14 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
|
|
|
139
112
|
return;
|
|
140
113
|
}
|
|
141
114
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
} catch (err) {
|
|
148
|
-
const msg = errMsg(err);
|
|
149
|
-
const status = msg.includes('traversal') ? 403 : msg.includes('not found') ? 404 : msg.includes('already exists') ? 409 : 500;
|
|
150
|
-
res.status(status).json({ success: false, error: msg });
|
|
151
|
-
}
|
|
152
|
-
});
|
|
115
|
+
const basePath = configService.getBasePath(type);
|
|
116
|
+
configService.renameFile(basePath, oldPath, newPath);
|
|
117
|
+
io.emit(`config:${type}:changed`, { action: 'renamed', oldPath, newPath });
|
|
118
|
+
res.json({ success: true });
|
|
119
|
+
}, inferErrorStatus));
|
|
153
120
|
|
|
154
121
|
// POST /config/:type/folder — create folder { path }
|
|
155
|
-
router.post('/config/:type/folder', (req, res) => {
|
|
122
|
+
router.post('/config/:type/folder', catchRoute((req, res) => {
|
|
156
123
|
const type = parseType(req.params.type, res);
|
|
157
124
|
if (!type) return;
|
|
158
125
|
|
|
@@ -162,21 +129,12 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
|
|
|
162
129
|
return;
|
|
163
130
|
}
|
|
164
131
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
} catch (err) {
|
|
171
|
-
const msg = errMsg(err);
|
|
172
|
-
const status = msg.includes('traversal') ? 403 : msg.includes('already exists') ? 409 : 500;
|
|
173
|
-
res.status(status).json({ success: false, error: msg });
|
|
174
|
-
}
|
|
175
|
-
});
|
|
132
|
+
const basePath = configService.getBasePath(type);
|
|
133
|
+
configService.createFolder(basePath, folderPath);
|
|
134
|
+
io.emit(`config:${type}:changed`, { action: 'folder-created', path: folderPath });
|
|
135
|
+
res.status(201).json({ success: true });
|
|
136
|
+
}, inferErrorStatus));
|
|
176
137
|
|
|
177
138
|
return router;
|
|
178
139
|
}
|
|
179
140
|
|
|
180
|
-
function errMsg(err: unknown): string {
|
|
181
|
-
return err instanceof Error ? err.message : String(err);
|
|
182
|
-
}
|
|
@@ -3,134 +3,51 @@ import { execFile } from 'child_process';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import type Database from 'better-sqlite3';
|
|
6
|
-
import type {
|
|
6
|
+
import type { Emitter } from '../project-emitter.js';
|
|
7
|
+
import type { EventService } from '../services/event-service.js';
|
|
7
8
|
import type { GateService } from '../services/gate-service.js';
|
|
8
9
|
import type { DeployService } from '../services/deploy-service.js';
|
|
10
|
+
import type { GitService } from '../services/git-service.js';
|
|
9
11
|
import type { WorkflowEngine } from '../../shared/workflow-engine.js';
|
|
10
12
|
import { getHookEnforcement } from '../../shared/workflow-config.js';
|
|
11
13
|
import { getClaudeSessions, getSessionStats, type SessionStats } from '../services/claude-session-service.js';
|
|
12
14
|
import { launchInTerminal } from '../utils/terminal-launcher.js';
|
|
15
|
+
import type { OrbitalConfig } from '../config.js';
|
|
16
|
+
import { buildClaudeFlags } from '../utils/flag-builder.js';
|
|
17
|
+
import { createLogger } from '../utils/logger.js';
|
|
18
|
+
import { parseJsonFields, type Row } from '../utils/json-fields.js';
|
|
13
19
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
// ─── Types & Helpers ────────────────────────────────────────
|
|
17
|
-
|
|
18
|
-
interface DriftCommit { sha: string; message: string; author: string; date: string }
|
|
19
|
-
interface BranchHead { sha: string; date: string; message: string }
|
|
20
|
-
interface PipelineDriftData {
|
|
21
|
-
devToStaging: { count: number; commits: DriftCommit[]; oldestDate: string | null };
|
|
22
|
-
stagingToMain: { count: number; commits: DriftCommit[]; oldestDate: string | null };
|
|
23
|
-
heads: { dev: BranchHead; staging: BranchHead; main: BranchHead };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const JSON_FIELDS = ['tags', 'blocked_by', 'blocks', 'data', 'discoveries', 'next_steps', 'details'];
|
|
27
|
-
|
|
28
|
-
type Row = Record<string, unknown>;
|
|
29
|
-
|
|
30
|
-
function parseJsonFields(row: Row): Row {
|
|
31
|
-
const parsed = { ...row };
|
|
32
|
-
for (const field of JSON_FIELDS) {
|
|
33
|
-
if (typeof parsed[field] === 'string') {
|
|
34
|
-
try { parsed[field] = JSON.parse(parsed[field] as string); } catch { /* keep string */ }
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return parsed;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function parseDriftCommits(raw: string): DriftCommit[] {
|
|
41
|
-
if (!raw) return [];
|
|
42
|
-
return raw.split('\n').map((line) => {
|
|
43
|
-
const [sha, date, message, author] = line.split('|');
|
|
44
|
-
return { sha, date, message: message ?? '', author: author ?? '' };
|
|
45
|
-
});
|
|
46
|
-
}
|
|
20
|
+
const log = createLogger('server');
|
|
47
21
|
|
|
48
|
-
|
|
49
|
-
const [sha, date, message] = raw.split('|');
|
|
50
|
-
return { sha: sha ?? '', date: date ?? '', message: message ?? '' };
|
|
51
|
-
}
|
|
22
|
+
const execFileAsync = promisify(execFile);
|
|
52
23
|
|
|
53
24
|
// ─── Route Factory ──────────────────────────────────────────
|
|
54
25
|
|
|
55
26
|
interface DataRouteDeps {
|
|
56
27
|
db: Database.Database;
|
|
57
|
-
io:
|
|
28
|
+
io: Emitter;
|
|
29
|
+
eventService: EventService;
|
|
58
30
|
gateService: GateService;
|
|
59
31
|
deployService: DeployService;
|
|
32
|
+
gitService: GitService;
|
|
60
33
|
engine: WorkflowEngine;
|
|
61
34
|
projectRoot: string;
|
|
62
35
|
inferScopeStatus: (type: string, scopeId: unknown, data: Record<string, unknown>) => void;
|
|
36
|
+
config: OrbitalConfig;
|
|
63
37
|
}
|
|
64
38
|
|
|
65
39
|
export function createDataRoutes({
|
|
66
|
-
db, io, gateService, deployService, engine, projectRoot, inferScopeStatus,
|
|
40
|
+
db, io, eventService, gateService, deployService, gitService, engine, projectRoot, inferScopeStatus, config,
|
|
67
41
|
}: DataRouteDeps): Router {
|
|
68
42
|
const router = Router();
|
|
69
43
|
|
|
70
|
-
// ─── Pipeline Drift (cached) ─────────────────────────────
|
|
71
|
-
|
|
72
|
-
let driftCache: { data: PipelineDriftData; ts: number } | null = null;
|
|
73
|
-
const DRIFT_CACHE_MS = 60_000;
|
|
74
|
-
|
|
75
|
-
async function gitLog(args: string[]): Promise<string> {
|
|
76
|
-
const { stdout } = await execFileAsync('git', args, { cwd: projectRoot });
|
|
77
|
-
return stdout.trim();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async function computeDrift(): Promise<PipelineDriftData> {
|
|
81
|
-
if (driftCache && Date.now() - driftCache.ts < DRIFT_CACHE_MS) return driftCache.data;
|
|
82
|
-
|
|
83
|
-
const [devToStagingRaw, stagingToMainRaw, devHead, stagingHead, mainHead] =
|
|
84
|
-
await Promise.all([
|
|
85
|
-
gitLog(['log', 'origin/dev', '--not', 'origin/staging', '--reverse', '--format=%H|%aI|%s|%an']),
|
|
86
|
-
gitLog(['log', 'origin/staging', '--not', 'origin/main', '--reverse', '--format=%H|%aI|%s|%an']),
|
|
87
|
-
gitLog(['log', 'origin/dev', '-1', '--format=%H|%aI|%s']),
|
|
88
|
-
gitLog(['log', 'origin/staging', '-1', '--format=%H|%aI|%s']),
|
|
89
|
-
gitLog(['log', 'origin/main', '-1', '--format=%H|%aI|%s']),
|
|
90
|
-
]);
|
|
91
|
-
|
|
92
|
-
const devToStaging = parseDriftCommits(devToStagingRaw);
|
|
93
|
-
const stagingToMain = parseDriftCommits(stagingToMainRaw);
|
|
94
|
-
|
|
95
|
-
const data: PipelineDriftData = {
|
|
96
|
-
devToStaging: {
|
|
97
|
-
count: devToStaging.length,
|
|
98
|
-
commits: devToStaging,
|
|
99
|
-
oldestDate: devToStaging[0]?.date ?? null,
|
|
100
|
-
},
|
|
101
|
-
stagingToMain: {
|
|
102
|
-
count: stagingToMain.length,
|
|
103
|
-
commits: stagingToMain,
|
|
104
|
-
oldestDate: stagingToMain[0]?.date ?? null,
|
|
105
|
-
},
|
|
106
|
-
heads: {
|
|
107
|
-
dev: parseHead(devHead),
|
|
108
|
-
staging: parseHead(stagingHead),
|
|
109
|
-
main: parseHead(mainHead),
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
driftCache = { data, ts: Date.now() };
|
|
114
|
-
return data;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
44
|
// ─── Event Routes ──────────────────────────────────────────
|
|
118
45
|
|
|
119
46
|
router.get('/events', (req, res) => {
|
|
120
47
|
const limit = Number(req.query.limit) || 50;
|
|
121
48
|
const type = req.query.type as string | undefined;
|
|
122
|
-
const scopeId = req.query.scope_id
|
|
123
|
-
|
|
124
|
-
let query = 'SELECT * FROM events WHERE 1=1';
|
|
125
|
-
const params: unknown[] = [];
|
|
126
|
-
|
|
127
|
-
if (type) { query += ' AND type = ?'; params.push(type); }
|
|
128
|
-
if (scopeId) { query += ' AND scope_id = ?'; params.push(Number(scopeId)); }
|
|
129
|
-
|
|
130
|
-
query += ' ORDER BY timestamp DESC LIMIT ?';
|
|
131
|
-
params.push(limit);
|
|
132
|
-
|
|
133
|
-
const events = db.prepare(query).all(...params) as Row[];
|
|
49
|
+
const scopeId = req.query.scope_id ? Number(req.query.scope_id) : undefined;
|
|
50
|
+
const events = eventService.getFiltered({ limit, type, scopeId }) as unknown as Row[];
|
|
134
51
|
res.json(events.map(parseJsonFields));
|
|
135
52
|
});
|
|
136
53
|
|
|
@@ -167,23 +84,9 @@ export function createDataRoutes({
|
|
|
167
84
|
|
|
168
85
|
router.get('/events/violations/summary', (_req, res) => {
|
|
169
86
|
try {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
).all();
|
|
174
|
-
const byFile = db.prepare(
|
|
175
|
-
`SELECT JSON_EXTRACT(data, '$.file') as file, COUNT(*) as count FROM events
|
|
176
|
-
WHERE type = 'VIOLATION' AND JSON_EXTRACT(data, '$.file') IS NOT NULL AND JSON_EXTRACT(data, '$.file') != ''
|
|
177
|
-
GROUP BY file ORDER BY count DESC LIMIT 20`
|
|
178
|
-
).all();
|
|
179
|
-
const overrides = db.prepare(
|
|
180
|
-
`SELECT JSON_EXTRACT(data, '$.rule') as rule, JSON_EXTRACT(data, '$.reason') as reason, timestamp as date
|
|
181
|
-
FROM events WHERE type = 'OVERRIDE' ORDER BY timestamp DESC LIMIT 50`
|
|
182
|
-
).all();
|
|
183
|
-
const totalViolations = db.prepare(`SELECT COUNT(*) as count FROM events WHERE type = 'VIOLATION'`).get() as { count: number };
|
|
184
|
-
const totalOverrides = db.prepare(`SELECT COUNT(*) as count FROM events WHERE type = 'OVERRIDE'`).get() as { count: number };
|
|
185
|
-
res.json({ byRule, byFile, overrides, totalViolations: totalViolations.count, totalOverrides: totalOverrides.count });
|
|
186
|
-
} catch {
|
|
87
|
+
res.json(eventService.getViolationSummary());
|
|
88
|
+
} catch (err) {
|
|
89
|
+
log.error('Violations summary failed', { error: String(err) });
|
|
187
90
|
res.status(500).json({ error: 'Failed to query violations summary' });
|
|
188
91
|
}
|
|
189
92
|
});
|
|
@@ -204,19 +107,7 @@ export function createDataRoutes({
|
|
|
204
107
|
}
|
|
205
108
|
}
|
|
206
109
|
|
|
207
|
-
|
|
208
|
-
const violationStats = db.prepare(
|
|
209
|
-
`SELECT JSON_EXTRACT(data, '$.rule') as rule, COUNT(*) as count, MAX(timestamp) as last_seen
|
|
210
|
-
FROM events WHERE type = 'VIOLATION' GROUP BY rule`
|
|
211
|
-
).all() as Array<{ rule: string; count: number; last_seen: string }>;
|
|
212
|
-
|
|
213
|
-
const overrideStats = db.prepare(
|
|
214
|
-
`SELECT JSON_EXTRACT(data, '$.rule') as rule, COUNT(*) as count
|
|
215
|
-
FROM events WHERE type = 'OVERRIDE' GROUP BY rule`
|
|
216
|
-
).all() as Array<{ rule: string; count: number }>;
|
|
217
|
-
|
|
218
|
-
const violationMap = new Map(violationStats.map((v) => [v.rule, v]));
|
|
219
|
-
const overrideMap = new Map(overrideStats.map((o) => [o.rule, o]));
|
|
110
|
+
const { violations: violationMap, overrides: overrideMap } = eventService.getViolationStatsByRule();
|
|
220
111
|
|
|
221
112
|
// Build summary counts
|
|
222
113
|
const summary = { guards: 0, gates: 0, lifecycle: 0, observers: 0 };
|
|
@@ -239,7 +130,8 @@ export function createDataRoutes({
|
|
|
239
130
|
}));
|
|
240
131
|
|
|
241
132
|
res.json({ summary, rules, totalEdges: allEdges.length });
|
|
242
|
-
} catch {
|
|
133
|
+
} catch (err) {
|
|
134
|
+
log.error('Enforcement rules failed', { error: String(err) });
|
|
243
135
|
res.status(500).json({ error: 'Failed to query enforcement rules' });
|
|
244
136
|
}
|
|
245
137
|
});
|
|
@@ -248,14 +140,10 @@ export function createDataRoutes({
|
|
|
248
140
|
|
|
249
141
|
router.get('/events/violations/trend', (req, res) => {
|
|
250
142
|
try {
|
|
251
|
-
const days = Number(req.query.days) || 30;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
GROUP BY day, rule ORDER BY day ASC`
|
|
256
|
-
).all(`-${days}`) as Array<{ day: string; rule: string; count: number }>;
|
|
257
|
-
res.json(trend);
|
|
258
|
-
} catch {
|
|
143
|
+
const days = Math.max(1, Math.min(365, Number(req.query.days) || 30));
|
|
144
|
+
res.json(eventService.getViolationTrend(days));
|
|
145
|
+
} catch (err) {
|
|
146
|
+
log.error('Violation trends failed', { error: String(err) });
|
|
259
147
|
res.status(500).json({ error: 'Failed to query violation trends' });
|
|
260
148
|
}
|
|
261
149
|
});
|
|
@@ -330,8 +218,7 @@ export function createDataRoutes({
|
|
|
330
218
|
|
|
331
219
|
router.get('/pipeline/drift', async (_req, res) => {
|
|
332
220
|
try {
|
|
333
|
-
|
|
334
|
-
res.json(drift);
|
|
221
|
+
res.json(await gitService.getPipelineDrift());
|
|
335
222
|
} catch (err) {
|
|
336
223
|
res.status(500).json({ error: 'Failed to compute drift', details: String(err) });
|
|
337
224
|
}
|
|
@@ -339,19 +226,9 @@ export function createDataRoutes({
|
|
|
339
226
|
|
|
340
227
|
router.get('/deployments/frequency', (_req, res) => {
|
|
341
228
|
try {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
).all() as Array<{ environment: string; week: string; count: number }>;
|
|
346
|
-
const weekMap = new Map<string, { week: string; staging: number; production: number }>();
|
|
347
|
-
for (const row of rows) {
|
|
348
|
-
if (!weekMap.has(row.week)) weekMap.set(row.week, { week: row.week, staging: 0, production: 0 });
|
|
349
|
-
const entry = weekMap.get(row.week)!;
|
|
350
|
-
if (row.environment === 'staging') entry.staging = row.count;
|
|
351
|
-
if (row.environment === 'production') entry.production = row.count;
|
|
352
|
-
}
|
|
353
|
-
res.json([...weekMap.values()]);
|
|
354
|
-
} catch {
|
|
229
|
+
res.json(eventService.getDeployFrequency());
|
|
230
|
+
} catch (err) {
|
|
231
|
+
log.error('Deploy frequency query failed', { error: String(err) });
|
|
355
232
|
res.status(500).json({ error: 'Failed to query deployment frequency' });
|
|
356
233
|
}
|
|
357
234
|
});
|
|
@@ -417,7 +294,7 @@ export function createDataRoutes({
|
|
|
417
294
|
let stats: SessionStats | null = null;
|
|
418
295
|
|
|
419
296
|
if (parsed.claude_session_id && typeof parsed.claude_session_id === 'string') {
|
|
420
|
-
const claudeSessions = await getClaudeSessions();
|
|
297
|
+
const claudeSessions = await getClaudeSessions(undefined, projectRoot);
|
|
421
298
|
const match = claudeSessions.find(s => s.id === parsed.claude_session_id);
|
|
422
299
|
if (match) {
|
|
423
300
|
meta = {
|
|
@@ -429,7 +306,7 @@ export function createDataRoutes({
|
|
|
429
306
|
lastActiveAt: match.lastActiveAt,
|
|
430
307
|
};
|
|
431
308
|
}
|
|
432
|
-
stats = getSessionStats(parsed.claude_session_id as string);
|
|
309
|
+
stats = getSessionStats(parsed.claude_session_id as string, projectRoot);
|
|
433
310
|
}
|
|
434
311
|
|
|
435
312
|
if (!content) {
|
|
@@ -466,7 +343,8 @@ export function createDataRoutes({
|
|
|
466
343
|
return;
|
|
467
344
|
}
|
|
468
345
|
|
|
469
|
-
const
|
|
346
|
+
const flagsStr = buildClaudeFlags(config.claude.dispatchFlags);
|
|
347
|
+
const resumeCmd = `cd '${projectRoot}' && claude ${flagsStr} --resume '${claude_session_id}'`;
|
|
470
348
|
|
|
471
349
|
try {
|
|
472
350
|
await launchInTerminal(resumeCmd);
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import type Database from 'better-sqlite3';
|
|
3
|
-
import type {
|
|
3
|
+
import type { Emitter } from '../project-emitter.js';
|
|
4
4
|
import type { ScopeService } from '../services/scope-service.js';
|
|
5
|
-
import { launchInCategorizedTerminal, escapeForAnsiC, buildSessionName, snapshotSessionPids, discoverNewSession, renameSession } from '../utils/terminal-launcher.js';
|
|
5
|
+
import { launchInCategorizedTerminal, escapeForAnsiC, shellQuote, buildSessionName, snapshotSessionPids, discoverNewSession, renameSession } from '../utils/terminal-launcher.js';
|
|
6
6
|
import { resolveDispatchEvent, resolveAbandonedDispatchesForScope, getActiveScopeIds, getAbandonedScopeIds, linkPidToDispatch } from '../utils/dispatch-utils.js';
|
|
7
7
|
import type { WorkflowEngine } from '../../shared/workflow-engine.js';
|
|
8
|
+
import type { OrbitalConfig } from '../config.js';
|
|
9
|
+
import { buildClaudeFlags, buildEnvVarPrefix } from '../utils/flag-builder.js';
|
|
8
10
|
import { createLogger } from '../utils/logger.js';
|
|
9
11
|
|
|
10
12
|
const log = createLogger('dispatch');
|
|
11
13
|
|
|
12
|
-
const
|
|
14
|
+
const DEFAULT_MAX_BATCH_SIZE = 20;
|
|
13
15
|
|
|
14
16
|
interface DispatchBody {
|
|
15
17
|
scope_id?: number;
|
|
@@ -20,13 +22,14 @@ interface DispatchBody {
|
|
|
20
22
|
|
|
21
23
|
interface DispatchRouteDeps {
|
|
22
24
|
db: Database.Database;
|
|
23
|
-
io:
|
|
25
|
+
io: Emitter;
|
|
24
26
|
scopeService: ScopeService;
|
|
25
27
|
projectRoot: string;
|
|
26
28
|
engine: WorkflowEngine;
|
|
29
|
+
config: OrbitalConfig;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine }: DispatchRouteDeps): Router {
|
|
32
|
+
export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine, config }: DispatchRouteDeps): Router {
|
|
30
33
|
const router = Router();
|
|
31
34
|
|
|
32
35
|
router.get('/dispatch/active-scopes', (_req, res) => {
|
|
@@ -79,6 +82,19 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
|
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
|
|
85
|
+
// Max concurrent dispatches guard
|
|
86
|
+
const maxConcurrent = config.dispatch.maxConcurrent;
|
|
87
|
+
if (maxConcurrent > 0) {
|
|
88
|
+
const activeCount = (db.prepare(
|
|
89
|
+
`SELECT COUNT(*) as count FROM events
|
|
90
|
+
WHERE type = 'DISPATCH' AND JSON_EXTRACT(data, '$.resolved') IS NULL`
|
|
91
|
+
).get() as { count: number }).count;
|
|
92
|
+
if (activeCount >= maxConcurrent) {
|
|
93
|
+
res.status(429).json({ error: `Max concurrent dispatches reached (${maxConcurrent})` });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
82
98
|
// Update scope status if transition provided
|
|
83
99
|
if (scope_id != null && transition?.to) {
|
|
84
100
|
const result = scopeService.updateStatus(scope_id, transition.to, 'dispatch');
|
|
@@ -107,22 +123,28 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
|
|
|
107
123
|
const sessionName = buildSessionName({ scopeId: scope_id ?? undefined, title: scope?.title, command });
|
|
108
124
|
const beforePids = snapshotSessionPids(projectRoot);
|
|
109
125
|
|
|
110
|
-
// Launch in iTerm — interactive TUI mode (no -p) for full visibility
|
|
126
|
+
// Launch in iTerm — interactive TUI mode (no -p unless printMode) for full visibility
|
|
111
127
|
const promptText = prompt ?? command;
|
|
112
128
|
const escaped = escapeForAnsiC(promptText);
|
|
113
|
-
const
|
|
129
|
+
const flagsStr = buildClaudeFlags(config.claude.dispatchFlags);
|
|
130
|
+
const envPrefix = buildEnvVarPrefix(config.dispatch.envVars);
|
|
131
|
+
const fullCmd = `cd '${shellQuote(projectRoot)}' && ${envPrefix}ORBITAL_DISPATCH_ID='${shellQuote(eventId)}' claude ${flagsStr} $'${escaped}'`;
|
|
114
132
|
try {
|
|
115
133
|
await launchInCategorizedTerminal(command, fullCmd, sessionName);
|
|
116
134
|
res.json({ ok: true, dispatch_id: eventId, scope_id: scope_id ?? null });
|
|
117
135
|
|
|
118
|
-
// Fire-and-forget: discover session PID, link to dispatch, and rename
|
|
136
|
+
// Fire-and-forget: discover session PID, link to dispatch, and rename.
|
|
137
|
+
// If discovery fails, SESSION_START event handler will link via ORBITAL_DISPATCH_ID.
|
|
119
138
|
discoverNewSession(projectRoot, beforePids)
|
|
120
139
|
.then((session) => {
|
|
121
|
-
if (!session)
|
|
140
|
+
if (!session) {
|
|
141
|
+
log.warn('PID discovery returned null — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId, scope_id });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
122
144
|
linkPidToDispatch(db, eventId, session.pid);
|
|
123
145
|
if (sessionName) renameSession(projectRoot, session.sessionId, sessionName);
|
|
124
146
|
})
|
|
125
|
-
.catch(err => log.
|
|
147
|
+
.catch(err => log.warn('PID discovery failed — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId, error: err.message }));
|
|
126
148
|
} catch (err) {
|
|
127
149
|
if (scope_id != null && transition?.from) {
|
|
128
150
|
scopeService.updateStatus(scope_id, transition.from, 'rollback');
|
|
@@ -211,8 +233,9 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
|
|
|
211
233
|
}
|
|
212
234
|
|
|
213
235
|
// W-12: Validate batch size and scope ID types
|
|
214
|
-
|
|
215
|
-
|
|
236
|
+
const maxBatch = config.dispatch.maxBatchSize || DEFAULT_MAX_BATCH_SIZE;
|
|
237
|
+
if (scope_ids.length > maxBatch) {
|
|
238
|
+
res.status(400).json({ error: `Maximum batch size is ${maxBatch}` });
|
|
216
239
|
return;
|
|
217
240
|
}
|
|
218
241
|
if (!scope_ids.every(id => Number.isInteger(id) && id > 0)) {
|
|
@@ -245,21 +268,27 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
|
|
|
245
268
|
timestamp: new Date().toISOString(),
|
|
246
269
|
});
|
|
247
270
|
|
|
248
|
-
// Launch single CLI session
|
|
271
|
+
// Launch single CLI session with batch env vars
|
|
249
272
|
const batchEscaped = escapeForAnsiC(command);
|
|
250
273
|
const beforePids = snapshotSessionPids(projectRoot);
|
|
251
|
-
const
|
|
274
|
+
const batchFlags = buildClaudeFlags(config.claude.dispatchFlags);
|
|
275
|
+
const envPrefix = buildEnvVarPrefix(config.dispatch.envVars);
|
|
276
|
+
const fullCmd = `cd '${shellQuote(projectRoot)}' && ${envPrefix}ORBITAL_DISPATCH_ID='${shellQuote(eventId)}' claude ${batchFlags} $'${batchEscaped}'`;
|
|
252
277
|
try {
|
|
253
278
|
await launchInCategorizedTerminal(command, fullCmd);
|
|
254
279
|
res.json({ ok: true, dispatch_id: eventId, scope_ids });
|
|
255
280
|
|
|
256
|
-
// Fire-and-forget: discover session PID and link to dispatch
|
|
281
|
+
// Fire-and-forget: discover session PID and link to dispatch.
|
|
282
|
+
// If discovery fails, SESSION_START event handler will link via ORBITAL_DISPATCH_ID.
|
|
257
283
|
discoverNewSession(projectRoot, beforePids)
|
|
258
284
|
.then((session) => {
|
|
259
|
-
if (!session)
|
|
285
|
+
if (!session) {
|
|
286
|
+
log.warn('Batch PID discovery returned null — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId });
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
260
289
|
linkPidToDispatch(db, eventId, session.pid);
|
|
261
290
|
})
|
|
262
|
-
.catch(err => log.
|
|
291
|
+
.catch(err => log.warn('Batch PID discovery failed — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId, error: err.message }));
|
|
263
292
|
} catch (err) {
|
|
264
293
|
if (transition?.from) {
|
|
265
294
|
for (const id of scope_ids) {
|