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,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Telemetry — uploads raw Claude session JSONL files to a remote
|
|
3
|
+
* Cloudflare Worker + R2 endpoint. This entire feature lives in this single
|
|
4
|
+
* file for easy removal.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { Router } from 'express';
|
|
10
|
+
import type Database from 'better-sqlite3';
|
|
11
|
+
import { getClaudeSessionsDir } from '../config.js';
|
|
12
|
+
|
|
13
|
+
export interface TelemetryConfig {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
url: string;
|
|
16
|
+
headers: Record<string, string>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface SessionRow {
|
|
20
|
+
id: string;
|
|
21
|
+
claude_session_id: string | null;
|
|
22
|
+
ended_at: string | null;
|
|
23
|
+
telemetry_sent_at: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface TelemetryResult {
|
|
27
|
+
ok: boolean;
|
|
28
|
+
uploaded: number;
|
|
29
|
+
errors: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ─── Service ───────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
export class TelemetryService {
|
|
35
|
+
private lastResult: TelemetryResult | null = null;
|
|
36
|
+
|
|
37
|
+
constructor(
|
|
38
|
+
private db: Database.Database,
|
|
39
|
+
private config: TelemetryConfig,
|
|
40
|
+
private projectName: string,
|
|
41
|
+
private projectRoot: string,
|
|
42
|
+
) {}
|
|
43
|
+
|
|
44
|
+
get enabled(): boolean {
|
|
45
|
+
return this.config.enabled && this.config.url.length > 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Upload sessions that have changed since last telemetry send. */
|
|
49
|
+
async uploadChangedSessions(): Promise<TelemetryResult> {
|
|
50
|
+
if (!this.enabled) return { ok: true, uploaded: 0, errors: 0 };
|
|
51
|
+
|
|
52
|
+
const rows = this.db.prepare(
|
|
53
|
+
`SELECT id, claude_session_id, ended_at, telemetry_sent_at
|
|
54
|
+
FROM sessions
|
|
55
|
+
WHERE claude_session_id IS NOT NULL
|
|
56
|
+
AND (telemetry_sent_at IS NULL OR ended_at > telemetry_sent_at)`
|
|
57
|
+
).all() as SessionRow[];
|
|
58
|
+
|
|
59
|
+
return this.uploadRows(rows);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Force re-upload all sessions regardless of telemetry_sent_at. */
|
|
63
|
+
async uploadAllSessions(): Promise<TelemetryResult> {
|
|
64
|
+
if (!this.enabled) return { ok: true, uploaded: 0, errors: 0 };
|
|
65
|
+
|
|
66
|
+
const rows = this.db.prepare(
|
|
67
|
+
`SELECT id, claude_session_id, ended_at, telemetry_sent_at
|
|
68
|
+
FROM sessions
|
|
69
|
+
WHERE claude_session_id IS NOT NULL`
|
|
70
|
+
).all() as SessionRow[];
|
|
71
|
+
|
|
72
|
+
return this.uploadRows(rows);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Ping the remote health endpoint. */
|
|
76
|
+
async testConnection(): Promise<{ ok: boolean; status: number; body: string }> {
|
|
77
|
+
try {
|
|
78
|
+
const res = await fetch(`${this.config.url}/health`, {
|
|
79
|
+
method: 'GET',
|
|
80
|
+
headers: this.config.headers,
|
|
81
|
+
signal: AbortSignal.timeout(10_000),
|
|
82
|
+
});
|
|
83
|
+
const body = await res.text();
|
|
84
|
+
return { ok: res.ok, status: res.status, body };
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return { ok: false, status: 0, body: (err as Error).message };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getStatus() {
|
|
91
|
+
return {
|
|
92
|
+
enabled: this.enabled,
|
|
93
|
+
url: this.config.url || null,
|
|
94
|
+
lastResult: this.lastResult,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ─── Internal ──────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
private async uploadRows(rows: SessionRow[]): Promise<TelemetryResult> {
|
|
101
|
+
if (rows.length === 0) {
|
|
102
|
+
this.lastResult = { ok: true, uploaded: 0, errors: 0 };
|
|
103
|
+
return this.lastResult;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const sessionsDir = getClaudeSessionsDir(this.projectRoot);
|
|
107
|
+
const now = new Date().toISOString();
|
|
108
|
+
let uploaded = 0;
|
|
109
|
+
let errors = 0;
|
|
110
|
+
|
|
111
|
+
const updateStmt = this.db.prepare(
|
|
112
|
+
'UPDATE sessions SET telemetry_sent_at = ? WHERE id = ?'
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Deduplicate by claude_session_id (multiple rows can share the same JSONL file)
|
|
116
|
+
const seen = new Set<string>();
|
|
117
|
+
const unique: SessionRow[] = [];
|
|
118
|
+
for (const row of rows) {
|
|
119
|
+
if (row.claude_session_id && !seen.has(row.claude_session_id)) {
|
|
120
|
+
seen.add(row.claude_session_id);
|
|
121
|
+
unique.push(row);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const row of unique) {
|
|
126
|
+
const sessionId = row.claude_session_id!;
|
|
127
|
+
const filePath = path.join(sessionsDir, `${sessionId}.jsonl`);
|
|
128
|
+
|
|
129
|
+
if (!fs.existsSync(filePath)) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const body = fs.readFileSync(filePath);
|
|
135
|
+
const encodedProject = encodeURIComponent(this.projectName);
|
|
136
|
+
const url = `${this.config.url}/upload/${encodedProject}/${sessionId}.jsonl`;
|
|
137
|
+
|
|
138
|
+
const res = await fetch(url, {
|
|
139
|
+
method: 'PUT',
|
|
140
|
+
body,
|
|
141
|
+
headers: {
|
|
142
|
+
'Content-Type': 'application/x-ndjson',
|
|
143
|
+
...this.config.headers,
|
|
144
|
+
},
|
|
145
|
+
signal: AbortSignal.timeout(30_000),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (res.ok) {
|
|
149
|
+
uploaded++;
|
|
150
|
+
// Update telemetry_sent_at for ALL rows with this claude_session_id
|
|
151
|
+
const matching = rows.filter((r) => r.claude_session_id === sessionId);
|
|
152
|
+
for (const m of matching) {
|
|
153
|
+
updateStmt.run(now, m.id);
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
errors++;
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
errors++;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this.lastResult = { ok: errors === 0, uploaded, errors };
|
|
164
|
+
return this.lastResult;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ─── Routes ────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
interface TelemetryRouteDeps {
|
|
171
|
+
telemetryService: TelemetryService;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function createTelemetryRoutes({ telemetryService }: TelemetryRouteDeps): Router {
|
|
175
|
+
const router = Router();
|
|
176
|
+
|
|
177
|
+
router.post('/telemetry/trigger', async (req, res) => {
|
|
178
|
+
const force = req.query.force === 'true';
|
|
179
|
+
const result = force
|
|
180
|
+
? await telemetryService.uploadAllSessions()
|
|
181
|
+
: await telemetryService.uploadChangedSessions();
|
|
182
|
+
res.json(result);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
router.post('/telemetry/test', async (_req, res) => {
|
|
186
|
+
const result = await telemetryService.testConnection();
|
|
187
|
+
res.json(result);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
router.get('/telemetry/status', (_req, res) => {
|
|
191
|
+
res.json(telemetryService.getStatus());
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return router;
|
|
195
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { WorkflowService } from './workflow-service.js';
|
|
3
|
+
import { WorkflowEngine } from '../../shared/workflow-engine.js';
|
|
4
|
+
import { DEFAULT_CONFIG, MINIMAL_CONFIG } from '../../shared/__fixtures__/workflow-configs.js';
|
|
5
|
+
import { createMockEmitter } from '../__tests__/helpers/mock-emitter.js';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
|
|
10
|
+
describe('WorkflowService', () => {
|
|
11
|
+
let tmpDir: string;
|
|
12
|
+
let configDir: string;
|
|
13
|
+
let scopesDir: string;
|
|
14
|
+
let defaultConfigPath: string;
|
|
15
|
+
let engine: WorkflowEngine;
|
|
16
|
+
let service: WorkflowService;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'workflow-svc-test-'));
|
|
20
|
+
configDir = path.join(tmpDir, 'config');
|
|
21
|
+
scopesDir = path.join(tmpDir, 'scopes');
|
|
22
|
+
defaultConfigPath = path.join(tmpDir, 'default-workflow.json');
|
|
23
|
+
|
|
24
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
25
|
+
fs.mkdirSync(scopesDir, { recursive: true });
|
|
26
|
+
|
|
27
|
+
// Write the default workflow config that the constructor reads
|
|
28
|
+
fs.writeFileSync(defaultConfigPath, JSON.stringify(DEFAULT_CONFIG));
|
|
29
|
+
|
|
30
|
+
// Create scope directories for the default workflow
|
|
31
|
+
for (const list of DEFAULT_CONFIG.lists) {
|
|
32
|
+
if (list.hasDirectory) {
|
|
33
|
+
fs.mkdirSync(path.join(scopesDir, list.id), { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
engine = new WorkflowEngine(DEFAULT_CONFIG);
|
|
38
|
+
service = new WorkflowService(configDir, engine, scopesDir, defaultConfigPath);
|
|
39
|
+
service.setSocketServer(createMockEmitter() as any);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// ─── validate() ──────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
describe('validate()', () => {
|
|
49
|
+
it('passes for valid config', () => {
|
|
50
|
+
const result = service.validate(DEFAULT_CONFIG);
|
|
51
|
+
expect(result.valid).toBe(true);
|
|
52
|
+
expect(result.errors).toHaveLength(0);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('fails for invalid config shape', () => {
|
|
56
|
+
const result = service.validate({ version: 2 } as any);
|
|
57
|
+
expect(result.valid).toBe(false);
|
|
58
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('fails for duplicate list IDs', () => {
|
|
62
|
+
const config = {
|
|
63
|
+
...MINIMAL_CONFIG,
|
|
64
|
+
lists: [
|
|
65
|
+
...MINIMAL_CONFIG.lists,
|
|
66
|
+
{ ...MINIMAL_CONFIG.lists[0] }, // duplicate 'todo'
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
const result = service.validate(config);
|
|
70
|
+
expect(result.valid).toBe(false);
|
|
71
|
+
expect(result.errors.some(e => e.toLowerCase().includes('duplicate'))).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('fails for edges referencing non-existent lists', () => {
|
|
75
|
+
const config = {
|
|
76
|
+
...MINIMAL_CONFIG,
|
|
77
|
+
edges: [
|
|
78
|
+
{ from: 'todo', to: 'nonexistent', direction: 'forward' as const, command: null, confirmLevel: 'quick' as const, label: 'X', description: 'X' },
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
const result = service.validate(config);
|
|
82
|
+
expect(result.valid).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('fails for zero entry points', () => {
|
|
86
|
+
const config = {
|
|
87
|
+
...MINIMAL_CONFIG,
|
|
88
|
+
lists: MINIMAL_CONFIG.lists.map(l => ({ ...l, isEntryPoint: false })),
|
|
89
|
+
};
|
|
90
|
+
const result = service.validate(config);
|
|
91
|
+
expect(result.valid).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// ─── getActive() / updateActive() ────────────────────────
|
|
96
|
+
|
|
97
|
+
describe('getActive()', () => {
|
|
98
|
+
it('returns active workflow config', () => {
|
|
99
|
+
const config = service.getActive();
|
|
100
|
+
expect(config.name).toBe(DEFAULT_CONFIG.name);
|
|
101
|
+
expect(config.version).toBe(1);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('strips _defaultDigest from returned config', () => {
|
|
105
|
+
const config = service.getActive() as any;
|
|
106
|
+
expect(config._defaultDigest).toBeUndefined();
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('updateActive()', () => {
|
|
111
|
+
it('updates config and returns valid result', () => {
|
|
112
|
+
const result = service.updateActive(DEFAULT_CONFIG);
|
|
113
|
+
expect(result.valid).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('rejects invalid config', () => {
|
|
117
|
+
const result = service.updateActive({ version: 1, name: 'Bad', lists: [], edges: [] } as any);
|
|
118
|
+
expect(result.valid).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ─── Presets ──────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
describe('presets', () => {
|
|
125
|
+
it('savePreset() creates a preset file', () => {
|
|
126
|
+
service.savePreset('test-preset');
|
|
127
|
+
const presets = service.listPresets();
|
|
128
|
+
expect(presets.some(p => p.name === 'test-preset')).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('listPresets() returns all saved presets', () => {
|
|
132
|
+
service.savePreset('preset-a');
|
|
133
|
+
service.savePreset('preset-b');
|
|
134
|
+
const presets = service.listPresets();
|
|
135
|
+
expect(presets.length).toBeGreaterThanOrEqual(2);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('getPreset() loads preset by name', () => {
|
|
139
|
+
service.savePreset('loadable');
|
|
140
|
+
const config = service.getPreset('loadable');
|
|
141
|
+
expect(config.version).toBe(1);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('getPreset() throws for non-existent preset', () => {
|
|
145
|
+
expect(() => service.getPreset('nonexistent')).toThrow();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('deletePreset() removes preset file', () => {
|
|
149
|
+
service.savePreset('to-delete');
|
|
150
|
+
service.deletePreset('to-delete');
|
|
151
|
+
expect(() => service.getPreset('to-delete')).toThrow();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('deletePreset() blocks deletion of "default"', () => {
|
|
155
|
+
expect(() => service.deletePreset('default')).toThrow();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// ─── previewMigration() ──────────────────────────────────
|
|
160
|
+
|
|
161
|
+
describe('previewMigration()', () => {
|
|
162
|
+
it('detects removed lists', () => {
|
|
163
|
+
// Preview migrating from DEFAULT_CONFIG (7 lists) to MINIMAL_CONFIG (2 lists)
|
|
164
|
+
const plan = service.previewMigration(MINIMAL_CONFIG);
|
|
165
|
+
expect(plan.removedLists.length).toBeGreaterThan(0);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('returns no-op plan when configs have same lists', () => {
|
|
169
|
+
const plan = service.previewMigration(DEFAULT_CONFIG);
|
|
170
|
+
expect(plan.valid).toBe(true);
|
|
171
|
+
expect(plan.removedLists).toHaveLength(0);
|
|
172
|
+
expect(plan.orphanedScopes).toHaveLength(0);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('returns valid: false for invalid config', () => {
|
|
176
|
+
const plan = service.previewMigration({ version: 1, name: 'Bad', lists: [], edges: [] } as any);
|
|
177
|
+
expect(plan.valid).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// ─── getEngine() ─────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
describe('getEngine()', () => {
|
|
184
|
+
it('returns the active workflow engine', () => {
|
|
185
|
+
const e = service.getEngine();
|
|
186
|
+
expect(e).toBeDefined();
|
|
187
|
+
expect(e.getLists().length).toBeGreaterThan(0);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import type {
|
|
4
|
+
import type { Emitter } from '../project-emitter.js';
|
|
5
5
|
import type { WorkflowConfig } from '../../shared/workflow-config.js';
|
|
6
6
|
import { createLogger } from '../utils/logger.js';
|
|
7
7
|
|
|
@@ -53,7 +53,7 @@ export class WorkflowService {
|
|
|
53
53
|
private engine: WorkflowEngine;
|
|
54
54
|
private defaultConfigPath: string;
|
|
55
55
|
private manifestPath: string;
|
|
56
|
-
private io:
|
|
56
|
+
private io: Emitter | null = null;
|
|
57
57
|
|
|
58
58
|
constructor(configDir: string, engine: WorkflowEngine, scopesDir: string, defaultConfigPath: string) {
|
|
59
59
|
this.presetsDir = path.join(configDir, 'workflows');
|
|
@@ -81,13 +81,19 @@ export class WorkflowService {
|
|
|
81
81
|
this.engine.reload(defaultConfig);
|
|
82
82
|
fs.writeFileSync(this.manifestPath, this.engine.generateShellManifest(), 'utf-8');
|
|
83
83
|
} else {
|
|
84
|
-
const active = JSON.parse(fs.readFileSync(this.activeConfigPath, 'utf-8')) as WorkflowConfig & { _defaultDigest?: string };
|
|
84
|
+
const active = JSON.parse(fs.readFileSync(this.activeConfigPath, 'utf-8')) as WorkflowConfig & { _defaultDigest?: string; _customized?: boolean };
|
|
85
85
|
if (!active._defaultDigest) {
|
|
86
|
-
// Legacy file without digest marker. If content matches current default, stamp it.
|
|
87
|
-
// If different, it's user-customized — leave it alone.
|
|
88
86
|
if (configDigest(active) === currentDigest) {
|
|
87
|
+
// Exact content match — just stamp it with the digest
|
|
89
88
|
this.writeWithDigest(this.activeConfigPath, defaultConfig, currentDigest);
|
|
89
|
+
} else if (this.isLikelyOldDefault(active, defaultConfig)) {
|
|
90
|
+
// Same list structure but different edges — old default that needs updating
|
|
91
|
+
log.info('Upgrading legacy workflow config to current default');
|
|
92
|
+
this.writeWithDigest(this.activeConfigPath, defaultConfig, currentDigest);
|
|
93
|
+
this.engine.reload(defaultConfig);
|
|
94
|
+
fs.writeFileSync(this.manifestPath, this.engine.generateShellManifest(), 'utf-8');
|
|
90
95
|
}
|
|
96
|
+
// else: genuinely user-customized (different list structure), leave alone
|
|
91
97
|
} else if (active._defaultDigest !== currentDigest) {
|
|
92
98
|
// Bundled default changed since last sync — refresh + regenerate manifest
|
|
93
99
|
this.writeWithDigest(this.activeConfigPath, defaultConfig, currentDigest);
|
|
@@ -96,13 +102,17 @@ export class WorkflowService {
|
|
|
96
102
|
}
|
|
97
103
|
}
|
|
98
104
|
|
|
99
|
-
// Always keep the "default" preset in sync with the bundled default
|
|
105
|
+
// Always keep the "default" preset in sync with the bundled default.
|
|
106
|
+
// Copy the raw template bytes to preserve formatting (avoid hash drift from re-serialization).
|
|
100
107
|
const defaultPresetPath = path.join(this.presetsDir, 'default.json');
|
|
101
|
-
const
|
|
102
|
-
fs.
|
|
108
|
+
const templateBytes = fs.readFileSync(this.defaultConfigPath, 'utf-8');
|
|
109
|
+
const existing = fs.existsSync(defaultPresetPath) ? fs.readFileSync(defaultPresetPath, 'utf-8') : '';
|
|
110
|
+
if (existing !== templateBytes) {
|
|
111
|
+
fs.writeFileSync(defaultPresetPath, templateBytes);
|
|
112
|
+
}
|
|
103
113
|
}
|
|
104
114
|
|
|
105
|
-
setSocketServer(io:
|
|
115
|
+
setSocketServer(io: Emitter): void {
|
|
106
116
|
this.io = io;
|
|
107
117
|
}
|
|
108
118
|
|
|
@@ -110,6 +120,16 @@ export class WorkflowService {
|
|
|
110
120
|
return this.engine;
|
|
111
121
|
}
|
|
112
122
|
|
|
123
|
+
/** Check if a legacy config (no digest) is structurally an old default rather than
|
|
124
|
+
* a user customization. If it has the same list IDs in the same order, it was
|
|
125
|
+
* almost certainly seeded from an older version of the bundled default. */
|
|
126
|
+
private isLikelyOldDefault(active: WorkflowConfig, currentDefault: WorkflowConfig): boolean {
|
|
127
|
+
if (active.name !== currentDefault.name) return false;
|
|
128
|
+
const activeListIds = active.lists.map(l => l.id).join(',');
|
|
129
|
+
const defaultListIds = currentDefault.lists.map(l => l.id).join(',');
|
|
130
|
+
return activeListIds === defaultListIds;
|
|
131
|
+
}
|
|
132
|
+
|
|
113
133
|
// ─── Validation ──────────────────────────────────────
|
|
114
134
|
|
|
115
135
|
validate(config: WorkflowConfig): ValidationResult {
|