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,369 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { Router } from 'express';
|
|
3
|
+
import type { Server } from 'socket.io';
|
|
4
|
+
import { ProjectEmitter } from './project-emitter.js';
|
|
5
|
+
import { createProjectContext } from './project-context.js';
|
|
6
|
+
import type { ProjectContext, ProjectStatus } from './project-context.js';
|
|
7
|
+
import {
|
|
8
|
+
loadGlobalConfig,
|
|
9
|
+
registerProject,
|
|
10
|
+
unregisterProject,
|
|
11
|
+
updateProject,
|
|
12
|
+
findProject,
|
|
13
|
+
} from './global-config.js';
|
|
14
|
+
import type { ProjectRegistration } from './global-config.js';
|
|
15
|
+
import { createScopeRoutes } from './routes/scope-routes.js';
|
|
16
|
+
import { createDataRoutes } from './routes/data-routes.js';
|
|
17
|
+
import { createDispatchRoutes } from './routes/dispatch-routes.js';
|
|
18
|
+
import { createSprintRoutes } from './routes/sprint-routes.js';
|
|
19
|
+
import { createWorkflowRoutes } from './routes/workflow-routes.js';
|
|
20
|
+
import { createConfigRoutes } from './routes/config-routes.js';
|
|
21
|
+
import { createGitRoutes } from './routes/git-routes.js';
|
|
22
|
+
import { createManifestRoutes } from './routes/manifest-routes.js';
|
|
23
|
+
import { createTelemetryRoutes } from './services/telemetry-service.js';
|
|
24
|
+
import { TEMPLATES_DIR } from './init.js';
|
|
25
|
+
import { getPackageVersion } from './utils/package-info.js';
|
|
26
|
+
import { resolveActiveDispatchesForScope } from './utils/dispatch-utils.js';
|
|
27
|
+
import { createLogger } from './utils/logger.js';
|
|
28
|
+
|
|
29
|
+
const log = createLogger('project-manager');
|
|
30
|
+
|
|
31
|
+
// ─── Types ──────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
export interface ProjectSummary {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
path: string;
|
|
37
|
+
color: string;
|
|
38
|
+
status: ProjectStatus;
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
scopeCount: number;
|
|
41
|
+
error?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ─── Manager ────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
export class ProjectManager {
|
|
47
|
+
private contexts = new Map<string, ProjectContext>();
|
|
48
|
+
private routers = new Map<string, Router>();
|
|
49
|
+
private healthCheckInterval: ReturnType<typeof setInterval> | null = null;
|
|
50
|
+
|
|
51
|
+
constructor(private io: Server) {}
|
|
52
|
+
|
|
53
|
+
// ─── Initialization ─────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
/** Initialize all enabled projects from the registry. */
|
|
56
|
+
async initializeAll(): Promise<void> {
|
|
57
|
+
const config = loadGlobalConfig();
|
|
58
|
+
const enabledProjects = config.projects.filter(p => p.enabled);
|
|
59
|
+
|
|
60
|
+
log.info('Initializing projects', { count: enabledProjects.length });
|
|
61
|
+
|
|
62
|
+
for (const reg of enabledProjects) {
|
|
63
|
+
try {
|
|
64
|
+
await this.initializeProject(reg);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
log.error('Failed to initialize project', {
|
|
67
|
+
id: reg.id,
|
|
68
|
+
path: reg.path,
|
|
69
|
+
error: String(err),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Start periodic health checks
|
|
75
|
+
this.healthCheckInterval = setInterval(() => this.checkHealth(), 60_000);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Initialize a single project from its registration. */
|
|
79
|
+
async initializeProject(reg: ProjectRegistration): Promise<ProjectContext | null> {
|
|
80
|
+
// Verify directory exists
|
|
81
|
+
if (!fs.existsSync(reg.path)) {
|
|
82
|
+
log.warn('Project directory not found, marking offline', { id: reg.id, path: reg.path });
|
|
83
|
+
// Store a placeholder context to track status
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const emitter = new ProjectEmitter(this.io, reg.id);
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const ctx = await createProjectContext(reg.id, reg.path, emitter);
|
|
91
|
+
this.contexts.set(reg.id, ctx);
|
|
92
|
+
|
|
93
|
+
// Build and cache the project's router
|
|
94
|
+
const router = this.buildProjectRouter(ctx);
|
|
95
|
+
this.routers.set(reg.id, router);
|
|
96
|
+
|
|
97
|
+
log.info('Project initialized', {
|
|
98
|
+
id: reg.id,
|
|
99
|
+
name: reg.name,
|
|
100
|
+
scopeCount: ctx.scopeService.getAll().length,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return ctx;
|
|
104
|
+
} catch (err) {
|
|
105
|
+
log.error('Project initialization failed', { id: reg.id, error: String(err) });
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ─── Context Access ─────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
/** Get a project context by ID. */
|
|
113
|
+
getContext(id: string): ProjectContext | undefined {
|
|
114
|
+
return this.contexts.get(id);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Get all active contexts. */
|
|
118
|
+
getAllContexts(): Map<string, ProjectContext> {
|
|
119
|
+
return this.contexts;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Get the router for a project. */
|
|
123
|
+
getRouter(id: string): Router | undefined {
|
|
124
|
+
return this.routers.get(id);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Get all project routers. */
|
|
128
|
+
getAllRouters(): Map<string, Router> {
|
|
129
|
+
return this.routers;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ─── Project List ───────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
/** Get summary of all registered projects with live status. */
|
|
135
|
+
getProjectList(options?: { includeWorkflow?: boolean }): (ProjectSummary & { workflow?: unknown })[] {
|
|
136
|
+
const config = loadGlobalConfig();
|
|
137
|
+
return config.projects.map(reg => {
|
|
138
|
+
const ctx = this.contexts.get(reg.id);
|
|
139
|
+
const summary: ProjectSummary & { workflow?: unknown } = {
|
|
140
|
+
id: reg.id,
|
|
141
|
+
name: reg.name,
|
|
142
|
+
path: reg.path,
|
|
143
|
+
color: reg.color,
|
|
144
|
+
status: ctx?.status ?? 'offline' as ProjectStatus,
|
|
145
|
+
enabled: reg.enabled,
|
|
146
|
+
scopeCount: ctx ? ctx.scopeService.getAll().length : 0,
|
|
147
|
+
error: ctx?.error,
|
|
148
|
+
};
|
|
149
|
+
if (options?.includeWorkflow && ctx) {
|
|
150
|
+
summary.workflow = ctx.workflowService.getActive();
|
|
151
|
+
}
|
|
152
|
+
return summary;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ─── Registration ───────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
/** Register and initialize a new project. */
|
|
159
|
+
async addProject(projectRoot: string, options?: { name?: string; color?: string }): Promise<ProjectSummary> {
|
|
160
|
+
const reg = registerProject(projectRoot, options);
|
|
161
|
+
const ctx = await this.initializeProject(reg);
|
|
162
|
+
|
|
163
|
+
// Notify all clients
|
|
164
|
+
this.io.emit('project:registered', {
|
|
165
|
+
id: reg.id,
|
|
166
|
+
name: reg.name,
|
|
167
|
+
path: reg.path,
|
|
168
|
+
color: reg.color,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
id: reg.id,
|
|
173
|
+
name: reg.name,
|
|
174
|
+
path: reg.path,
|
|
175
|
+
color: reg.color,
|
|
176
|
+
status: ctx?.status ?? 'offline',
|
|
177
|
+
enabled: reg.enabled,
|
|
178
|
+
scopeCount: ctx ? ctx.scopeService.getAll().length : 0,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Unregister a project and shut down its context. */
|
|
183
|
+
async removeProject(idOrPath: string): Promise<boolean> {
|
|
184
|
+
// Find the registration before removing
|
|
185
|
+
const config = loadGlobalConfig();
|
|
186
|
+
const reg = config.projects.find(p => p.id === idOrPath || p.path === idOrPath);
|
|
187
|
+
if (!reg) return false;
|
|
188
|
+
|
|
189
|
+
// Shut down context
|
|
190
|
+
await this.shutdownProject(reg.id);
|
|
191
|
+
|
|
192
|
+
// Remove from registry
|
|
193
|
+
unregisterProject(idOrPath);
|
|
194
|
+
|
|
195
|
+
// Notify clients
|
|
196
|
+
this.io.emit('project:unregistered', { id: reg.id });
|
|
197
|
+
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Update project metadata (name, color, enabled). */
|
|
202
|
+
async updateProject(
|
|
203
|
+
id: string,
|
|
204
|
+
updates: Partial<Pick<ProjectRegistration, 'name' | 'color' | 'enabled'>>,
|
|
205
|
+
): Promise<ProjectRegistration | null> {
|
|
206
|
+
const result = updateProject(id, updates);
|
|
207
|
+
if (!result) return null;
|
|
208
|
+
|
|
209
|
+
// If disabling, shut down the context
|
|
210
|
+
if (updates.enabled === false) {
|
|
211
|
+
await this.shutdownProject(id);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// If enabling, initialize the context
|
|
215
|
+
if (updates.enabled === true && !this.contexts.has(id)) {
|
|
216
|
+
const reg = findProject(id);
|
|
217
|
+
if (reg) await this.initializeProject(reg);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Notify clients of metadata change
|
|
221
|
+
this.io.emit('project:updated', { id, ...updates });
|
|
222
|
+
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ─── Lifecycle ──────────────────────────────────────────
|
|
227
|
+
|
|
228
|
+
/** Shut down a single project's context. */
|
|
229
|
+
async shutdownProject(id: string): Promise<void> {
|
|
230
|
+
const ctx = this.contexts.get(id);
|
|
231
|
+
if (ctx) {
|
|
232
|
+
await ctx.shutdown();
|
|
233
|
+
this.contexts.delete(id);
|
|
234
|
+
this.routers.delete(id);
|
|
235
|
+
log.info('Project shut down', { id });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Shut down all projects and stop health checks. */
|
|
240
|
+
async shutdownAll(): Promise<void> {
|
|
241
|
+
if (this.healthCheckInterval) {
|
|
242
|
+
clearInterval(this.healthCheckInterval);
|
|
243
|
+
this.healthCheckInterval = null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const shutdowns = [...this.contexts.entries()].map(([id, ctx]) =>
|
|
247
|
+
ctx.shutdown().catch(err =>
|
|
248
|
+
log.error('Error shutting down project', { id, error: String(err) }),
|
|
249
|
+
),
|
|
250
|
+
);
|
|
251
|
+
await Promise.all(shutdowns);
|
|
252
|
+
this.contexts.clear();
|
|
253
|
+
this.routers.clear();
|
|
254
|
+
log.info('All projects shut down');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ─── Health Checks ──────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
/** Periodic health check — detect projects that have gone offline or come back. */
|
|
260
|
+
private async checkHealth(): Promise<void> {
|
|
261
|
+
const config = loadGlobalConfig();
|
|
262
|
+
|
|
263
|
+
for (const reg of config.projects) {
|
|
264
|
+
if (!reg.enabled) continue;
|
|
265
|
+
|
|
266
|
+
const ctx = this.contexts.get(reg.id);
|
|
267
|
+
const dirExists = fs.existsSync(reg.path);
|
|
268
|
+
|
|
269
|
+
if (ctx && !dirExists) {
|
|
270
|
+
// Project went offline
|
|
271
|
+
log.warn('Project directory disappeared', { id: reg.id, path: reg.path });
|
|
272
|
+
await this.shutdownProject(reg.id);
|
|
273
|
+
this.io.emit('project:status:changed', { id: reg.id, status: 'offline' });
|
|
274
|
+
} else if (!ctx && dirExists) {
|
|
275
|
+
// Project came back online (or failed to initialize previously — retry)
|
|
276
|
+
log.info('Attempting to initialize project', { id: reg.id, path: reg.path });
|
|
277
|
+
try {
|
|
278
|
+
await this.initializeProject(reg);
|
|
279
|
+
this.io.emit('project:status:changed', { id: reg.id, status: 'active' });
|
|
280
|
+
} catch (err) {
|
|
281
|
+
log.warn('Project initialization retry failed', { id: reg.id, error: String(err) });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ─── Route Building ─────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
/** Build an Express Router with all per-project routes for a context. */
|
|
290
|
+
/** Build an Express Router with all per-project routes.
|
|
291
|
+
* Note: createVersionRoutes is intentionally omitted — version/update endpoints
|
|
292
|
+
* are global (they update the Orbital Command package), not per-project. */
|
|
293
|
+
private buildProjectRouter(ctx: ProjectContext): Router {
|
|
294
|
+
const router = Router();
|
|
295
|
+
const { db, emitter, config, scopeService, eventService, gateService, deployService,
|
|
296
|
+
sprintService, sprintOrchestrator, batchOrchestrator,
|
|
297
|
+
readinessService, workflowService, workflowEngine,
|
|
298
|
+
gitService, githubService, telemetryService } = ctx;
|
|
299
|
+
|
|
300
|
+
// Scope status inference function (same logic as index.ts)
|
|
301
|
+
function inferScopeStatus(
|
|
302
|
+
eventType: string,
|
|
303
|
+
scopeId: unknown,
|
|
304
|
+
data: Record<string, unknown>,
|
|
305
|
+
): void {
|
|
306
|
+
if (scopeId == null) return;
|
|
307
|
+
const id = Number(scopeId);
|
|
308
|
+
if (isNaN(id) || id <= 0) return;
|
|
309
|
+
const current = scopeService.getById(id);
|
|
310
|
+
if (current?.status === 'icebox') return;
|
|
311
|
+
const currentStatus = current?.status ?? '';
|
|
312
|
+
const result = workflowEngine.inferStatus(eventType, currentStatus, data);
|
|
313
|
+
if (result === null) return;
|
|
314
|
+
if (typeof result === 'object' && 'dispatchResolution' in result) {
|
|
315
|
+
resolveActiveDispatchesForScope(db, emitter, id, result.resolution as 'completed' | 'failed');
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
scopeService.updateStatus(id, result, 'event');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Project config endpoint
|
|
322
|
+
router.get('/config', (_req, res) => {
|
|
323
|
+
res.json({
|
|
324
|
+
projectName: config.projectName,
|
|
325
|
+
categories: config.categories,
|
|
326
|
+
agents: config.agents,
|
|
327
|
+
serverPort: config.serverPort,
|
|
328
|
+
clientPort: config.clientPort,
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Mount all route groups
|
|
333
|
+
router.use(createScopeRoutes({
|
|
334
|
+
db, io: emitter, scopeService, readinessService,
|
|
335
|
+
projectRoot: config.projectRoot, projectName: config.projectName,
|
|
336
|
+
engine: workflowEngine,
|
|
337
|
+
}));
|
|
338
|
+
router.use(createDataRoutes({
|
|
339
|
+
db, io: emitter, eventService, gateService, deployService, gitService,
|
|
340
|
+
engine: workflowEngine, projectRoot: config.projectRoot,
|
|
341
|
+
inferScopeStatus,
|
|
342
|
+
}));
|
|
343
|
+
router.use(createDispatchRoutes({
|
|
344
|
+
db, io: emitter, scopeService,
|
|
345
|
+
projectRoot: config.projectRoot, engine: workflowEngine,
|
|
346
|
+
}));
|
|
347
|
+
router.use(createSprintRoutes({
|
|
348
|
+
sprintService, sprintOrchestrator, batchOrchestrator,
|
|
349
|
+
}));
|
|
350
|
+
router.use(createWorkflowRoutes({
|
|
351
|
+
workflowService, projectRoot: config.projectRoot,
|
|
352
|
+
}));
|
|
353
|
+
router.use(createConfigRoutes({
|
|
354
|
+
projectRoot: config.projectRoot, workflowService, io: emitter,
|
|
355
|
+
}));
|
|
356
|
+
router.use(createGitRoutes({
|
|
357
|
+
gitService, githubService, engine: workflowEngine,
|
|
358
|
+
}));
|
|
359
|
+
router.use(createManifestRoutes({
|
|
360
|
+
projectRoot: config.projectRoot,
|
|
361
|
+
templatesDir: TEMPLATES_DIR,
|
|
362
|
+
packageVersion: getPackageVersion(),
|
|
363
|
+
io: emitter,
|
|
364
|
+
}));
|
|
365
|
+
router.use(createTelemetryRoutes({ telemetryService }));
|
|
366
|
+
|
|
367
|
+
return router;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Emitter } from '../project-emitter.js';
|
|
3
3
|
import { ConfigService, isValidPrimitiveType } from '../services/config-service.js';
|
|
4
4
|
import type { ConfigPrimitiveType } from '../services/config-service.js';
|
|
5
5
|
import type { WorkflowService } from '../services/workflow-service.js';
|
|
6
|
+
import { errMsg } from '../utils/route-helpers.js';
|
|
6
7
|
|
|
7
8
|
interface ConfigRouteDeps {
|
|
8
9
|
projectRoot: string;
|
|
9
10
|
workflowService: WorkflowService;
|
|
10
|
-
io:
|
|
11
|
+
io: Emitter;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function createConfigRoutes({ projectRoot, workflowService: _workflowService, io }: ConfigRouteDeps): Router {
|
|
@@ -177,6 +178,3 @@ export function createConfigRoutes({ projectRoot, workflowService: _workflowServ
|
|
|
177
178
|
return router;
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
function errMsg(err: unknown): string {
|
|
181
|
-
return err instanceof Error ? err.message : String(err);
|
|
182
|
-
}
|
|
@@ -3,25 +3,20 @@ import { execFile } from 'child_process';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import type Database from 'better-sqlite3';
|
|
6
|
-
import type {
|
|
6
|
+
import type { Emitter } from '../project-emitter.js';
|
|
7
|
+
import type { EventService } from '../services/event-service.js';
|
|
7
8
|
import type { GateService } from '../services/gate-service.js';
|
|
8
9
|
import type { DeployService } from '../services/deploy-service.js';
|
|
10
|
+
import type { GitService } from '../services/git-service.js';
|
|
9
11
|
import type { WorkflowEngine } from '../../shared/workflow-engine.js';
|
|
10
12
|
import { getHookEnforcement } from '../../shared/workflow-config.js';
|
|
11
13
|
import { getClaudeSessions, getSessionStats, type SessionStats } from '../services/claude-session-service.js';
|
|
12
14
|
import { launchInTerminal } from '../utils/terminal-launcher.js';
|
|
15
|
+
import { createLogger } from '../utils/logger.js';
|
|
13
16
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
// ─── Types & Helpers ────────────────────────────────────────
|
|
17
|
+
const log = createLogger('server');
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
interface BranchHead { sha: string; date: string; message: string }
|
|
20
|
-
interface PipelineDriftData {
|
|
21
|
-
devToStaging: { count: number; commits: DriftCommit[]; oldestDate: string | null };
|
|
22
|
-
stagingToMain: { count: number; commits: DriftCommit[]; oldestDate: string | null };
|
|
23
|
-
heads: { dev: BranchHead; staging: BranchHead; main: BranchHead };
|
|
24
|
-
}
|
|
19
|
+
const execFileAsync = promisify(execFile);
|
|
25
20
|
|
|
26
21
|
const JSON_FIELDS = ['tags', 'blocked_by', 'blocks', 'data', 'discoveries', 'next_steps', 'details'];
|
|
27
22
|
|
|
@@ -37,100 +32,32 @@ function parseJsonFields(row: Row): Row {
|
|
|
37
32
|
return parsed;
|
|
38
33
|
}
|
|
39
34
|
|
|
40
|
-
function parseDriftCommits(raw: string): DriftCommit[] {
|
|
41
|
-
if (!raw) return [];
|
|
42
|
-
return raw.split('\n').map((line) => {
|
|
43
|
-
const [sha, date, message, author] = line.split('|');
|
|
44
|
-
return { sha, date, message: message ?? '', author: author ?? '' };
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function parseHead(raw: string): BranchHead {
|
|
49
|
-
const [sha, date, message] = raw.split('|');
|
|
50
|
-
return { sha: sha ?? '', date: date ?? '', message: message ?? '' };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
35
|
// ─── Route Factory ──────────────────────────────────────────
|
|
54
36
|
|
|
55
37
|
interface DataRouteDeps {
|
|
56
38
|
db: Database.Database;
|
|
57
|
-
io:
|
|
39
|
+
io: Emitter;
|
|
40
|
+
eventService: EventService;
|
|
58
41
|
gateService: GateService;
|
|
59
42
|
deployService: DeployService;
|
|
43
|
+
gitService: GitService;
|
|
60
44
|
engine: WorkflowEngine;
|
|
61
45
|
projectRoot: string;
|
|
62
46
|
inferScopeStatus: (type: string, scopeId: unknown, data: Record<string, unknown>) => void;
|
|
63
47
|
}
|
|
64
48
|
|
|
65
49
|
export function createDataRoutes({
|
|
66
|
-
db, io, gateService, deployService, engine, projectRoot, inferScopeStatus,
|
|
50
|
+
db, io, eventService, gateService, deployService, gitService, engine, projectRoot, inferScopeStatus,
|
|
67
51
|
}: DataRouteDeps): Router {
|
|
68
52
|
const router = Router();
|
|
69
53
|
|
|
70
|
-
// ─── Pipeline Drift (cached) ─────────────────────────────
|
|
71
|
-
|
|
72
|
-
let driftCache: { data: PipelineDriftData; ts: number } | null = null;
|
|
73
|
-
const DRIFT_CACHE_MS = 60_000;
|
|
74
|
-
|
|
75
|
-
async function gitLog(args: string[]): Promise<string> {
|
|
76
|
-
const { stdout } = await execFileAsync('git', args, { cwd: projectRoot });
|
|
77
|
-
return stdout.trim();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async function computeDrift(): Promise<PipelineDriftData> {
|
|
81
|
-
if (driftCache && Date.now() - driftCache.ts < DRIFT_CACHE_MS) return driftCache.data;
|
|
82
|
-
|
|
83
|
-
const [devToStagingRaw, stagingToMainRaw, devHead, stagingHead, mainHead] =
|
|
84
|
-
await Promise.all([
|
|
85
|
-
gitLog(['log', 'origin/dev', '--not', 'origin/staging', '--reverse', '--format=%H|%aI|%s|%an']),
|
|
86
|
-
gitLog(['log', 'origin/staging', '--not', 'origin/main', '--reverse', '--format=%H|%aI|%s|%an']),
|
|
87
|
-
gitLog(['log', 'origin/dev', '-1', '--format=%H|%aI|%s']),
|
|
88
|
-
gitLog(['log', 'origin/staging', '-1', '--format=%H|%aI|%s']),
|
|
89
|
-
gitLog(['log', 'origin/main', '-1', '--format=%H|%aI|%s']),
|
|
90
|
-
]);
|
|
91
|
-
|
|
92
|
-
const devToStaging = parseDriftCommits(devToStagingRaw);
|
|
93
|
-
const stagingToMain = parseDriftCommits(stagingToMainRaw);
|
|
94
|
-
|
|
95
|
-
const data: PipelineDriftData = {
|
|
96
|
-
devToStaging: {
|
|
97
|
-
count: devToStaging.length,
|
|
98
|
-
commits: devToStaging,
|
|
99
|
-
oldestDate: devToStaging[0]?.date ?? null,
|
|
100
|
-
},
|
|
101
|
-
stagingToMain: {
|
|
102
|
-
count: stagingToMain.length,
|
|
103
|
-
commits: stagingToMain,
|
|
104
|
-
oldestDate: stagingToMain[0]?.date ?? null,
|
|
105
|
-
},
|
|
106
|
-
heads: {
|
|
107
|
-
dev: parseHead(devHead),
|
|
108
|
-
staging: parseHead(stagingHead),
|
|
109
|
-
main: parseHead(mainHead),
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
driftCache = { data, ts: Date.now() };
|
|
114
|
-
return data;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
54
|
// ─── Event Routes ──────────────────────────────────────────
|
|
118
55
|
|
|
119
56
|
router.get('/events', (req, res) => {
|
|
120
57
|
const limit = Number(req.query.limit) || 50;
|
|
121
58
|
const type = req.query.type as string | undefined;
|
|
122
|
-
const scopeId = req.query.scope_id
|
|
123
|
-
|
|
124
|
-
let query = 'SELECT * FROM events WHERE 1=1';
|
|
125
|
-
const params: unknown[] = [];
|
|
126
|
-
|
|
127
|
-
if (type) { query += ' AND type = ?'; params.push(type); }
|
|
128
|
-
if (scopeId) { query += ' AND scope_id = ?'; params.push(Number(scopeId)); }
|
|
129
|
-
|
|
130
|
-
query += ' ORDER BY timestamp DESC LIMIT ?';
|
|
131
|
-
params.push(limit);
|
|
132
|
-
|
|
133
|
-
const events = db.prepare(query).all(...params) as Row[];
|
|
59
|
+
const scopeId = req.query.scope_id ? Number(req.query.scope_id) : undefined;
|
|
60
|
+
const events = eventService.getFiltered({ limit, type, scopeId }) as unknown as Row[];
|
|
134
61
|
res.json(events.map(parseJsonFields));
|
|
135
62
|
});
|
|
136
63
|
|
|
@@ -167,23 +94,9 @@ export function createDataRoutes({
|
|
|
167
94
|
|
|
168
95
|
router.get('/events/violations/summary', (_req, res) => {
|
|
169
96
|
try {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
).all();
|
|
174
|
-
const byFile = db.prepare(
|
|
175
|
-
`SELECT JSON_EXTRACT(data, '$.file') as file, COUNT(*) as count FROM events
|
|
176
|
-
WHERE type = 'VIOLATION' AND JSON_EXTRACT(data, '$.file') IS NOT NULL AND JSON_EXTRACT(data, '$.file') != ''
|
|
177
|
-
GROUP BY file ORDER BY count DESC LIMIT 20`
|
|
178
|
-
).all();
|
|
179
|
-
const overrides = db.prepare(
|
|
180
|
-
`SELECT JSON_EXTRACT(data, '$.rule') as rule, JSON_EXTRACT(data, '$.reason') as reason, timestamp as date
|
|
181
|
-
FROM events WHERE type = 'OVERRIDE' ORDER BY timestamp DESC LIMIT 50`
|
|
182
|
-
).all();
|
|
183
|
-
const totalViolations = db.prepare(`SELECT COUNT(*) as count FROM events WHERE type = 'VIOLATION'`).get() as { count: number };
|
|
184
|
-
const totalOverrides = db.prepare(`SELECT COUNT(*) as count FROM events WHERE type = 'OVERRIDE'`).get() as { count: number };
|
|
185
|
-
res.json({ byRule, byFile, overrides, totalViolations: totalViolations.count, totalOverrides: totalOverrides.count });
|
|
186
|
-
} catch {
|
|
97
|
+
res.json(eventService.getViolationSummary());
|
|
98
|
+
} catch (err) {
|
|
99
|
+
log.error('Violations summary failed', { error: String(err) });
|
|
187
100
|
res.status(500).json({ error: 'Failed to query violations summary' });
|
|
188
101
|
}
|
|
189
102
|
});
|
|
@@ -204,19 +117,7 @@ export function createDataRoutes({
|
|
|
204
117
|
}
|
|
205
118
|
}
|
|
206
119
|
|
|
207
|
-
|
|
208
|
-
const violationStats = db.prepare(
|
|
209
|
-
`SELECT JSON_EXTRACT(data, '$.rule') as rule, COUNT(*) as count, MAX(timestamp) as last_seen
|
|
210
|
-
FROM events WHERE type = 'VIOLATION' GROUP BY rule`
|
|
211
|
-
).all() as Array<{ rule: string; count: number; last_seen: string }>;
|
|
212
|
-
|
|
213
|
-
const overrideStats = db.prepare(
|
|
214
|
-
`SELECT JSON_EXTRACT(data, '$.rule') as rule, COUNT(*) as count
|
|
215
|
-
FROM events WHERE type = 'OVERRIDE' GROUP BY rule`
|
|
216
|
-
).all() as Array<{ rule: string; count: number }>;
|
|
217
|
-
|
|
218
|
-
const violationMap = new Map(violationStats.map((v) => [v.rule, v]));
|
|
219
|
-
const overrideMap = new Map(overrideStats.map((o) => [o.rule, o]));
|
|
120
|
+
const { violations: violationMap, overrides: overrideMap } = eventService.getViolationStatsByRule();
|
|
220
121
|
|
|
221
122
|
// Build summary counts
|
|
222
123
|
const summary = { guards: 0, gates: 0, lifecycle: 0, observers: 0 };
|
|
@@ -239,7 +140,8 @@ export function createDataRoutes({
|
|
|
239
140
|
}));
|
|
240
141
|
|
|
241
142
|
res.json({ summary, rules, totalEdges: allEdges.length });
|
|
242
|
-
} catch {
|
|
143
|
+
} catch (err) {
|
|
144
|
+
log.error('Enforcement rules failed', { error: String(err) });
|
|
243
145
|
res.status(500).json({ error: 'Failed to query enforcement rules' });
|
|
244
146
|
}
|
|
245
147
|
});
|
|
@@ -248,14 +150,10 @@ export function createDataRoutes({
|
|
|
248
150
|
|
|
249
151
|
router.get('/events/violations/trend', (req, res) => {
|
|
250
152
|
try {
|
|
251
|
-
const days = Number(req.query.days) || 30;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
GROUP BY day, rule ORDER BY day ASC`
|
|
256
|
-
).all(`-${days}`) as Array<{ day: string; rule: string; count: number }>;
|
|
257
|
-
res.json(trend);
|
|
258
|
-
} catch {
|
|
153
|
+
const days = Math.max(1, Math.min(365, Number(req.query.days) || 30));
|
|
154
|
+
res.json(eventService.getViolationTrend(days));
|
|
155
|
+
} catch (err) {
|
|
156
|
+
log.error('Violation trends failed', { error: String(err) });
|
|
259
157
|
res.status(500).json({ error: 'Failed to query violation trends' });
|
|
260
158
|
}
|
|
261
159
|
});
|
|
@@ -330,8 +228,7 @@ export function createDataRoutes({
|
|
|
330
228
|
|
|
331
229
|
router.get('/pipeline/drift', async (_req, res) => {
|
|
332
230
|
try {
|
|
333
|
-
|
|
334
|
-
res.json(drift);
|
|
231
|
+
res.json(await gitService.getPipelineDrift());
|
|
335
232
|
} catch (err) {
|
|
336
233
|
res.status(500).json({ error: 'Failed to compute drift', details: String(err) });
|
|
337
234
|
}
|
|
@@ -339,19 +236,9 @@ export function createDataRoutes({
|
|
|
339
236
|
|
|
340
237
|
router.get('/deployments/frequency', (_req, res) => {
|
|
341
238
|
try {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
).all() as Array<{ environment: string; week: string; count: number }>;
|
|
346
|
-
const weekMap = new Map<string, { week: string; staging: number; production: number }>();
|
|
347
|
-
for (const row of rows) {
|
|
348
|
-
if (!weekMap.has(row.week)) weekMap.set(row.week, { week: row.week, staging: 0, production: 0 });
|
|
349
|
-
const entry = weekMap.get(row.week)!;
|
|
350
|
-
if (row.environment === 'staging') entry.staging = row.count;
|
|
351
|
-
if (row.environment === 'production') entry.production = row.count;
|
|
352
|
-
}
|
|
353
|
-
res.json([...weekMap.values()]);
|
|
354
|
-
} catch {
|
|
239
|
+
res.json(eventService.getDeployFrequency());
|
|
240
|
+
} catch (err) {
|
|
241
|
+
log.error('Deploy frequency query failed', { error: String(err) });
|
|
355
242
|
res.status(500).json({ error: 'Failed to query deployment frequency' });
|
|
356
243
|
}
|
|
357
244
|
});
|
|
@@ -417,7 +304,7 @@ export function createDataRoutes({
|
|
|
417
304
|
let stats: SessionStats | null = null;
|
|
418
305
|
|
|
419
306
|
if (parsed.claude_session_id && typeof parsed.claude_session_id === 'string') {
|
|
420
|
-
const claudeSessions = await getClaudeSessions();
|
|
307
|
+
const claudeSessions = await getClaudeSessions(undefined, projectRoot);
|
|
421
308
|
const match = claudeSessions.find(s => s.id === parsed.claude_session_id);
|
|
422
309
|
if (match) {
|
|
423
310
|
meta = {
|
|
@@ -429,7 +316,7 @@ export function createDataRoutes({
|
|
|
429
316
|
lastActiveAt: match.lastActiveAt,
|
|
430
317
|
};
|
|
431
318
|
}
|
|
432
|
-
stats = getSessionStats(parsed.claude_session_id as string);
|
|
319
|
+
stats = getSessionStats(parsed.claude_session_id as string, projectRoot);
|
|
433
320
|
}
|
|
434
321
|
|
|
435
322
|
if (!content) {
|