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,5 +1,6 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import { ConfigService, isValidPrimitiveType } from '../services/config-service.js';
|
|
3
|
+
import { catchRoute, inferErrorStatus } from '../utils/route-helpers.js';
|
|
3
4
|
export function createConfigRoutes({ projectRoot, workflowService: _workflowService, io }) {
|
|
4
5
|
const router = Router();
|
|
5
6
|
const configService = new ConfigService(projectRoot);
|
|
@@ -12,21 +13,16 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
|
|
|
12
13
|
return typeParam;
|
|
13
14
|
}
|
|
14
15
|
// GET /config/:type/tree — directory tree with frontmatter
|
|
15
|
-
router.get('/config/:type/tree', (req, res) => {
|
|
16
|
+
router.get('/config/:type/tree', catchRoute((req, res) => {
|
|
16
17
|
const type = parseType(req.params.type, res);
|
|
17
18
|
if (!type)
|
|
18
19
|
return;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
catch (err) {
|
|
25
|
-
res.status(500).json({ success: false, error: errMsg(err) });
|
|
26
|
-
}
|
|
27
|
-
});
|
|
20
|
+
const basePath = configService.getBasePath(type);
|
|
21
|
+
const tree = configService.scanDirectory(basePath);
|
|
22
|
+
res.json({ success: true, data: tree });
|
|
23
|
+
}));
|
|
28
24
|
// GET /config/:type/file?path=<relative> — file content
|
|
29
|
-
router.get('/config/:type/file', (req, res) => {
|
|
25
|
+
router.get('/config/:type/file', catchRoute((req, res) => {
|
|
30
26
|
const type = parseType(req.params.type, res);
|
|
31
27
|
if (!type)
|
|
32
28
|
return;
|
|
@@ -35,19 +31,12 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
|
|
|
35
31
|
res.status(400).json({ success: false, error: 'path query parameter is required' });
|
|
36
32
|
return;
|
|
37
33
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
catch (err) {
|
|
44
|
-
const msg = errMsg(err);
|
|
45
|
-
const status = msg.includes('traversal') ? 403 : msg.includes('ENOENT') || msg.includes('not found') ? 404 : 500;
|
|
46
|
-
res.status(status).json({ success: false, error: msg });
|
|
47
|
-
}
|
|
48
|
-
});
|
|
34
|
+
const basePath = configService.getBasePath(type);
|
|
35
|
+
const content = configService.readFile(basePath, filePath);
|
|
36
|
+
res.json({ success: true, data: { path: filePath, content } });
|
|
37
|
+
}, inferErrorStatus));
|
|
49
38
|
// PUT /config/:type/file — save file { path, content }
|
|
50
|
-
router.put('/config/:type/file', (req, res) => {
|
|
39
|
+
router.put('/config/:type/file', catchRoute((req, res) => {
|
|
51
40
|
const type = parseType(req.params.type, res);
|
|
52
41
|
if (!type)
|
|
53
42
|
return;
|
|
@@ -56,20 +45,13 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
|
|
|
56
45
|
res.status(400).json({ success: false, error: 'path and content are required' });
|
|
57
46
|
return;
|
|
58
47
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
const msg = errMsg(err);
|
|
67
|
-
const status = msg.includes('traversal') ? 403 : msg.includes('not found') ? 404 : 500;
|
|
68
|
-
res.status(status).json({ success: false, error: msg });
|
|
69
|
-
}
|
|
70
|
-
});
|
|
48
|
+
const basePath = configService.getBasePath(type);
|
|
49
|
+
configService.writeFile(basePath, filePath, content);
|
|
50
|
+
io.emit(`config:${type}:changed`, { action: 'updated', path: filePath });
|
|
51
|
+
res.json({ success: true });
|
|
52
|
+
}, inferErrorStatus));
|
|
71
53
|
// POST /config/:type/file — create file { path, content }
|
|
72
|
-
router.post('/config/:type/file', (req, res) => {
|
|
54
|
+
router.post('/config/:type/file', catchRoute((req, res) => {
|
|
73
55
|
const type = parseType(req.params.type, res);
|
|
74
56
|
if (!type)
|
|
75
57
|
return;
|
|
@@ -78,20 +60,13 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
|
|
|
78
60
|
res.status(400).json({ success: false, error: 'path and content are required' });
|
|
79
61
|
return;
|
|
80
62
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
catch (err) {
|
|
88
|
-
const msg = errMsg(err);
|
|
89
|
-
const status = msg.includes('traversal') ? 403 : msg.includes('already exists') ? 409 : 500;
|
|
90
|
-
res.status(status).json({ success: false, error: msg });
|
|
91
|
-
}
|
|
92
|
-
});
|
|
63
|
+
const basePath = configService.getBasePath(type);
|
|
64
|
+
configService.createFile(basePath, filePath, content);
|
|
65
|
+
io.emit(`config:${type}:changed`, { action: 'created', path: filePath });
|
|
66
|
+
res.status(201).json({ success: true });
|
|
67
|
+
}, inferErrorStatus));
|
|
93
68
|
// DELETE /config/:type/file?path=<relative> — delete file
|
|
94
|
-
router.delete('/config/:type/file', (req, res) => {
|
|
69
|
+
router.delete('/config/:type/file', catchRoute((req, res) => {
|
|
95
70
|
const type = parseType(req.params.type, res);
|
|
96
71
|
if (!type)
|
|
97
72
|
return;
|
|
@@ -100,20 +75,13 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
|
|
|
100
75
|
res.status(400).json({ success: false, error: 'path query parameter is required' });
|
|
101
76
|
return;
|
|
102
77
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
catch (err) {
|
|
110
|
-
const msg = errMsg(err);
|
|
111
|
-
const status = msg.includes('traversal') ? 403 : msg.includes('not found') ? 404 : msg.includes('directory') ? 400 : 500;
|
|
112
|
-
res.status(status).json({ success: false, error: msg });
|
|
113
|
-
}
|
|
114
|
-
});
|
|
78
|
+
const basePath = configService.getBasePath(type);
|
|
79
|
+
configService.deleteFile(basePath, filePath);
|
|
80
|
+
io.emit(`config:${type}:changed`, { action: 'deleted', path: filePath });
|
|
81
|
+
res.json({ success: true });
|
|
82
|
+
}, inferErrorStatus));
|
|
115
83
|
// POST /config/:type/rename — rename { oldPath, newPath }
|
|
116
|
-
router.post('/config/:type/rename', (req, res) => {
|
|
84
|
+
router.post('/config/:type/rename', catchRoute((req, res) => {
|
|
117
85
|
const type = parseType(req.params.type, res);
|
|
118
86
|
if (!type)
|
|
119
87
|
return;
|
|
@@ -122,20 +90,13 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
|
|
|
122
90
|
res.status(400).json({ success: false, error: 'oldPath and newPath are required' });
|
|
123
91
|
return;
|
|
124
92
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
catch (err) {
|
|
132
|
-
const msg = errMsg(err);
|
|
133
|
-
const status = msg.includes('traversal') ? 403 : msg.includes('not found') ? 404 : msg.includes('already exists') ? 409 : 500;
|
|
134
|
-
res.status(status).json({ success: false, error: msg });
|
|
135
|
-
}
|
|
136
|
-
});
|
|
93
|
+
const basePath = configService.getBasePath(type);
|
|
94
|
+
configService.renameFile(basePath, oldPath, newPath);
|
|
95
|
+
io.emit(`config:${type}:changed`, { action: 'renamed', oldPath, newPath });
|
|
96
|
+
res.json({ success: true });
|
|
97
|
+
}, inferErrorStatus));
|
|
137
98
|
// POST /config/:type/folder — create folder { path }
|
|
138
|
-
router.post('/config/:type/folder', (req, res) => {
|
|
99
|
+
router.post('/config/:type/folder', catchRoute((req, res) => {
|
|
139
100
|
const type = parseType(req.params.type, res);
|
|
140
101
|
if (!type)
|
|
141
102
|
return;
|
|
@@ -144,20 +105,10 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
|
|
|
144
105
|
res.status(400).json({ success: false, error: 'path is required' });
|
|
145
106
|
return;
|
|
146
107
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
catch (err) {
|
|
154
|
-
const msg = errMsg(err);
|
|
155
|
-
const status = msg.includes('traversal') ? 403 : msg.includes('already exists') ? 409 : 500;
|
|
156
|
-
res.status(status).json({ success: false, error: msg });
|
|
157
|
-
}
|
|
158
|
-
});
|
|
108
|
+
const basePath = configService.getBasePath(type);
|
|
109
|
+
configService.createFolder(basePath, folderPath);
|
|
110
|
+
io.emit(`config:${type}:changed`, { action: 'folder-created', path: folderPath });
|
|
111
|
+
res.status(201).json({ success: true });
|
|
112
|
+
}, inferErrorStatus));
|
|
159
113
|
return router;
|
|
160
114
|
}
|
|
161
|
-
function errMsg(err) {
|
|
162
|
-
return err instanceof Error ? err.message : String(err);
|
|
163
|
-
}
|
|
@@ -5,91 +5,19 @@ import { promisify } from 'util';
|
|
|
5
5
|
import { getHookEnforcement } from '../../shared/workflow-config.js';
|
|
6
6
|
import { getClaudeSessions, getSessionStats } from '../services/claude-session-service.js';
|
|
7
7
|
import { launchInTerminal } from '../utils/terminal-launcher.js';
|
|
8
|
+
import { buildClaudeFlags } from '../utils/flag-builder.js';
|
|
9
|
+
import { createLogger } from '../utils/logger.js';
|
|
10
|
+
import { parseJsonFields } from '../utils/json-fields.js';
|
|
11
|
+
const log = createLogger('server');
|
|
8
12
|
const execFileAsync = promisify(execFile);
|
|
9
|
-
|
|
10
|
-
function parseJsonFields(row) {
|
|
11
|
-
const parsed = { ...row };
|
|
12
|
-
for (const field of JSON_FIELDS) {
|
|
13
|
-
if (typeof parsed[field] === 'string') {
|
|
14
|
-
try {
|
|
15
|
-
parsed[field] = JSON.parse(parsed[field]);
|
|
16
|
-
}
|
|
17
|
-
catch { /* keep string */ }
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return parsed;
|
|
21
|
-
}
|
|
22
|
-
function parseDriftCommits(raw) {
|
|
23
|
-
if (!raw)
|
|
24
|
-
return [];
|
|
25
|
-
return raw.split('\n').map((line) => {
|
|
26
|
-
const [sha, date, message, author] = line.split('|');
|
|
27
|
-
return { sha, date, message: message ?? '', author: author ?? '' };
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
function parseHead(raw) {
|
|
31
|
-
const [sha, date, message] = raw.split('|');
|
|
32
|
-
return { sha: sha ?? '', date: date ?? '', message: message ?? '' };
|
|
33
|
-
}
|
|
34
|
-
export function createDataRoutes({ db, io, gateService, deployService, engine, projectRoot, inferScopeStatus, }) {
|
|
13
|
+
export function createDataRoutes({ db, io, eventService, gateService, deployService, gitService, engine, projectRoot, inferScopeStatus, config, }) {
|
|
35
14
|
const router = Router();
|
|
36
|
-
// ─── Pipeline Drift (cached) ─────────────────────────────
|
|
37
|
-
let driftCache = null;
|
|
38
|
-
const DRIFT_CACHE_MS = 60_000;
|
|
39
|
-
async function gitLog(args) {
|
|
40
|
-
const { stdout } = await execFileAsync('git', args, { cwd: projectRoot });
|
|
41
|
-
return stdout.trim();
|
|
42
|
-
}
|
|
43
|
-
async function computeDrift() {
|
|
44
|
-
if (driftCache && Date.now() - driftCache.ts < DRIFT_CACHE_MS)
|
|
45
|
-
return driftCache.data;
|
|
46
|
-
const [devToStagingRaw, stagingToMainRaw, devHead, stagingHead, mainHead] = await Promise.all([
|
|
47
|
-
gitLog(['log', 'origin/dev', '--not', 'origin/staging', '--reverse', '--format=%H|%aI|%s|%an']),
|
|
48
|
-
gitLog(['log', 'origin/staging', '--not', 'origin/main', '--reverse', '--format=%H|%aI|%s|%an']),
|
|
49
|
-
gitLog(['log', 'origin/dev', '-1', '--format=%H|%aI|%s']),
|
|
50
|
-
gitLog(['log', 'origin/staging', '-1', '--format=%H|%aI|%s']),
|
|
51
|
-
gitLog(['log', 'origin/main', '-1', '--format=%H|%aI|%s']),
|
|
52
|
-
]);
|
|
53
|
-
const devToStaging = parseDriftCommits(devToStagingRaw);
|
|
54
|
-
const stagingToMain = parseDriftCommits(stagingToMainRaw);
|
|
55
|
-
const data = {
|
|
56
|
-
devToStaging: {
|
|
57
|
-
count: devToStaging.length,
|
|
58
|
-
commits: devToStaging,
|
|
59
|
-
oldestDate: devToStaging[0]?.date ?? null,
|
|
60
|
-
},
|
|
61
|
-
stagingToMain: {
|
|
62
|
-
count: stagingToMain.length,
|
|
63
|
-
commits: stagingToMain,
|
|
64
|
-
oldestDate: stagingToMain[0]?.date ?? null,
|
|
65
|
-
},
|
|
66
|
-
heads: {
|
|
67
|
-
dev: parseHead(devHead),
|
|
68
|
-
staging: parseHead(stagingHead),
|
|
69
|
-
main: parseHead(mainHead),
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
driftCache = { data, ts: Date.now() };
|
|
73
|
-
return data;
|
|
74
|
-
}
|
|
75
15
|
// ─── Event Routes ──────────────────────────────────────────
|
|
76
16
|
router.get('/events', (req, res) => {
|
|
77
17
|
const limit = Number(req.query.limit) || 50;
|
|
78
18
|
const type = req.query.type;
|
|
79
|
-
const scopeId = req.query.scope_id;
|
|
80
|
-
|
|
81
|
-
const params = [];
|
|
82
|
-
if (type) {
|
|
83
|
-
query += ' AND type = ?';
|
|
84
|
-
params.push(type);
|
|
85
|
-
}
|
|
86
|
-
if (scopeId) {
|
|
87
|
-
query += ' AND scope_id = ?';
|
|
88
|
-
params.push(Number(scopeId));
|
|
89
|
-
}
|
|
90
|
-
query += ' ORDER BY timestamp DESC LIMIT ?';
|
|
91
|
-
params.push(limit);
|
|
92
|
-
const events = db.prepare(query).all(...params);
|
|
19
|
+
const scopeId = req.query.scope_id ? Number(req.query.scope_id) : undefined;
|
|
20
|
+
const events = eventService.getFiltered({ limit, type, scopeId });
|
|
93
21
|
res.json(events.map(parseJsonFields));
|
|
94
22
|
});
|
|
95
23
|
router.post('/events', (req, res) => {
|
|
@@ -115,18 +43,10 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
|
|
|
115
43
|
// ─── Violations Summary ──────────────────────────────────
|
|
116
44
|
router.get('/events/violations/summary', (_req, res) => {
|
|
117
45
|
try {
|
|
118
|
-
|
|
119
|
-
FROM events WHERE type = 'VIOLATION' GROUP BY rule ORDER BY count DESC`).all();
|
|
120
|
-
const byFile = db.prepare(`SELECT JSON_EXTRACT(data, '$.file') as file, COUNT(*) as count FROM events
|
|
121
|
-
WHERE type = 'VIOLATION' AND JSON_EXTRACT(data, '$.file') IS NOT NULL AND JSON_EXTRACT(data, '$.file') != ''
|
|
122
|
-
GROUP BY file ORDER BY count DESC LIMIT 20`).all();
|
|
123
|
-
const overrides = db.prepare(`SELECT JSON_EXTRACT(data, '$.rule') as rule, JSON_EXTRACT(data, '$.reason') as reason, timestamp as date
|
|
124
|
-
FROM events WHERE type = 'OVERRIDE' ORDER BY timestamp DESC LIMIT 50`).all();
|
|
125
|
-
const totalViolations = db.prepare(`SELECT COUNT(*) as count FROM events WHERE type = 'VIOLATION'`).get();
|
|
126
|
-
const totalOverrides = db.prepare(`SELECT COUNT(*) as count FROM events WHERE type = 'OVERRIDE'`).get();
|
|
127
|
-
res.json({ byRule, byFile, overrides, totalViolations: totalViolations.count, totalOverrides: totalOverrides.count });
|
|
46
|
+
res.json(eventService.getViolationSummary());
|
|
128
47
|
}
|
|
129
|
-
catch {
|
|
48
|
+
catch (err) {
|
|
49
|
+
log.error('Violations summary failed', { error: String(err) });
|
|
130
50
|
res.status(500).json({ error: 'Failed to query violations summary' });
|
|
131
51
|
}
|
|
132
52
|
});
|
|
@@ -144,13 +64,7 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
|
|
|
144
64
|
hookEdgeMap.get(hookId).push({ from: edge.from, to: edge.to, label: edge.label });
|
|
145
65
|
}
|
|
146
66
|
}
|
|
147
|
-
|
|
148
|
-
const violationStats = db.prepare(`SELECT JSON_EXTRACT(data, '$.rule') as rule, COUNT(*) as count, MAX(timestamp) as last_seen
|
|
149
|
-
FROM events WHERE type = 'VIOLATION' GROUP BY rule`).all();
|
|
150
|
-
const overrideStats = db.prepare(`SELECT JSON_EXTRACT(data, '$.rule') as rule, COUNT(*) as count
|
|
151
|
-
FROM events WHERE type = 'OVERRIDE' GROUP BY rule`).all();
|
|
152
|
-
const violationMap = new Map(violationStats.map((v) => [v.rule, v]));
|
|
153
|
-
const overrideMap = new Map(overrideStats.map((o) => [o.rule, o]));
|
|
67
|
+
const { violations: violationMap, overrides: overrideMap } = eventService.getViolationStatsByRule();
|
|
154
68
|
// Build summary counts
|
|
155
69
|
const summary = { guards: 0, gates: 0, lifecycle: 0, observers: 0 };
|
|
156
70
|
for (const hook of allHooks) {
|
|
@@ -175,20 +89,19 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
|
|
|
175
89
|
}));
|
|
176
90
|
res.json({ summary, rules, totalEdges: allEdges.length });
|
|
177
91
|
}
|
|
178
|
-
catch {
|
|
92
|
+
catch (err) {
|
|
93
|
+
log.error('Enforcement rules failed', { error: String(err) });
|
|
179
94
|
res.status(500).json({ error: 'Failed to query enforcement rules' });
|
|
180
95
|
}
|
|
181
96
|
});
|
|
182
97
|
// ─── Violation Trends ──────────────────────────────────────
|
|
183
98
|
router.get('/events/violations/trend', (req, res) => {
|
|
184
99
|
try {
|
|
185
|
-
const days = Number(req.query.days) || 30;
|
|
186
|
-
|
|
187
|
-
FROM events WHERE type = 'VIOLATION' AND timestamp >= datetime('now', ? || ' days')
|
|
188
|
-
GROUP BY day, rule ORDER BY day ASC`).all(`-${days}`);
|
|
189
|
-
res.json(trend);
|
|
100
|
+
const days = Math.max(1, Math.min(365, Number(req.query.days) || 30));
|
|
101
|
+
res.json(eventService.getViolationTrend(days));
|
|
190
102
|
}
|
|
191
|
-
catch {
|
|
103
|
+
catch (err) {
|
|
104
|
+
log.error('Violation trends failed', { error: String(err) });
|
|
192
105
|
res.status(500).json({ error: 'Failed to query violation trends' });
|
|
193
106
|
}
|
|
194
107
|
});
|
|
@@ -251,8 +164,7 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
|
|
|
251
164
|
});
|
|
252
165
|
router.get('/pipeline/drift', async (_req, res) => {
|
|
253
166
|
try {
|
|
254
|
-
|
|
255
|
-
res.json(drift);
|
|
167
|
+
res.json(await gitService.getPipelineDrift());
|
|
256
168
|
}
|
|
257
169
|
catch (err) {
|
|
258
170
|
res.status(500).json({ error: 'Failed to compute drift', details: String(err) });
|
|
@@ -260,21 +172,10 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
|
|
|
260
172
|
});
|
|
261
173
|
router.get('/deployments/frequency', (_req, res) => {
|
|
262
174
|
try {
|
|
263
|
-
|
|
264
|
-
FROM deployments WHERE started_at > datetime('now', '-56 days') GROUP BY environment, week ORDER BY week ASC`).all();
|
|
265
|
-
const weekMap = new Map();
|
|
266
|
-
for (const row of rows) {
|
|
267
|
-
if (!weekMap.has(row.week))
|
|
268
|
-
weekMap.set(row.week, { week: row.week, staging: 0, production: 0 });
|
|
269
|
-
const entry = weekMap.get(row.week);
|
|
270
|
-
if (row.environment === 'staging')
|
|
271
|
-
entry.staging = row.count;
|
|
272
|
-
if (row.environment === 'production')
|
|
273
|
-
entry.production = row.count;
|
|
274
|
-
}
|
|
275
|
-
res.json([...weekMap.values()]);
|
|
175
|
+
res.json(eventService.getDeployFrequency());
|
|
276
176
|
}
|
|
277
|
-
catch {
|
|
177
|
+
catch (err) {
|
|
178
|
+
log.error('Deploy frequency query failed', { error: String(err) });
|
|
278
179
|
res.status(500).json({ error: 'Failed to query deployment frequency' });
|
|
279
180
|
}
|
|
280
181
|
});
|
|
@@ -330,7 +231,7 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
|
|
|
330
231
|
let meta = null;
|
|
331
232
|
let stats = null;
|
|
332
233
|
if (parsed.claude_session_id && typeof parsed.claude_session_id === 'string') {
|
|
333
|
-
const claudeSessions = await getClaudeSessions();
|
|
234
|
+
const claudeSessions = await getClaudeSessions(undefined, projectRoot);
|
|
334
235
|
const match = claudeSessions.find(s => s.id === parsed.claude_session_id);
|
|
335
236
|
if (match) {
|
|
336
237
|
meta = {
|
|
@@ -342,7 +243,7 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
|
|
|
342
243
|
lastActiveAt: match.lastActiveAt,
|
|
343
244
|
};
|
|
344
245
|
}
|
|
345
|
-
stats = getSessionStats(parsed.claude_session_id);
|
|
246
|
+
stats = getSessionStats(parsed.claude_session_id, projectRoot);
|
|
346
247
|
}
|
|
347
248
|
if (!content) {
|
|
348
249
|
const parts = [];
|
|
@@ -377,7 +278,8 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
|
|
|
377
278
|
res.status(400).json({ error: 'Valid claude_session_id (UUID) required' });
|
|
378
279
|
return;
|
|
379
280
|
}
|
|
380
|
-
const
|
|
281
|
+
const flagsStr = buildClaudeFlags(config.claude.dispatchFlags);
|
|
282
|
+
const resumeCmd = `cd '${projectRoot}' && claude ${flagsStr} --resume '${claude_session_id}'`;
|
|
381
283
|
try {
|
|
382
284
|
await launchInTerminal(resumeCmd);
|
|
383
285
|
res.json({ ok: true, session_id: claude_session_id });
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
|
-
import { launchInCategorizedTerminal, escapeForAnsiC, buildSessionName, snapshotSessionPids, discoverNewSession, renameSession } from '../utils/terminal-launcher.js';
|
|
2
|
+
import { launchInCategorizedTerminal, escapeForAnsiC, shellQuote, buildSessionName, snapshotSessionPids, discoverNewSession, renameSession } from '../utils/terminal-launcher.js';
|
|
3
3
|
import { resolveDispatchEvent, resolveAbandonedDispatchesForScope, getActiveScopeIds, getAbandonedScopeIds, linkPidToDispatch } from '../utils/dispatch-utils.js';
|
|
4
|
+
import { buildClaudeFlags, buildEnvVarPrefix } from '../utils/flag-builder.js';
|
|
4
5
|
import { createLogger } from '../utils/logger.js';
|
|
5
6
|
const log = createLogger('dispatch');
|
|
6
|
-
const
|
|
7
|
-
export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine }) {
|
|
7
|
+
const DEFAULT_MAX_BATCH_SIZE = 20;
|
|
8
|
+
export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine, config }) {
|
|
8
9
|
const router = Router();
|
|
9
10
|
router.get('/dispatch/active-scopes', (_req, res) => {
|
|
10
11
|
const scope_ids = getActiveScopeIds(db, scopeService, engine);
|
|
@@ -44,6 +45,16 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
|
|
|
44
45
|
return;
|
|
45
46
|
}
|
|
46
47
|
}
|
|
48
|
+
// Max concurrent dispatches guard
|
|
49
|
+
const maxConcurrent = config.dispatch.maxConcurrent;
|
|
50
|
+
if (maxConcurrent > 0) {
|
|
51
|
+
const activeCount = db.prepare(`SELECT COUNT(*) as count FROM events
|
|
52
|
+
WHERE type = 'DISPATCH' AND JSON_EXTRACT(data, '$.resolved') IS NULL`).get().count;
|
|
53
|
+
if (activeCount >= maxConcurrent) {
|
|
54
|
+
res.status(429).json({ error: `Max concurrent dispatches reached (${maxConcurrent})` });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
47
58
|
// Update scope status if transition provided
|
|
48
59
|
if (scope_id != null && transition?.to) {
|
|
49
60
|
const result = scopeService.updateStatus(scope_id, transition.to, 'dispatch');
|
|
@@ -66,23 +77,28 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
|
|
|
66
77
|
const scope = scope_id != null ? scopeService.getById(scope_id) : undefined;
|
|
67
78
|
const sessionName = buildSessionName({ scopeId: scope_id ?? undefined, title: scope?.title, command });
|
|
68
79
|
const beforePids = snapshotSessionPids(projectRoot);
|
|
69
|
-
// Launch in iTerm — interactive TUI mode (no -p) for full visibility
|
|
80
|
+
// Launch in iTerm — interactive TUI mode (no -p unless printMode) for full visibility
|
|
70
81
|
const promptText = prompt ?? command;
|
|
71
82
|
const escaped = escapeForAnsiC(promptText);
|
|
72
|
-
const
|
|
83
|
+
const flagsStr = buildClaudeFlags(config.claude.dispatchFlags);
|
|
84
|
+
const envPrefix = buildEnvVarPrefix(config.dispatch.envVars);
|
|
85
|
+
const fullCmd = `cd '${shellQuote(projectRoot)}' && ${envPrefix}ORBITAL_DISPATCH_ID='${shellQuote(eventId)}' claude ${flagsStr} $'${escaped}'`;
|
|
73
86
|
try {
|
|
74
87
|
await launchInCategorizedTerminal(command, fullCmd, sessionName);
|
|
75
88
|
res.json({ ok: true, dispatch_id: eventId, scope_id: scope_id ?? null });
|
|
76
|
-
// Fire-and-forget: discover session PID, link to dispatch, and rename
|
|
89
|
+
// Fire-and-forget: discover session PID, link to dispatch, and rename.
|
|
90
|
+
// If discovery fails, SESSION_START event handler will link via ORBITAL_DISPATCH_ID.
|
|
77
91
|
discoverNewSession(projectRoot, beforePids)
|
|
78
92
|
.then((session) => {
|
|
79
|
-
if (!session)
|
|
93
|
+
if (!session) {
|
|
94
|
+
log.warn('PID discovery returned null — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId, scope_id });
|
|
80
95
|
return;
|
|
96
|
+
}
|
|
81
97
|
linkPidToDispatch(db, eventId, session.pid);
|
|
82
98
|
if (sessionName)
|
|
83
99
|
renameSession(projectRoot, session.sessionId, sessionName);
|
|
84
100
|
})
|
|
85
|
-
.catch(err => log.
|
|
101
|
+
.catch(err => log.warn('PID discovery failed — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId, error: err.message }));
|
|
86
102
|
}
|
|
87
103
|
catch (err) {
|
|
88
104
|
if (scope_id != null && transition?.from) {
|
|
@@ -157,8 +173,9 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
|
|
|
157
173
|
return;
|
|
158
174
|
}
|
|
159
175
|
// W-12: Validate batch size and scope ID types
|
|
160
|
-
|
|
161
|
-
|
|
176
|
+
const maxBatch = config.dispatch.maxBatchSize || DEFAULT_MAX_BATCH_SIZE;
|
|
177
|
+
if (scope_ids.length > maxBatch) {
|
|
178
|
+
res.status(400).json({ error: `Maximum batch size is ${maxBatch}` });
|
|
162
179
|
return;
|
|
163
180
|
}
|
|
164
181
|
if (!scope_ids.every(id => Number.isInteger(id) && id > 0)) {
|
|
@@ -185,21 +202,26 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
|
|
|
185
202
|
session_id: null, agent: 'dashboard', data: eventData,
|
|
186
203
|
timestamp: new Date().toISOString(),
|
|
187
204
|
});
|
|
188
|
-
// Launch single CLI session
|
|
205
|
+
// Launch single CLI session with batch env vars
|
|
189
206
|
const batchEscaped = escapeForAnsiC(command);
|
|
190
207
|
const beforePids = snapshotSessionPids(projectRoot);
|
|
191
|
-
const
|
|
208
|
+
const batchFlags = buildClaudeFlags(config.claude.dispatchFlags);
|
|
209
|
+
const envPrefix = buildEnvVarPrefix(config.dispatch.envVars);
|
|
210
|
+
const fullCmd = `cd '${shellQuote(projectRoot)}' && ${envPrefix}ORBITAL_DISPATCH_ID='${shellQuote(eventId)}' claude ${batchFlags} $'${batchEscaped}'`;
|
|
192
211
|
try {
|
|
193
212
|
await launchInCategorizedTerminal(command, fullCmd);
|
|
194
213
|
res.json({ ok: true, dispatch_id: eventId, scope_ids });
|
|
195
|
-
// Fire-and-forget: discover session PID and link to dispatch
|
|
214
|
+
// Fire-and-forget: discover session PID and link to dispatch.
|
|
215
|
+
// If discovery fails, SESSION_START event handler will link via ORBITAL_DISPATCH_ID.
|
|
196
216
|
discoverNewSession(projectRoot, beforePids)
|
|
197
217
|
.then((session) => {
|
|
198
|
-
if (!session)
|
|
218
|
+
if (!session) {
|
|
219
|
+
log.warn('Batch PID discovery returned null — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId });
|
|
199
220
|
return;
|
|
221
|
+
}
|
|
200
222
|
linkPidToDispatch(db, eventId, session.pid);
|
|
201
223
|
})
|
|
202
|
-
.catch(err => log.
|
|
224
|
+
.catch(err => log.warn('Batch PID discovery failed — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId, error: err.message }));
|
|
203
225
|
}
|
|
204
226
|
catch (err) {
|
|
205
227
|
if (transition?.from) {
|
|
@@ -88,5 +88,79 @@ export function createGitRoutes({ gitService, githubService, engine }) {
|
|
|
88
88
|
res.status(500).json({ error: 'Failed to get PRs', details: String(err) });
|
|
89
89
|
}
|
|
90
90
|
});
|
|
91
|
+
// ─── GitHub Auth Flow ──────────────────────────────────────
|
|
92
|
+
router.post('/github/connect', async (req, res) => {
|
|
93
|
+
try {
|
|
94
|
+
const { method, token } = req.body;
|
|
95
|
+
if (method === 'pat' && token) {
|
|
96
|
+
const result = await githubService.connectWithToken(token);
|
|
97
|
+
res.json(result);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const result = await githubService.connectOAuth();
|
|
101
|
+
res.json(result);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
res.status(500).json({ error: 'Failed to connect', details: String(err) });
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
router.get('/github/auth-status', async (_req, res) => {
|
|
109
|
+
try {
|
|
110
|
+
const status = await githubService.getAuthStatus();
|
|
111
|
+
res.json(status);
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
res.status(500).json({ error: 'Failed to check auth', details: String(err) });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
router.post('/github/disconnect', async (_req, res) => {
|
|
118
|
+
try {
|
|
119
|
+
const result = await githubService.disconnect();
|
|
120
|
+
res.json(result);
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
res.status(500).json({ error: 'Failed to disconnect', details: String(err) });
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
// ─── GitHub CI Checks ──────────────────────────────────────
|
|
127
|
+
router.get('/github/checks/:ref', async (req, res) => {
|
|
128
|
+
try {
|
|
129
|
+
const checks = await githubService.getCheckRuns(req.params.ref);
|
|
130
|
+
res.json(checks);
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
res.status(500).json({ error: 'Failed to get checks', details: String(err) });
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
// ─── Git Health Metrics ────────────────────────────────────
|
|
137
|
+
router.get('/git/health', async (_req, res) => {
|
|
138
|
+
try {
|
|
139
|
+
// Get PR ages for health calculation
|
|
140
|
+
let prAges = [];
|
|
141
|
+
try {
|
|
142
|
+
const prs = await githubService.getOpenPRs();
|
|
143
|
+
const now = Date.now();
|
|
144
|
+
prAges = prs.map(pr => Math.round((now - new Date(pr.createdAt).getTime()) / (1000 * 60 * 60 * 24)));
|
|
145
|
+
}
|
|
146
|
+
catch { /* ok — GitHub may not be connected */ }
|
|
147
|
+
const health = await gitService.getHealthMetrics(prAges);
|
|
148
|
+
res.json(health);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
res.status(500).json({ error: 'Failed to get health metrics', details: String(err) });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
// ─── Git Activity Series ───────────────────────────────────
|
|
155
|
+
router.get('/git/activity', async (req, res) => {
|
|
156
|
+
try {
|
|
157
|
+
const days = Number(req.query.days) || 30;
|
|
158
|
+
const activity = await gitService.getActivitySeries(days);
|
|
159
|
+
res.json(activity);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
res.status(500).json({ error: 'Failed to get activity', details: String(err) });
|
|
163
|
+
}
|
|
164
|
+
});
|
|
91
165
|
return router;
|
|
92
166
|
}
|