orbital-command 0.1.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/orbital.js +676 -53
- package/dist/assets/PrimitivesConfig-CrmQXYh4.js +32 -0
- package/dist/assets/QualityGates-BbasOsF3.js +21 -0
- package/dist/assets/SessionTimeline-CGeJsVvy.js +1 -0
- package/dist/assets/Settings-oiM496mc.js +12 -0
- package/dist/assets/SourceControl-B1fP2nJL.js +41 -0
- package/dist/assets/WorkflowVisualizer-CWLYf-f0.js +74 -0
- package/dist/assets/arrow-down-CPy85_J6.js +6 -0
- package/dist/assets/charts-DbDg0Psc.js +68 -0
- package/dist/assets/circle-x-Cwz6ZQDV.js +6 -0
- package/dist/assets/file-text-C46Xr65c.js +6 -0
- package/dist/assets/formatDistanceToNow-BMqsSP44.js +1 -0
- package/dist/assets/globe-Cn2yNZUD.js +6 -0
- package/dist/assets/index-Aj4sV8Al.css +1 -0
- package/dist/assets/index-Bc9dK3MW.js +354 -0
- package/dist/assets/key-OPaNTWJ5.js +6 -0
- package/dist/assets/minus-GMsbpKym.js +6 -0
- package/dist/assets/shield-DwAFkDYI.js +6 -0
- package/dist/assets/ui-BmsSg9jU.js +53 -0
- package/dist/assets/useWorkflowEditor-BJkTX_NR.js +16 -0
- package/dist/assets/{vendor-Dzv9lrRc.js → vendor-Bqt8AJn2.js} +1 -1
- package/dist/assets/zap-DfbUoOty.js +11 -0
- package/dist/favicon.svg +1 -0
- package/dist/index.html +6 -5
- package/dist/server/server/__tests__/data-routes.test.js +124 -0
- package/dist/server/server/__tests__/helpers/db.js +17 -0
- package/dist/server/server/__tests__/helpers/mock-emitter.js +8 -0
- package/dist/server/server/__tests__/scope-routes.test.js +137 -0
- package/dist/server/server/__tests__/sprint-routes.test.js +102 -0
- package/dist/server/server/__tests__/workflow-routes.test.js +107 -0
- package/dist/server/server/config-migrator.js +138 -0
- package/dist/server/server/config.js +17 -2
- package/dist/server/server/database.js +27 -12
- package/dist/server/server/global-config.js +143 -0
- package/dist/server/server/index.js +882 -252
- package/dist/server/server/init.js +579 -194
- package/dist/server/server/launch.js +29 -0
- package/dist/server/server/manifest-types.js +8 -0
- package/dist/server/server/manifest.js +454 -0
- package/dist/server/server/migrate-legacy.js +229 -0
- package/dist/server/server/parsers/event-parser.test.js +117 -0
- package/dist/server/server/parsers/scope-parser.js +74 -28
- package/dist/server/server/parsers/scope-parser.test.js +230 -0
- package/dist/server/server/project-context.js +255 -0
- package/dist/server/server/project-emitter.js +41 -0
- package/dist/server/server/project-manager.js +297 -0
- package/dist/server/server/routes/config-routes.js +1 -3
- package/dist/server/server/routes/data-routes.js +22 -110
- package/dist/server/server/routes/dispatch-routes.js +15 -9
- package/dist/server/server/routes/git-routes.js +74 -0
- package/dist/server/server/routes/manifest-routes.js +319 -0
- package/dist/server/server/routes/scope-routes.js +37 -23
- package/dist/server/server/routes/sync-routes.js +134 -0
- package/dist/server/server/routes/version-routes.js +1 -15
- package/dist/server/server/routes/workflow-routes.js +9 -3
- package/dist/server/server/schema.js +2 -0
- package/dist/server/server/services/batch-orchestrator.js +26 -16
- package/dist/server/server/services/claude-session-service.js +17 -14
- package/dist/server/server/services/deploy-service.test.js +119 -0
- package/dist/server/server/services/event-service.js +64 -1
- package/dist/server/server/services/event-service.test.js +191 -0
- package/dist/server/server/services/gate-service.test.js +105 -0
- package/dist/server/server/services/git-service.js +108 -4
- package/dist/server/server/services/github-service.js +110 -2
- package/dist/server/server/services/readiness-service.test.js +190 -0
- package/dist/server/server/services/scope-cache.js +5 -1
- package/dist/server/server/services/scope-cache.test.js +142 -0
- package/dist/server/server/services/scope-service.js +217 -126
- package/dist/server/server/services/scope-service.test.js +137 -0
- package/dist/server/server/services/sprint-orchestrator.js +7 -6
- package/dist/server/server/services/sprint-service.js +21 -1
- package/dist/server/server/services/sprint-service.test.js +238 -0
- package/dist/server/server/services/sync-service.js +434 -0
- package/dist/server/server/services/sync-types.js +2 -0
- package/dist/server/server/services/telemetry-service.js +143 -0
- package/dist/server/server/services/workflow-service.js +26 -5
- package/dist/server/server/services/workflow-service.test.js +159 -0
- package/dist/server/server/settings-sync.js +284 -0
- package/dist/server/server/update-planner.js +279 -0
- package/dist/server/server/utils/cc-hooks-parser.js +3 -0
- package/dist/server/server/utils/cc-hooks-parser.test.js +86 -0
- package/dist/server/server/utils/dispatch-utils.js +77 -20
- package/dist/server/server/utils/dispatch-utils.test.js +182 -0
- package/dist/server/server/utils/logger.js +37 -3
- package/dist/server/server/utils/package-info.js +30 -0
- package/dist/server/server/utils/route-helpers.js +10 -0
- package/dist/server/server/utils/terminal-launcher.js +79 -25
- package/dist/server/server/utils/worktree-manager.js +13 -4
- package/dist/server/server/validator.js +230 -0
- package/dist/server/server/watchers/global-watcher.js +63 -0
- package/dist/server/server/watchers/scope-watcher.js +27 -12
- package/dist/server/server/wizard/config-editor.js +237 -0
- package/dist/server/server/wizard/detect.js +96 -0
- package/dist/server/server/wizard/doctor.js +115 -0
- package/dist/server/server/wizard/index.js +155 -0
- package/dist/server/server/wizard/phases/confirm.js +39 -0
- package/dist/server/server/wizard/phases/project-setup.js +90 -0
- package/dist/server/server/wizard/phases/setup-wizard.js +66 -0
- package/dist/server/server/wizard/phases/welcome.js +35 -0
- package/dist/server/server/wizard/phases/workflow-setup.js +22 -0
- package/dist/server/server/wizard/types.js +29 -0
- package/dist/server/server/wizard/ui.js +74 -0
- package/dist/server/shared/__fixtures__/workflow-configs.js +75 -0
- package/dist/server/shared/default-workflow.json +65 -0
- package/dist/server/shared/onboarding-tour.test.js +81 -0
- package/dist/server/shared/project-colors.js +24 -0
- package/dist/server/shared/workflow-config.test.js +84 -0
- package/dist/server/shared/workflow-engine.test.js +302 -0
- package/dist/server/shared/workflow-normalizer.js +101 -0
- package/dist/server/shared/workflow-normalizer.test.js +100 -0
- package/dist/server/src/components/onboarding/tour-steps.js +84 -0
- package/package.json +20 -15
- package/schemas/orbital.config.schema.json +16 -1
- package/scripts/postinstall.js +55 -7
- package/server/__tests__/data-routes.test.ts +149 -0
- package/server/__tests__/helpers/db.ts +19 -0
- package/server/__tests__/helpers/mock-emitter.ts +10 -0
- package/server/__tests__/scope-routes.test.ts +157 -0
- package/server/__tests__/sprint-routes.test.ts +118 -0
- package/server/__tests__/workflow-routes.test.ts +120 -0
- package/server/config-migrator.ts +163 -0
- package/server/config.ts +26 -2
- package/server/database.ts +35 -18
- package/server/global-config.ts +200 -0
- package/server/index.ts +975 -287
- package/server/init.ts +625 -182
- package/server/launch.ts +32 -0
- package/server/manifest-types.ts +145 -0
- package/server/manifest.ts +494 -0
- package/server/migrate-legacy.ts +290 -0
- package/server/parsers/event-parser.test.ts +135 -0
- package/server/parsers/scope-parser.test.ts +270 -0
- package/server/parsers/scope-parser.ts +79 -31
- package/server/project-context.ts +309 -0
- package/server/project-emitter.ts +50 -0
- package/server/project-manager.ts +369 -0
- package/server/routes/config-routes.ts +3 -5
- package/server/routes/data-routes.ts +28 -141
- package/server/routes/dispatch-routes.ts +19 -11
- package/server/routes/git-routes.ts +77 -0
- package/server/routes/manifest-routes.ts +388 -0
- package/server/routes/scope-routes.ts +29 -25
- package/server/routes/sync-routes.ts +175 -0
- package/server/routes/version-routes.ts +1 -16
- package/server/routes/workflow-routes.ts +9 -3
- package/server/schema.ts +2 -0
- package/server/services/batch-orchestrator.ts +24 -16
- package/server/services/claude-session-service.ts +16 -14
- package/server/services/deploy-service.test.ts +145 -0
- package/server/services/deploy-service.ts +2 -2
- package/server/services/event-service.test.ts +242 -0
- package/server/services/event-service.ts +92 -3
- package/server/services/gate-service.test.ts +131 -0
- package/server/services/gate-service.ts +2 -2
- package/server/services/git-service.ts +137 -4
- package/server/services/github-service.ts +120 -2
- package/server/services/readiness-service.test.ts +217 -0
- package/server/services/scope-cache.test.ts +167 -0
- package/server/services/scope-cache.ts +4 -1
- package/server/services/scope-service.test.ts +169 -0
- package/server/services/scope-service.ts +220 -126
- package/server/services/sprint-orchestrator.ts +7 -7
- package/server/services/sprint-service.test.ts +271 -0
- package/server/services/sprint-service.ts +27 -3
- package/server/services/sync-service.ts +482 -0
- package/server/services/sync-types.ts +77 -0
- package/server/services/telemetry-service.ts +195 -0
- package/server/services/workflow-service.test.ts +190 -0
- package/server/services/workflow-service.ts +29 -9
- package/server/settings-sync.ts +359 -0
- package/server/update-planner.ts +346 -0
- package/server/utils/cc-hooks-parser.test.ts +96 -0
- package/server/utils/cc-hooks-parser.ts +4 -0
- package/server/utils/dispatch-utils.test.ts +245 -0
- package/server/utils/dispatch-utils.ts +97 -27
- package/server/utils/logger.ts +40 -3
- package/server/utils/package-info.ts +32 -0
- package/server/utils/route-helpers.ts +12 -0
- package/server/utils/terminal-launcher.ts +85 -25
- package/server/utils/worktree-manager.ts +9 -4
- package/server/validator.ts +270 -0
- package/server/watchers/global-watcher.ts +77 -0
- package/server/watchers/scope-watcher.ts +21 -9
- package/server/wizard/config-editor.ts +248 -0
- package/server/wizard/detect.ts +104 -0
- package/server/wizard/doctor.ts +114 -0
- package/server/wizard/index.ts +187 -0
- package/server/wizard/phases/confirm.ts +45 -0
- package/server/wizard/phases/project-setup.ts +106 -0
- package/server/wizard/phases/setup-wizard.ts +78 -0
- package/server/wizard/phases/welcome.ts +43 -0
- package/server/wizard/phases/workflow-setup.ts +28 -0
- package/server/wizard/types.ts +56 -0
- package/server/wizard/ui.ts +93 -0
- package/shared/__fixtures__/workflow-configs.ts +80 -0
- package/shared/default-workflow.json +65 -0
- package/shared/onboarding-tour.test.ts +94 -0
- package/shared/project-colors.ts +24 -0
- package/shared/workflow-config.test.ts +111 -0
- package/shared/workflow-config.ts +7 -0
- package/shared/workflow-engine.test.ts +388 -0
- package/shared/workflow-normalizer.test.ts +119 -0
- package/shared/workflow-normalizer.ts +118 -0
- package/templates/hooks/end-session.sh +3 -1
- package/templates/hooks/orbital-emit.sh +2 -2
- package/templates/hooks/orbital-report-deploy.sh +4 -4
- package/templates/hooks/orbital-report-gates.sh +4 -4
- package/templates/hooks/orbital-scope-update.sh +1 -1
- package/templates/hooks/scope-create-cleanup.sh +2 -2
- package/templates/hooks/scope-create-gate.sh +0 -1
- package/templates/hooks/scope-helpers.sh +18 -0
- package/templates/hooks/scope-prepare.sh +66 -11
- package/templates/migrations/renames.json +1 -0
- package/templates/orbital.config.json +7 -2
- package/templates/settings-hooks.json +1 -1
- package/templates/skills/git-commit/SKILL.md +9 -4
- package/templates/skills/git-dev/SKILL.md +8 -3
- package/templates/skills/git-main/SKILL.md +8 -2
- package/templates/skills/git-production/SKILL.md +6 -2
- package/templates/skills/git-staging/SKILL.md +8 -3
- package/templates/skills/scope-create/SKILL.md +17 -3
- package/templates/skills/scope-fix-review/SKILL.md +6 -3
- package/templates/skills/scope-implement/SKILL.md +4 -1
- package/templates/skills/scope-post-review/SKILL.md +63 -5
- package/templates/skills/scope-pre-review/SKILL.md +5 -2
- package/templates/skills/scope-verify/SKILL.md +5 -3
- package/templates/skills/test-code-review/SKILL.md +41 -33
- package/templates/skills/test-scaffold/SKILL.md +222 -0
- package/dist/assets/WorkflowVisualizer-BZ21PIIF.js +0 -84
- package/dist/assets/charts-D__PA1zp.js +0 -72
- package/dist/assets/index-D1G6i0nS.css +0 -1
- package/dist/assets/index-DpItvKpf.js +0 -419
- package/dist/assets/ui-BvF022GT.js +0 -53
- package/index.html +0 -15
- package/postcss.config.js +0 -6
- package/src/App.tsx +0 -33
- package/src/components/AgentBadge.tsx +0 -40
- package/src/components/BatchPreflightModal.tsx +0 -115
- package/src/components/CardDisplayToggle.tsx +0 -74
- package/src/components/ColumnHeaderActions.tsx +0 -55
- package/src/components/ColumnMenu.tsx +0 -99
- package/src/components/DeployHistory.tsx +0 -141
- package/src/components/DispatchModal.tsx +0 -164
- package/src/components/DispatchPopover.tsx +0 -139
- package/src/components/DragOverlay.tsx +0 -25
- package/src/components/DriftSidebar.tsx +0 -140
- package/src/components/EnvironmentStrip.tsx +0 -88
- package/src/components/ErrorBoundary.tsx +0 -62
- package/src/components/FilterChip.tsx +0 -105
- package/src/components/GateIndicator.tsx +0 -33
- package/src/components/IdeaDetailModal.tsx +0 -190
- package/src/components/IdeaFormDialog.tsx +0 -113
- package/src/components/KanbanColumn.tsx +0 -201
- package/src/components/MarkdownRenderer.tsx +0 -114
- package/src/components/NeonGrid.tsx +0 -128
- package/src/components/PromotionQueue.tsx +0 -89
- package/src/components/ScopeCard.tsx +0 -234
- package/src/components/ScopeDetailModal.tsx +0 -255
- package/src/components/ScopeFilterBar.tsx +0 -152
- package/src/components/SearchInput.tsx +0 -102
- package/src/components/SessionPanel.tsx +0 -335
- package/src/components/SprintContainer.tsx +0 -303
- package/src/components/SprintDependencyDialog.tsx +0 -78
- package/src/components/SprintPreflightModal.tsx +0 -138
- package/src/components/StatusBar.tsx +0 -168
- package/src/components/SwimCell.tsx +0 -67
- package/src/components/SwimLaneRow.tsx +0 -94
- package/src/components/SwimlaneBoardView.tsx +0 -108
- package/src/components/VersionBadge.tsx +0 -139
- package/src/components/ViewModeSelector.tsx +0 -114
- package/src/components/config/AgentChip.tsx +0 -53
- package/src/components/config/AgentCreateDialog.tsx +0 -321
- package/src/components/config/AgentEditor.tsx +0 -175
- package/src/components/config/DirectoryTree.tsx +0 -582
- package/src/components/config/FileEditor.tsx +0 -550
- package/src/components/config/HookChip.tsx +0 -50
- package/src/components/config/StageCard.tsx +0 -198
- package/src/components/config/TransitionZone.tsx +0 -173
- package/src/components/config/UnifiedWorkflowPipeline.tsx +0 -216
- package/src/components/config/WorkflowPipeline.tsx +0 -161
- package/src/components/source-control/BranchList.tsx +0 -93
- package/src/components/source-control/BranchPanel.tsx +0 -105
- package/src/components/source-control/CommitLog.tsx +0 -100
- package/src/components/source-control/CommitRow.tsx +0 -47
- package/src/components/source-control/GitHubPanel.tsx +0 -110
- package/src/components/source-control/GitHubSetupGuide.tsx +0 -52
- package/src/components/source-control/GitOverviewBar.tsx +0 -101
- package/src/components/source-control/PullRequestList.tsx +0 -69
- package/src/components/source-control/WorktreeList.tsx +0 -80
- package/src/components/ui/badge.tsx +0 -41
- package/src/components/ui/button.tsx +0 -55
- package/src/components/ui/card.tsx +0 -78
- package/src/components/ui/dialog.tsx +0 -94
- package/src/components/ui/popover.tsx +0 -33
- package/src/components/ui/scroll-area.tsx +0 -54
- package/src/components/ui/separator.tsx +0 -28
- package/src/components/ui/tabs.tsx +0 -52
- package/src/components/ui/toggle-switch.tsx +0 -35
- package/src/components/ui/tooltip.tsx +0 -27
- package/src/components/workflow/AddEdgeDialog.tsx +0 -217
- package/src/components/workflow/AddListDialog.tsx +0 -201
- package/src/components/workflow/ChecklistEditor.tsx +0 -239
- package/src/components/workflow/CommandPrefixManager.tsx +0 -118
- package/src/components/workflow/ConfigSettingsPanel.tsx +0 -189
- package/src/components/workflow/DirectionSelector.tsx +0 -133
- package/src/components/workflow/DispatchConfigPanel.tsx +0 -180
- package/src/components/workflow/EdgeDetailPanel.tsx +0 -236
- package/src/components/workflow/EdgePropertyEditor.tsx +0 -251
- package/src/components/workflow/EditToolbar.tsx +0 -138
- package/src/components/workflow/HookDetailPanel.tsx +0 -250
- package/src/components/workflow/HookExecutionLog.tsx +0 -24
- package/src/components/workflow/HookSourceModal.tsx +0 -129
- package/src/components/workflow/HooksDashboard.tsx +0 -363
- package/src/components/workflow/ListPropertyEditor.tsx +0 -251
- package/src/components/workflow/MigrationPreviewDialog.tsx +0 -237
- package/src/components/workflow/MovementRulesPanel.tsx +0 -188
- package/src/components/workflow/NodeDetailPanel.tsx +0 -245
- package/src/components/workflow/PresetSelector.tsx +0 -414
- package/src/components/workflow/SkillCommandBuilder.tsx +0 -174
- package/src/components/workflow/WorkflowEdgeComponent.tsx +0 -145
- package/src/components/workflow/WorkflowNode.tsx +0 -147
- package/src/components/workflow/graphLayout.ts +0 -186
- package/src/components/workflow/mergeHooks.ts +0 -85
- package/src/components/workflow/useEditHistory.ts +0 -88
- package/src/components/workflow/useWorkflowEditor.ts +0 -262
- package/src/components/workflow/validateConfig.ts +0 -70
- package/src/hooks/useActiveDispatches.ts +0 -198
- package/src/hooks/useBoardSettings.ts +0 -170
- package/src/hooks/useCardDisplay.ts +0 -57
- package/src/hooks/useCcHooks.ts +0 -24
- package/src/hooks/useConfigTree.ts +0 -51
- package/src/hooks/useEnforcementRules.ts +0 -46
- package/src/hooks/useEvents.ts +0 -59
- package/src/hooks/useFileEditor.ts +0 -165
- package/src/hooks/useGates.ts +0 -57
- package/src/hooks/useIdeaActions.ts +0 -53
- package/src/hooks/useKanbanDnd.ts +0 -410
- package/src/hooks/useOrbitalConfig.ts +0 -54
- package/src/hooks/usePipeline.ts +0 -47
- package/src/hooks/usePipelineData.ts +0 -338
- package/src/hooks/useReconnect.ts +0 -25
- package/src/hooks/useScopeFilters.ts +0 -125
- package/src/hooks/useScopeSessions.ts +0 -44
- package/src/hooks/useScopes.ts +0 -67
- package/src/hooks/useSearch.ts +0 -67
- package/src/hooks/useSettings.tsx +0 -187
- package/src/hooks/useSocket.ts +0 -25
- package/src/hooks/useSourceControl.ts +0 -105
- package/src/hooks/useSprintPreflight.ts +0 -55
- package/src/hooks/useSprints.ts +0 -154
- package/src/hooks/useStatusBarHighlight.ts +0 -18
- package/src/hooks/useSwimlaneBoardSettings.ts +0 -104
- package/src/hooks/useTheme.ts +0 -9
- package/src/hooks/useTransitionReadiness.ts +0 -53
- package/src/hooks/useVersion.ts +0 -155
- package/src/hooks/useViolations.ts +0 -65
- package/src/hooks/useWorkflow.tsx +0 -125
- package/src/hooks/useZoomModifier.ts +0 -19
- package/src/index.css +0 -797
- package/src/layouts/DashboardLayout.tsx +0 -113
- package/src/lib/collisionDetection.ts +0 -20
- package/src/lib/scope-fields.ts +0 -61
- package/src/lib/swimlane.ts +0 -146
- package/src/lib/utils.ts +0 -15
- package/src/main.tsx +0 -19
- package/src/socket.ts +0 -11
- package/src/types/index.ts +0 -497
- package/src/views/AgentFeed.tsx +0 -339
- package/src/views/DeployPipeline.tsx +0 -59
- package/src/views/EnforcementView.tsx +0 -378
- package/src/views/PrimitivesConfig.tsx +0 -500
- package/src/views/QualityGates.tsx +0 -1012
- package/src/views/ScopeBoard.tsx +0 -454
- package/src/views/SessionTimeline.tsx +0 -516
- package/src/views/Settings.tsx +0 -183
- package/src/views/SourceControl.tsx +0 -95
- package/src/views/WorkflowVisualizer.tsx +0 -382
- package/tailwind.config.js +0 -161
- package/tsconfig.json +0 -25
- package/vite.config.ts +0 -49
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { Router } from 'express';
|
|
3
|
+
import { ProjectEmitter } from './project-emitter.js';
|
|
4
|
+
import { createProjectContext } from './project-context.js';
|
|
5
|
+
import { loadGlobalConfig, registerProject, unregisterProject, updateProject, findProject, } from './global-config.js';
|
|
6
|
+
import { createScopeRoutes } from './routes/scope-routes.js';
|
|
7
|
+
import { createDataRoutes } from './routes/data-routes.js';
|
|
8
|
+
import { createDispatchRoutes } from './routes/dispatch-routes.js';
|
|
9
|
+
import { createSprintRoutes } from './routes/sprint-routes.js';
|
|
10
|
+
import { createWorkflowRoutes } from './routes/workflow-routes.js';
|
|
11
|
+
import { createConfigRoutes } from './routes/config-routes.js';
|
|
12
|
+
import { createGitRoutes } from './routes/git-routes.js';
|
|
13
|
+
import { createManifestRoutes } from './routes/manifest-routes.js';
|
|
14
|
+
import { createTelemetryRoutes } from './services/telemetry-service.js';
|
|
15
|
+
import { TEMPLATES_DIR } from './init.js';
|
|
16
|
+
import { getPackageVersion } from './utils/package-info.js';
|
|
17
|
+
import { resolveActiveDispatchesForScope } from './utils/dispatch-utils.js';
|
|
18
|
+
import { createLogger } from './utils/logger.js';
|
|
19
|
+
const log = createLogger('project-manager');
|
|
20
|
+
// ─── Manager ────────────────────────────────────────────────
|
|
21
|
+
export class ProjectManager {
|
|
22
|
+
io;
|
|
23
|
+
contexts = new Map();
|
|
24
|
+
routers = new Map();
|
|
25
|
+
healthCheckInterval = null;
|
|
26
|
+
constructor(io) {
|
|
27
|
+
this.io = io;
|
|
28
|
+
}
|
|
29
|
+
// ─── Initialization ─────────────────────────────────────
|
|
30
|
+
/** Initialize all enabled projects from the registry. */
|
|
31
|
+
async initializeAll() {
|
|
32
|
+
const config = loadGlobalConfig();
|
|
33
|
+
const enabledProjects = config.projects.filter(p => p.enabled);
|
|
34
|
+
log.info('Initializing projects', { count: enabledProjects.length });
|
|
35
|
+
for (const reg of enabledProjects) {
|
|
36
|
+
try {
|
|
37
|
+
await this.initializeProject(reg);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
log.error('Failed to initialize project', {
|
|
41
|
+
id: reg.id,
|
|
42
|
+
path: reg.path,
|
|
43
|
+
error: String(err),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Start periodic health checks
|
|
48
|
+
this.healthCheckInterval = setInterval(() => this.checkHealth(), 60_000);
|
|
49
|
+
}
|
|
50
|
+
/** Initialize a single project from its registration. */
|
|
51
|
+
async initializeProject(reg) {
|
|
52
|
+
// Verify directory exists
|
|
53
|
+
if (!fs.existsSync(reg.path)) {
|
|
54
|
+
log.warn('Project directory not found, marking offline', { id: reg.id, path: reg.path });
|
|
55
|
+
// Store a placeholder context to track status
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const emitter = new ProjectEmitter(this.io, reg.id);
|
|
59
|
+
try {
|
|
60
|
+
const ctx = await createProjectContext(reg.id, reg.path, emitter);
|
|
61
|
+
this.contexts.set(reg.id, ctx);
|
|
62
|
+
// Build and cache the project's router
|
|
63
|
+
const router = this.buildProjectRouter(ctx);
|
|
64
|
+
this.routers.set(reg.id, router);
|
|
65
|
+
log.info('Project initialized', {
|
|
66
|
+
id: reg.id,
|
|
67
|
+
name: reg.name,
|
|
68
|
+
scopeCount: ctx.scopeService.getAll().length,
|
|
69
|
+
});
|
|
70
|
+
return ctx;
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
log.error('Project initialization failed', { id: reg.id, error: String(err) });
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// ─── Context Access ─────────────────────────────────────
|
|
78
|
+
/** Get a project context by ID. */
|
|
79
|
+
getContext(id) {
|
|
80
|
+
return this.contexts.get(id);
|
|
81
|
+
}
|
|
82
|
+
/** Get all active contexts. */
|
|
83
|
+
getAllContexts() {
|
|
84
|
+
return this.contexts;
|
|
85
|
+
}
|
|
86
|
+
/** Get the router for a project. */
|
|
87
|
+
getRouter(id) {
|
|
88
|
+
return this.routers.get(id);
|
|
89
|
+
}
|
|
90
|
+
/** Get all project routers. */
|
|
91
|
+
getAllRouters() {
|
|
92
|
+
return this.routers;
|
|
93
|
+
}
|
|
94
|
+
// ─── Project List ───────────────────────────────────────
|
|
95
|
+
/** Get summary of all registered projects with live status. */
|
|
96
|
+
getProjectList(options) {
|
|
97
|
+
const config = loadGlobalConfig();
|
|
98
|
+
return config.projects.map(reg => {
|
|
99
|
+
const ctx = this.contexts.get(reg.id);
|
|
100
|
+
const summary = {
|
|
101
|
+
id: reg.id,
|
|
102
|
+
name: reg.name,
|
|
103
|
+
path: reg.path,
|
|
104
|
+
color: reg.color,
|
|
105
|
+
status: ctx?.status ?? 'offline',
|
|
106
|
+
enabled: reg.enabled,
|
|
107
|
+
scopeCount: ctx ? ctx.scopeService.getAll().length : 0,
|
|
108
|
+
error: ctx?.error,
|
|
109
|
+
};
|
|
110
|
+
if (options?.includeWorkflow && ctx) {
|
|
111
|
+
summary.workflow = ctx.workflowService.getActive();
|
|
112
|
+
}
|
|
113
|
+
return summary;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// ─── Registration ───────────────────────────────────────
|
|
117
|
+
/** Register and initialize a new project. */
|
|
118
|
+
async addProject(projectRoot, options) {
|
|
119
|
+
const reg = registerProject(projectRoot, options);
|
|
120
|
+
const ctx = await this.initializeProject(reg);
|
|
121
|
+
// Notify all clients
|
|
122
|
+
this.io.emit('project:registered', {
|
|
123
|
+
id: reg.id,
|
|
124
|
+
name: reg.name,
|
|
125
|
+
path: reg.path,
|
|
126
|
+
color: reg.color,
|
|
127
|
+
});
|
|
128
|
+
return {
|
|
129
|
+
id: reg.id,
|
|
130
|
+
name: reg.name,
|
|
131
|
+
path: reg.path,
|
|
132
|
+
color: reg.color,
|
|
133
|
+
status: ctx?.status ?? 'offline',
|
|
134
|
+
enabled: reg.enabled,
|
|
135
|
+
scopeCount: ctx ? ctx.scopeService.getAll().length : 0,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/** Unregister a project and shut down its context. */
|
|
139
|
+
async removeProject(idOrPath) {
|
|
140
|
+
// Find the registration before removing
|
|
141
|
+
const config = loadGlobalConfig();
|
|
142
|
+
const reg = config.projects.find(p => p.id === idOrPath || p.path === idOrPath);
|
|
143
|
+
if (!reg)
|
|
144
|
+
return false;
|
|
145
|
+
// Shut down context
|
|
146
|
+
await this.shutdownProject(reg.id);
|
|
147
|
+
// Remove from registry
|
|
148
|
+
unregisterProject(idOrPath);
|
|
149
|
+
// Notify clients
|
|
150
|
+
this.io.emit('project:unregistered', { id: reg.id });
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
/** Update project metadata (name, color, enabled). */
|
|
154
|
+
async updateProject(id, updates) {
|
|
155
|
+
const result = updateProject(id, updates);
|
|
156
|
+
if (!result)
|
|
157
|
+
return null;
|
|
158
|
+
// If disabling, shut down the context
|
|
159
|
+
if (updates.enabled === false) {
|
|
160
|
+
await this.shutdownProject(id);
|
|
161
|
+
}
|
|
162
|
+
// If enabling, initialize the context
|
|
163
|
+
if (updates.enabled === true && !this.contexts.has(id)) {
|
|
164
|
+
const reg = findProject(id);
|
|
165
|
+
if (reg)
|
|
166
|
+
await this.initializeProject(reg);
|
|
167
|
+
}
|
|
168
|
+
// Notify clients of metadata change
|
|
169
|
+
this.io.emit('project:updated', { id, ...updates });
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
// ─── Lifecycle ──────────────────────────────────────────
|
|
173
|
+
/** Shut down a single project's context. */
|
|
174
|
+
async shutdownProject(id) {
|
|
175
|
+
const ctx = this.contexts.get(id);
|
|
176
|
+
if (ctx) {
|
|
177
|
+
await ctx.shutdown();
|
|
178
|
+
this.contexts.delete(id);
|
|
179
|
+
this.routers.delete(id);
|
|
180
|
+
log.info('Project shut down', { id });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/** Shut down all projects and stop health checks. */
|
|
184
|
+
async shutdownAll() {
|
|
185
|
+
if (this.healthCheckInterval) {
|
|
186
|
+
clearInterval(this.healthCheckInterval);
|
|
187
|
+
this.healthCheckInterval = null;
|
|
188
|
+
}
|
|
189
|
+
const shutdowns = [...this.contexts.entries()].map(([id, ctx]) => ctx.shutdown().catch(err => log.error('Error shutting down project', { id, error: String(err) })));
|
|
190
|
+
await Promise.all(shutdowns);
|
|
191
|
+
this.contexts.clear();
|
|
192
|
+
this.routers.clear();
|
|
193
|
+
log.info('All projects shut down');
|
|
194
|
+
}
|
|
195
|
+
// ─── Health Checks ──────────────────────────────────────
|
|
196
|
+
/** Periodic health check — detect projects that have gone offline or come back. */
|
|
197
|
+
async checkHealth() {
|
|
198
|
+
const config = loadGlobalConfig();
|
|
199
|
+
for (const reg of config.projects) {
|
|
200
|
+
if (!reg.enabled)
|
|
201
|
+
continue;
|
|
202
|
+
const ctx = this.contexts.get(reg.id);
|
|
203
|
+
const dirExists = fs.existsSync(reg.path);
|
|
204
|
+
if (ctx && !dirExists) {
|
|
205
|
+
// Project went offline
|
|
206
|
+
log.warn('Project directory disappeared', { id: reg.id, path: reg.path });
|
|
207
|
+
await this.shutdownProject(reg.id);
|
|
208
|
+
this.io.emit('project:status:changed', { id: reg.id, status: 'offline' });
|
|
209
|
+
}
|
|
210
|
+
else if (!ctx && dirExists) {
|
|
211
|
+
// Project came back online (or failed to initialize previously — retry)
|
|
212
|
+
log.info('Attempting to initialize project', { id: reg.id, path: reg.path });
|
|
213
|
+
try {
|
|
214
|
+
await this.initializeProject(reg);
|
|
215
|
+
this.io.emit('project:status:changed', { id: reg.id, status: 'active' });
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
log.warn('Project initialization retry failed', { id: reg.id, error: String(err) });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// ─── Route Building ─────────────────────────────────────
|
|
224
|
+
/** Build an Express Router with all per-project routes for a context. */
|
|
225
|
+
/** Build an Express Router with all per-project routes.
|
|
226
|
+
* Note: createVersionRoutes is intentionally omitted — version/update endpoints
|
|
227
|
+
* are global (they update the Orbital Command package), not per-project. */
|
|
228
|
+
buildProjectRouter(ctx) {
|
|
229
|
+
const router = Router();
|
|
230
|
+
const { db, emitter, config, scopeService, eventService, gateService, deployService, sprintService, sprintOrchestrator, batchOrchestrator, readinessService, workflowService, workflowEngine, gitService, githubService, telemetryService } = ctx;
|
|
231
|
+
// Scope status inference function (same logic as index.ts)
|
|
232
|
+
function inferScopeStatus(eventType, scopeId, data) {
|
|
233
|
+
if (scopeId == null)
|
|
234
|
+
return;
|
|
235
|
+
const id = Number(scopeId);
|
|
236
|
+
if (isNaN(id) || id <= 0)
|
|
237
|
+
return;
|
|
238
|
+
const current = scopeService.getById(id);
|
|
239
|
+
if (current?.status === 'icebox')
|
|
240
|
+
return;
|
|
241
|
+
const currentStatus = current?.status ?? '';
|
|
242
|
+
const result = workflowEngine.inferStatus(eventType, currentStatus, data);
|
|
243
|
+
if (result === null)
|
|
244
|
+
return;
|
|
245
|
+
if (typeof result === 'object' && 'dispatchResolution' in result) {
|
|
246
|
+
resolveActiveDispatchesForScope(db, emitter, id, result.resolution);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
scopeService.updateStatus(id, result, 'event');
|
|
250
|
+
}
|
|
251
|
+
// Project config endpoint
|
|
252
|
+
router.get('/config', (_req, res) => {
|
|
253
|
+
res.json({
|
|
254
|
+
projectName: config.projectName,
|
|
255
|
+
categories: config.categories,
|
|
256
|
+
agents: config.agents,
|
|
257
|
+
serverPort: config.serverPort,
|
|
258
|
+
clientPort: config.clientPort,
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
// Mount all route groups
|
|
262
|
+
router.use(createScopeRoutes({
|
|
263
|
+
db, io: emitter, scopeService, readinessService,
|
|
264
|
+
projectRoot: config.projectRoot, projectName: config.projectName,
|
|
265
|
+
engine: workflowEngine,
|
|
266
|
+
}));
|
|
267
|
+
router.use(createDataRoutes({
|
|
268
|
+
db, io: emitter, eventService, gateService, deployService, gitService,
|
|
269
|
+
engine: workflowEngine, projectRoot: config.projectRoot,
|
|
270
|
+
inferScopeStatus,
|
|
271
|
+
}));
|
|
272
|
+
router.use(createDispatchRoutes({
|
|
273
|
+
db, io: emitter, scopeService,
|
|
274
|
+
projectRoot: config.projectRoot, engine: workflowEngine,
|
|
275
|
+
}));
|
|
276
|
+
router.use(createSprintRoutes({
|
|
277
|
+
sprintService, sprintOrchestrator, batchOrchestrator,
|
|
278
|
+
}));
|
|
279
|
+
router.use(createWorkflowRoutes({
|
|
280
|
+
workflowService, projectRoot: config.projectRoot,
|
|
281
|
+
}));
|
|
282
|
+
router.use(createConfigRoutes({
|
|
283
|
+
projectRoot: config.projectRoot, workflowService, io: emitter,
|
|
284
|
+
}));
|
|
285
|
+
router.use(createGitRoutes({
|
|
286
|
+
gitService, githubService, engine: workflowEngine,
|
|
287
|
+
}));
|
|
288
|
+
router.use(createManifestRoutes({
|
|
289
|
+
projectRoot: config.projectRoot,
|
|
290
|
+
templatesDir: TEMPLATES_DIR,
|
|
291
|
+
packageVersion: getPackageVersion(),
|
|
292
|
+
io: emitter,
|
|
293
|
+
}));
|
|
294
|
+
router.use(createTelemetryRoutes({ telemetryService }));
|
|
295
|
+
return router;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import { ConfigService, isValidPrimitiveType } from '../services/config-service.js';
|
|
3
|
+
import { errMsg } 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);
|
|
@@ -158,6 +159,3 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
|
|
|
158
159
|
});
|
|
159
160
|
return router;
|
|
160
161
|
}
|
|
161
|
-
function errMsg(err) {
|
|
162
|
-
return err instanceof Error ? err.message : String(err);
|
|
163
|
-
}
|
|
@@ -5,6 +5,8 @@ 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 { createLogger } from '../utils/logger.js';
|
|
9
|
+
const log = createLogger('server');
|
|
8
10
|
const execFileAsync = promisify(execFile);
|
|
9
11
|
const JSON_FIELDS = ['tags', 'blocked_by', 'blocks', 'data', 'discoveries', 'next_steps', 'details'];
|
|
10
12
|
function parseJsonFields(row) {
|
|
@@ -19,77 +21,14 @@ function parseJsonFields(row) {
|
|
|
19
21
|
}
|
|
20
22
|
return parsed;
|
|
21
23
|
}
|
|
22
|
-
function
|
|
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, }) {
|
|
24
|
+
export function createDataRoutes({ db, io, eventService, gateService, deployService, gitService, engine, projectRoot, inferScopeStatus, }) {
|
|
35
25
|
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
26
|
// ─── Event Routes ──────────────────────────────────────────
|
|
76
27
|
router.get('/events', (req, res) => {
|
|
77
28
|
const limit = Number(req.query.limit) || 50;
|
|
78
29
|
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);
|
|
30
|
+
const scopeId = req.query.scope_id ? Number(req.query.scope_id) : undefined;
|
|
31
|
+
const events = eventService.getFiltered({ limit, type, scopeId });
|
|
93
32
|
res.json(events.map(parseJsonFields));
|
|
94
33
|
});
|
|
95
34
|
router.post('/events', (req, res) => {
|
|
@@ -115,18 +54,10 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
|
|
|
115
54
|
// ─── Violations Summary ──────────────────────────────────
|
|
116
55
|
router.get('/events/violations/summary', (_req, res) => {
|
|
117
56
|
try {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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 });
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
57
|
+
res.json(eventService.getViolationSummary());
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
log.error('Violations summary failed', { error: String(err) });
|
|
130
61
|
res.status(500).json({ error: 'Failed to query violations summary' });
|
|
131
62
|
}
|
|
132
63
|
});
|
|
@@ -144,13 +75,7 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
|
|
|
144
75
|
hookEdgeMap.get(hookId).push({ from: edge.from, to: edge.to, label: edge.label });
|
|
145
76
|
}
|
|
146
77
|
}
|
|
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]));
|
|
78
|
+
const { violations: violationMap, overrides: overrideMap } = eventService.getViolationStatsByRule();
|
|
154
79
|
// Build summary counts
|
|
155
80
|
const summary = { guards: 0, gates: 0, lifecycle: 0, observers: 0 };
|
|
156
81
|
for (const hook of allHooks) {
|
|
@@ -175,20 +100,19 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
|
|
|
175
100
|
}));
|
|
176
101
|
res.json({ summary, rules, totalEdges: allEdges.length });
|
|
177
102
|
}
|
|
178
|
-
catch {
|
|
103
|
+
catch (err) {
|
|
104
|
+
log.error('Enforcement rules failed', { error: String(err) });
|
|
179
105
|
res.status(500).json({ error: 'Failed to query enforcement rules' });
|
|
180
106
|
}
|
|
181
107
|
});
|
|
182
108
|
// ─── Violation Trends ──────────────────────────────────────
|
|
183
109
|
router.get('/events/violations/trend', (req, res) => {
|
|
184
110
|
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);
|
|
111
|
+
const days = Math.max(1, Math.min(365, Number(req.query.days) || 30));
|
|
112
|
+
res.json(eventService.getViolationTrend(days));
|
|
190
113
|
}
|
|
191
|
-
catch {
|
|
114
|
+
catch (err) {
|
|
115
|
+
log.error('Violation trends failed', { error: String(err) });
|
|
192
116
|
res.status(500).json({ error: 'Failed to query violation trends' });
|
|
193
117
|
}
|
|
194
118
|
});
|
|
@@ -251,8 +175,7 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
|
|
|
251
175
|
});
|
|
252
176
|
router.get('/pipeline/drift', async (_req, res) => {
|
|
253
177
|
try {
|
|
254
|
-
|
|
255
|
-
res.json(drift);
|
|
178
|
+
res.json(await gitService.getPipelineDrift());
|
|
256
179
|
}
|
|
257
180
|
catch (err) {
|
|
258
181
|
res.status(500).json({ error: 'Failed to compute drift', details: String(err) });
|
|
@@ -260,21 +183,10 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
|
|
|
260
183
|
});
|
|
261
184
|
router.get('/deployments/frequency', (_req, res) => {
|
|
262
185
|
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()]);
|
|
186
|
+
res.json(eventService.getDeployFrequency());
|
|
276
187
|
}
|
|
277
|
-
catch {
|
|
188
|
+
catch (err) {
|
|
189
|
+
log.error('Deploy frequency query failed', { error: String(err) });
|
|
278
190
|
res.status(500).json({ error: 'Failed to query deployment frequency' });
|
|
279
191
|
}
|
|
280
192
|
});
|
|
@@ -330,7 +242,7 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
|
|
|
330
242
|
let meta = null;
|
|
331
243
|
let stats = null;
|
|
332
244
|
if (parsed.claude_session_id && typeof parsed.claude_session_id === 'string') {
|
|
333
|
-
const claudeSessions = await getClaudeSessions();
|
|
245
|
+
const claudeSessions = await getClaudeSessions(undefined, projectRoot);
|
|
334
246
|
const match = claudeSessions.find(s => s.id === parsed.claude_session_id);
|
|
335
247
|
if (match) {
|
|
336
248
|
meta = {
|
|
@@ -342,7 +254,7 @@ export function createDataRoutes({ db, io, gateService, deployService, engine, p
|
|
|
342
254
|
lastActiveAt: match.lastActiveAt,
|
|
343
255
|
};
|
|
344
256
|
}
|
|
345
|
-
stats = getSessionStats(parsed.claude_session_id);
|
|
257
|
+
stats = getSessionStats(parsed.claude_session_id, projectRoot);
|
|
346
258
|
}
|
|
347
259
|
if (!content) {
|
|
348
260
|
const parts = [];
|
|
@@ -1,5 +1,5 @@
|
|
|
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
4
|
import { createLogger } from '../utils/logger.js';
|
|
5
5
|
const log = createLogger('dispatch');
|
|
@@ -69,20 +69,23 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
|
|
|
69
69
|
// Launch in iTerm — interactive TUI mode (no -p) for full visibility
|
|
70
70
|
const promptText = prompt ?? command;
|
|
71
71
|
const escaped = escapeForAnsiC(promptText);
|
|
72
|
-
const fullCmd = `cd '${projectRoot}' && ORBITAL_DISPATCH_ID='${eventId}' claude --dangerously-skip-permissions $'${escaped}'`;
|
|
72
|
+
const fullCmd = `cd '${shellQuote(projectRoot)}' && ORBITAL_DISPATCH_ID='${shellQuote(eventId)}' claude --dangerously-skip-permissions $'${escaped}'`;
|
|
73
73
|
try {
|
|
74
74
|
await launchInCategorizedTerminal(command, fullCmd, sessionName);
|
|
75
75
|
res.json({ ok: true, dispatch_id: eventId, scope_id: scope_id ?? null });
|
|
76
|
-
// Fire-and-forget: discover session PID, link to dispatch, and rename
|
|
76
|
+
// Fire-and-forget: discover session PID, link to dispatch, and rename.
|
|
77
|
+
// If discovery fails, SESSION_START event handler will link via ORBITAL_DISPATCH_ID.
|
|
77
78
|
discoverNewSession(projectRoot, beforePids)
|
|
78
79
|
.then((session) => {
|
|
79
|
-
if (!session)
|
|
80
|
+
if (!session) {
|
|
81
|
+
log.warn('PID discovery returned null — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId, scope_id });
|
|
80
82
|
return;
|
|
83
|
+
}
|
|
81
84
|
linkPidToDispatch(db, eventId, session.pid);
|
|
82
85
|
if (sessionName)
|
|
83
86
|
renameSession(projectRoot, session.sessionId, sessionName);
|
|
84
87
|
})
|
|
85
|
-
.catch(err => log.
|
|
88
|
+
.catch(err => log.warn('PID discovery failed — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId, error: err.message }));
|
|
86
89
|
}
|
|
87
90
|
catch (err) {
|
|
88
91
|
if (scope_id != null && transition?.from) {
|
|
@@ -188,18 +191,21 @@ export function createDispatchRoutes({ db, io, scopeService, projectRoot, engine
|
|
|
188
191
|
// Launch single CLI session
|
|
189
192
|
const batchEscaped = escapeForAnsiC(command);
|
|
190
193
|
const beforePids = snapshotSessionPids(projectRoot);
|
|
191
|
-
const fullCmd = `cd '${projectRoot}' && ORBITAL_DISPATCH_ID='${eventId}' claude --dangerously-skip-permissions -p $'${batchEscaped}'`;
|
|
194
|
+
const fullCmd = `cd '${shellQuote(projectRoot)}' && ORBITAL_DISPATCH_ID='${shellQuote(eventId)}' claude --dangerously-skip-permissions -p $'${batchEscaped}'`;
|
|
192
195
|
try {
|
|
193
196
|
await launchInCategorizedTerminal(command, fullCmd);
|
|
194
197
|
res.json({ ok: true, dispatch_id: eventId, scope_ids });
|
|
195
|
-
// Fire-and-forget: discover session PID and link to dispatch
|
|
198
|
+
// Fire-and-forget: discover session PID and link to dispatch.
|
|
199
|
+
// If discovery fails, SESSION_START event handler will link via ORBITAL_DISPATCH_ID.
|
|
196
200
|
discoverNewSession(projectRoot, beforePids)
|
|
197
201
|
.then((session) => {
|
|
198
|
-
if (!session)
|
|
202
|
+
if (!session) {
|
|
203
|
+
log.warn('Batch PID discovery returned null — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId });
|
|
199
204
|
return;
|
|
205
|
+
}
|
|
200
206
|
linkPidToDispatch(db, eventId, session.pid);
|
|
201
207
|
})
|
|
202
|
-
.catch(err => log.
|
|
208
|
+
.catch(err => log.warn('Batch PID discovery failed — dispatch will rely on ORBITAL_DISPATCH_ID for linkage', { dispatch_id: eventId, error: err.message }));
|
|
203
209
|
}
|
|
204
210
|
catch (err) {
|
|
205
211
|
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
|
}
|