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.
Files changed (380) hide show
  1. package/bin/orbital.js +676 -53
  2. package/dist/assets/PrimitivesConfig-CrmQXYh4.js +32 -0
  3. package/dist/assets/QualityGates-BbasOsF3.js +21 -0
  4. package/dist/assets/SessionTimeline-CGeJsVvy.js +1 -0
  5. package/dist/assets/Settings-oiM496mc.js +12 -0
  6. package/dist/assets/SourceControl-B1fP2nJL.js +41 -0
  7. package/dist/assets/WorkflowVisualizer-CWLYf-f0.js +74 -0
  8. package/dist/assets/arrow-down-CPy85_J6.js +6 -0
  9. package/dist/assets/charts-DbDg0Psc.js +68 -0
  10. package/dist/assets/circle-x-Cwz6ZQDV.js +6 -0
  11. package/dist/assets/file-text-C46Xr65c.js +6 -0
  12. package/dist/assets/formatDistanceToNow-BMqsSP44.js +1 -0
  13. package/dist/assets/globe-Cn2yNZUD.js +6 -0
  14. package/dist/assets/index-Aj4sV8Al.css +1 -0
  15. package/dist/assets/index-Bc9dK3MW.js +354 -0
  16. package/dist/assets/key-OPaNTWJ5.js +6 -0
  17. package/dist/assets/minus-GMsbpKym.js +6 -0
  18. package/dist/assets/shield-DwAFkDYI.js +6 -0
  19. package/dist/assets/ui-BmsSg9jU.js +53 -0
  20. package/dist/assets/useWorkflowEditor-BJkTX_NR.js +16 -0
  21. package/dist/assets/{vendor-Dzv9lrRc.js → vendor-Bqt8AJn2.js} +1 -1
  22. package/dist/assets/zap-DfbUoOty.js +11 -0
  23. package/dist/favicon.svg +1 -0
  24. package/dist/index.html +6 -5
  25. package/dist/server/server/__tests__/data-routes.test.js +124 -0
  26. package/dist/server/server/__tests__/helpers/db.js +17 -0
  27. package/dist/server/server/__tests__/helpers/mock-emitter.js +8 -0
  28. package/dist/server/server/__tests__/scope-routes.test.js +137 -0
  29. package/dist/server/server/__tests__/sprint-routes.test.js +102 -0
  30. package/dist/server/server/__tests__/workflow-routes.test.js +107 -0
  31. package/dist/server/server/config-migrator.js +138 -0
  32. package/dist/server/server/config.js +17 -2
  33. package/dist/server/server/database.js +27 -12
  34. package/dist/server/server/global-config.js +143 -0
  35. package/dist/server/server/index.js +882 -252
  36. package/dist/server/server/init.js +579 -194
  37. package/dist/server/server/launch.js +29 -0
  38. package/dist/server/server/manifest-types.js +8 -0
  39. package/dist/server/server/manifest.js +454 -0
  40. package/dist/server/server/migrate-legacy.js +229 -0
  41. package/dist/server/server/parsers/event-parser.test.js +117 -0
  42. package/dist/server/server/parsers/scope-parser.js +74 -28
  43. package/dist/server/server/parsers/scope-parser.test.js +230 -0
  44. package/dist/server/server/project-context.js +255 -0
  45. package/dist/server/server/project-emitter.js +41 -0
  46. package/dist/server/server/project-manager.js +297 -0
  47. package/dist/server/server/routes/config-routes.js +1 -3
  48. package/dist/server/server/routes/data-routes.js +22 -110
  49. package/dist/server/server/routes/dispatch-routes.js +15 -9
  50. package/dist/server/server/routes/git-routes.js +74 -0
  51. package/dist/server/server/routes/manifest-routes.js +319 -0
  52. package/dist/server/server/routes/scope-routes.js +37 -23
  53. package/dist/server/server/routes/sync-routes.js +134 -0
  54. package/dist/server/server/routes/version-routes.js +1 -15
  55. package/dist/server/server/routes/workflow-routes.js +9 -3
  56. package/dist/server/server/schema.js +2 -0
  57. package/dist/server/server/services/batch-orchestrator.js +26 -16
  58. package/dist/server/server/services/claude-session-service.js +17 -14
  59. package/dist/server/server/services/deploy-service.test.js +119 -0
  60. package/dist/server/server/services/event-service.js +64 -1
  61. package/dist/server/server/services/event-service.test.js +191 -0
  62. package/dist/server/server/services/gate-service.test.js +105 -0
  63. package/dist/server/server/services/git-service.js +108 -4
  64. package/dist/server/server/services/github-service.js +110 -2
  65. package/dist/server/server/services/readiness-service.test.js +190 -0
  66. package/dist/server/server/services/scope-cache.js +5 -1
  67. package/dist/server/server/services/scope-cache.test.js +142 -0
  68. package/dist/server/server/services/scope-service.js +217 -126
  69. package/dist/server/server/services/scope-service.test.js +137 -0
  70. package/dist/server/server/services/sprint-orchestrator.js +7 -6
  71. package/dist/server/server/services/sprint-service.js +21 -1
  72. package/dist/server/server/services/sprint-service.test.js +238 -0
  73. package/dist/server/server/services/sync-service.js +434 -0
  74. package/dist/server/server/services/sync-types.js +2 -0
  75. package/dist/server/server/services/telemetry-service.js +143 -0
  76. package/dist/server/server/services/workflow-service.js +26 -5
  77. package/dist/server/server/services/workflow-service.test.js +159 -0
  78. package/dist/server/server/settings-sync.js +284 -0
  79. package/dist/server/server/update-planner.js +279 -0
  80. package/dist/server/server/utils/cc-hooks-parser.js +3 -0
  81. package/dist/server/server/utils/cc-hooks-parser.test.js +86 -0
  82. package/dist/server/server/utils/dispatch-utils.js +77 -20
  83. package/dist/server/server/utils/dispatch-utils.test.js +182 -0
  84. package/dist/server/server/utils/logger.js +37 -3
  85. package/dist/server/server/utils/package-info.js +30 -0
  86. package/dist/server/server/utils/route-helpers.js +10 -0
  87. package/dist/server/server/utils/terminal-launcher.js +79 -25
  88. package/dist/server/server/utils/worktree-manager.js +13 -4
  89. package/dist/server/server/validator.js +230 -0
  90. package/dist/server/server/watchers/global-watcher.js +63 -0
  91. package/dist/server/server/watchers/scope-watcher.js +27 -12
  92. package/dist/server/server/wizard/config-editor.js +237 -0
  93. package/dist/server/server/wizard/detect.js +96 -0
  94. package/dist/server/server/wizard/doctor.js +115 -0
  95. package/dist/server/server/wizard/index.js +155 -0
  96. package/dist/server/server/wizard/phases/confirm.js +39 -0
  97. package/dist/server/server/wizard/phases/project-setup.js +90 -0
  98. package/dist/server/server/wizard/phases/setup-wizard.js +66 -0
  99. package/dist/server/server/wizard/phases/welcome.js +35 -0
  100. package/dist/server/server/wizard/phases/workflow-setup.js +22 -0
  101. package/dist/server/server/wizard/types.js +29 -0
  102. package/dist/server/server/wizard/ui.js +74 -0
  103. package/dist/server/shared/__fixtures__/workflow-configs.js +75 -0
  104. package/dist/server/shared/default-workflow.json +65 -0
  105. package/dist/server/shared/onboarding-tour.test.js +81 -0
  106. package/dist/server/shared/project-colors.js +24 -0
  107. package/dist/server/shared/workflow-config.test.js +84 -0
  108. package/dist/server/shared/workflow-engine.test.js +302 -0
  109. package/dist/server/shared/workflow-normalizer.js +101 -0
  110. package/dist/server/shared/workflow-normalizer.test.js +100 -0
  111. package/dist/server/src/components/onboarding/tour-steps.js +84 -0
  112. package/package.json +20 -15
  113. package/schemas/orbital.config.schema.json +16 -1
  114. package/scripts/postinstall.js +55 -7
  115. package/server/__tests__/data-routes.test.ts +149 -0
  116. package/server/__tests__/helpers/db.ts +19 -0
  117. package/server/__tests__/helpers/mock-emitter.ts +10 -0
  118. package/server/__tests__/scope-routes.test.ts +157 -0
  119. package/server/__tests__/sprint-routes.test.ts +118 -0
  120. package/server/__tests__/workflow-routes.test.ts +120 -0
  121. package/server/config-migrator.ts +163 -0
  122. package/server/config.ts +26 -2
  123. package/server/database.ts +35 -18
  124. package/server/global-config.ts +200 -0
  125. package/server/index.ts +975 -287
  126. package/server/init.ts +625 -182
  127. package/server/launch.ts +32 -0
  128. package/server/manifest-types.ts +145 -0
  129. package/server/manifest.ts +494 -0
  130. package/server/migrate-legacy.ts +290 -0
  131. package/server/parsers/event-parser.test.ts +135 -0
  132. package/server/parsers/scope-parser.test.ts +270 -0
  133. package/server/parsers/scope-parser.ts +79 -31
  134. package/server/project-context.ts +309 -0
  135. package/server/project-emitter.ts +50 -0
  136. package/server/project-manager.ts +369 -0
  137. package/server/routes/config-routes.ts +3 -5
  138. package/server/routes/data-routes.ts +28 -141
  139. package/server/routes/dispatch-routes.ts +19 -11
  140. package/server/routes/git-routes.ts +77 -0
  141. package/server/routes/manifest-routes.ts +388 -0
  142. package/server/routes/scope-routes.ts +29 -25
  143. package/server/routes/sync-routes.ts +175 -0
  144. package/server/routes/version-routes.ts +1 -16
  145. package/server/routes/workflow-routes.ts +9 -3
  146. package/server/schema.ts +2 -0
  147. package/server/services/batch-orchestrator.ts +24 -16
  148. package/server/services/claude-session-service.ts +16 -14
  149. package/server/services/deploy-service.test.ts +145 -0
  150. package/server/services/deploy-service.ts +2 -2
  151. package/server/services/event-service.test.ts +242 -0
  152. package/server/services/event-service.ts +92 -3
  153. package/server/services/gate-service.test.ts +131 -0
  154. package/server/services/gate-service.ts +2 -2
  155. package/server/services/git-service.ts +137 -4
  156. package/server/services/github-service.ts +120 -2
  157. package/server/services/readiness-service.test.ts +217 -0
  158. package/server/services/scope-cache.test.ts +167 -0
  159. package/server/services/scope-cache.ts +4 -1
  160. package/server/services/scope-service.test.ts +169 -0
  161. package/server/services/scope-service.ts +220 -126
  162. package/server/services/sprint-orchestrator.ts +7 -7
  163. package/server/services/sprint-service.test.ts +271 -0
  164. package/server/services/sprint-service.ts +27 -3
  165. package/server/services/sync-service.ts +482 -0
  166. package/server/services/sync-types.ts +77 -0
  167. package/server/services/telemetry-service.ts +195 -0
  168. package/server/services/workflow-service.test.ts +190 -0
  169. package/server/services/workflow-service.ts +29 -9
  170. package/server/settings-sync.ts +359 -0
  171. package/server/update-planner.ts +346 -0
  172. package/server/utils/cc-hooks-parser.test.ts +96 -0
  173. package/server/utils/cc-hooks-parser.ts +4 -0
  174. package/server/utils/dispatch-utils.test.ts +245 -0
  175. package/server/utils/dispatch-utils.ts +97 -27
  176. package/server/utils/logger.ts +40 -3
  177. package/server/utils/package-info.ts +32 -0
  178. package/server/utils/route-helpers.ts +12 -0
  179. package/server/utils/terminal-launcher.ts +85 -25
  180. package/server/utils/worktree-manager.ts +9 -4
  181. package/server/validator.ts +270 -0
  182. package/server/watchers/global-watcher.ts +77 -0
  183. package/server/watchers/scope-watcher.ts +21 -9
  184. package/server/wizard/config-editor.ts +248 -0
  185. package/server/wizard/detect.ts +104 -0
  186. package/server/wizard/doctor.ts +114 -0
  187. package/server/wizard/index.ts +187 -0
  188. package/server/wizard/phases/confirm.ts +45 -0
  189. package/server/wizard/phases/project-setup.ts +106 -0
  190. package/server/wizard/phases/setup-wizard.ts +78 -0
  191. package/server/wizard/phases/welcome.ts +43 -0
  192. package/server/wizard/phases/workflow-setup.ts +28 -0
  193. package/server/wizard/types.ts +56 -0
  194. package/server/wizard/ui.ts +93 -0
  195. package/shared/__fixtures__/workflow-configs.ts +80 -0
  196. package/shared/default-workflow.json +65 -0
  197. package/shared/onboarding-tour.test.ts +94 -0
  198. package/shared/project-colors.ts +24 -0
  199. package/shared/workflow-config.test.ts +111 -0
  200. package/shared/workflow-config.ts +7 -0
  201. package/shared/workflow-engine.test.ts +388 -0
  202. package/shared/workflow-normalizer.test.ts +119 -0
  203. package/shared/workflow-normalizer.ts +118 -0
  204. package/templates/hooks/end-session.sh +3 -1
  205. package/templates/hooks/orbital-emit.sh +2 -2
  206. package/templates/hooks/orbital-report-deploy.sh +4 -4
  207. package/templates/hooks/orbital-report-gates.sh +4 -4
  208. package/templates/hooks/orbital-scope-update.sh +1 -1
  209. package/templates/hooks/scope-create-cleanup.sh +2 -2
  210. package/templates/hooks/scope-create-gate.sh +0 -1
  211. package/templates/hooks/scope-helpers.sh +18 -0
  212. package/templates/hooks/scope-prepare.sh +66 -11
  213. package/templates/migrations/renames.json +1 -0
  214. package/templates/orbital.config.json +7 -2
  215. package/templates/settings-hooks.json +1 -1
  216. package/templates/skills/git-commit/SKILL.md +9 -4
  217. package/templates/skills/git-dev/SKILL.md +8 -3
  218. package/templates/skills/git-main/SKILL.md +8 -2
  219. package/templates/skills/git-production/SKILL.md +6 -2
  220. package/templates/skills/git-staging/SKILL.md +8 -3
  221. package/templates/skills/scope-create/SKILL.md +17 -3
  222. package/templates/skills/scope-fix-review/SKILL.md +6 -3
  223. package/templates/skills/scope-implement/SKILL.md +4 -1
  224. package/templates/skills/scope-post-review/SKILL.md +63 -5
  225. package/templates/skills/scope-pre-review/SKILL.md +5 -2
  226. package/templates/skills/scope-verify/SKILL.md +5 -3
  227. package/templates/skills/test-code-review/SKILL.md +41 -33
  228. package/templates/skills/test-scaffold/SKILL.md +222 -0
  229. package/dist/assets/WorkflowVisualizer-BZ21PIIF.js +0 -84
  230. package/dist/assets/charts-D__PA1zp.js +0 -72
  231. package/dist/assets/index-D1G6i0nS.css +0 -1
  232. package/dist/assets/index-DpItvKpf.js +0 -419
  233. package/dist/assets/ui-BvF022GT.js +0 -53
  234. package/index.html +0 -15
  235. package/postcss.config.js +0 -6
  236. package/src/App.tsx +0 -33
  237. package/src/components/AgentBadge.tsx +0 -40
  238. package/src/components/BatchPreflightModal.tsx +0 -115
  239. package/src/components/CardDisplayToggle.tsx +0 -74
  240. package/src/components/ColumnHeaderActions.tsx +0 -55
  241. package/src/components/ColumnMenu.tsx +0 -99
  242. package/src/components/DeployHistory.tsx +0 -141
  243. package/src/components/DispatchModal.tsx +0 -164
  244. package/src/components/DispatchPopover.tsx +0 -139
  245. package/src/components/DragOverlay.tsx +0 -25
  246. package/src/components/DriftSidebar.tsx +0 -140
  247. package/src/components/EnvironmentStrip.tsx +0 -88
  248. package/src/components/ErrorBoundary.tsx +0 -62
  249. package/src/components/FilterChip.tsx +0 -105
  250. package/src/components/GateIndicator.tsx +0 -33
  251. package/src/components/IdeaDetailModal.tsx +0 -190
  252. package/src/components/IdeaFormDialog.tsx +0 -113
  253. package/src/components/KanbanColumn.tsx +0 -201
  254. package/src/components/MarkdownRenderer.tsx +0 -114
  255. package/src/components/NeonGrid.tsx +0 -128
  256. package/src/components/PromotionQueue.tsx +0 -89
  257. package/src/components/ScopeCard.tsx +0 -234
  258. package/src/components/ScopeDetailModal.tsx +0 -255
  259. package/src/components/ScopeFilterBar.tsx +0 -152
  260. package/src/components/SearchInput.tsx +0 -102
  261. package/src/components/SessionPanel.tsx +0 -335
  262. package/src/components/SprintContainer.tsx +0 -303
  263. package/src/components/SprintDependencyDialog.tsx +0 -78
  264. package/src/components/SprintPreflightModal.tsx +0 -138
  265. package/src/components/StatusBar.tsx +0 -168
  266. package/src/components/SwimCell.tsx +0 -67
  267. package/src/components/SwimLaneRow.tsx +0 -94
  268. package/src/components/SwimlaneBoardView.tsx +0 -108
  269. package/src/components/VersionBadge.tsx +0 -139
  270. package/src/components/ViewModeSelector.tsx +0 -114
  271. package/src/components/config/AgentChip.tsx +0 -53
  272. package/src/components/config/AgentCreateDialog.tsx +0 -321
  273. package/src/components/config/AgentEditor.tsx +0 -175
  274. package/src/components/config/DirectoryTree.tsx +0 -582
  275. package/src/components/config/FileEditor.tsx +0 -550
  276. package/src/components/config/HookChip.tsx +0 -50
  277. package/src/components/config/StageCard.tsx +0 -198
  278. package/src/components/config/TransitionZone.tsx +0 -173
  279. package/src/components/config/UnifiedWorkflowPipeline.tsx +0 -216
  280. package/src/components/config/WorkflowPipeline.tsx +0 -161
  281. package/src/components/source-control/BranchList.tsx +0 -93
  282. package/src/components/source-control/BranchPanel.tsx +0 -105
  283. package/src/components/source-control/CommitLog.tsx +0 -100
  284. package/src/components/source-control/CommitRow.tsx +0 -47
  285. package/src/components/source-control/GitHubPanel.tsx +0 -110
  286. package/src/components/source-control/GitHubSetupGuide.tsx +0 -52
  287. package/src/components/source-control/GitOverviewBar.tsx +0 -101
  288. package/src/components/source-control/PullRequestList.tsx +0 -69
  289. package/src/components/source-control/WorktreeList.tsx +0 -80
  290. package/src/components/ui/badge.tsx +0 -41
  291. package/src/components/ui/button.tsx +0 -55
  292. package/src/components/ui/card.tsx +0 -78
  293. package/src/components/ui/dialog.tsx +0 -94
  294. package/src/components/ui/popover.tsx +0 -33
  295. package/src/components/ui/scroll-area.tsx +0 -54
  296. package/src/components/ui/separator.tsx +0 -28
  297. package/src/components/ui/tabs.tsx +0 -52
  298. package/src/components/ui/toggle-switch.tsx +0 -35
  299. package/src/components/ui/tooltip.tsx +0 -27
  300. package/src/components/workflow/AddEdgeDialog.tsx +0 -217
  301. package/src/components/workflow/AddListDialog.tsx +0 -201
  302. package/src/components/workflow/ChecklistEditor.tsx +0 -239
  303. package/src/components/workflow/CommandPrefixManager.tsx +0 -118
  304. package/src/components/workflow/ConfigSettingsPanel.tsx +0 -189
  305. package/src/components/workflow/DirectionSelector.tsx +0 -133
  306. package/src/components/workflow/DispatchConfigPanel.tsx +0 -180
  307. package/src/components/workflow/EdgeDetailPanel.tsx +0 -236
  308. package/src/components/workflow/EdgePropertyEditor.tsx +0 -251
  309. package/src/components/workflow/EditToolbar.tsx +0 -138
  310. package/src/components/workflow/HookDetailPanel.tsx +0 -250
  311. package/src/components/workflow/HookExecutionLog.tsx +0 -24
  312. package/src/components/workflow/HookSourceModal.tsx +0 -129
  313. package/src/components/workflow/HooksDashboard.tsx +0 -363
  314. package/src/components/workflow/ListPropertyEditor.tsx +0 -251
  315. package/src/components/workflow/MigrationPreviewDialog.tsx +0 -237
  316. package/src/components/workflow/MovementRulesPanel.tsx +0 -188
  317. package/src/components/workflow/NodeDetailPanel.tsx +0 -245
  318. package/src/components/workflow/PresetSelector.tsx +0 -414
  319. package/src/components/workflow/SkillCommandBuilder.tsx +0 -174
  320. package/src/components/workflow/WorkflowEdgeComponent.tsx +0 -145
  321. package/src/components/workflow/WorkflowNode.tsx +0 -147
  322. package/src/components/workflow/graphLayout.ts +0 -186
  323. package/src/components/workflow/mergeHooks.ts +0 -85
  324. package/src/components/workflow/useEditHistory.ts +0 -88
  325. package/src/components/workflow/useWorkflowEditor.ts +0 -262
  326. package/src/components/workflow/validateConfig.ts +0 -70
  327. package/src/hooks/useActiveDispatches.ts +0 -198
  328. package/src/hooks/useBoardSettings.ts +0 -170
  329. package/src/hooks/useCardDisplay.ts +0 -57
  330. package/src/hooks/useCcHooks.ts +0 -24
  331. package/src/hooks/useConfigTree.ts +0 -51
  332. package/src/hooks/useEnforcementRules.ts +0 -46
  333. package/src/hooks/useEvents.ts +0 -59
  334. package/src/hooks/useFileEditor.ts +0 -165
  335. package/src/hooks/useGates.ts +0 -57
  336. package/src/hooks/useIdeaActions.ts +0 -53
  337. package/src/hooks/useKanbanDnd.ts +0 -410
  338. package/src/hooks/useOrbitalConfig.ts +0 -54
  339. package/src/hooks/usePipeline.ts +0 -47
  340. package/src/hooks/usePipelineData.ts +0 -338
  341. package/src/hooks/useReconnect.ts +0 -25
  342. package/src/hooks/useScopeFilters.ts +0 -125
  343. package/src/hooks/useScopeSessions.ts +0 -44
  344. package/src/hooks/useScopes.ts +0 -67
  345. package/src/hooks/useSearch.ts +0 -67
  346. package/src/hooks/useSettings.tsx +0 -187
  347. package/src/hooks/useSocket.ts +0 -25
  348. package/src/hooks/useSourceControl.ts +0 -105
  349. package/src/hooks/useSprintPreflight.ts +0 -55
  350. package/src/hooks/useSprints.ts +0 -154
  351. package/src/hooks/useStatusBarHighlight.ts +0 -18
  352. package/src/hooks/useSwimlaneBoardSettings.ts +0 -104
  353. package/src/hooks/useTheme.ts +0 -9
  354. package/src/hooks/useTransitionReadiness.ts +0 -53
  355. package/src/hooks/useVersion.ts +0 -155
  356. package/src/hooks/useViolations.ts +0 -65
  357. package/src/hooks/useWorkflow.tsx +0 -125
  358. package/src/hooks/useZoomModifier.ts +0 -19
  359. package/src/index.css +0 -797
  360. package/src/layouts/DashboardLayout.tsx +0 -113
  361. package/src/lib/collisionDetection.ts +0 -20
  362. package/src/lib/scope-fields.ts +0 -61
  363. package/src/lib/swimlane.ts +0 -146
  364. package/src/lib/utils.ts +0 -15
  365. package/src/main.tsx +0 -19
  366. package/src/socket.ts +0 -11
  367. package/src/types/index.ts +0 -497
  368. package/src/views/AgentFeed.tsx +0 -339
  369. package/src/views/DeployPipeline.tsx +0 -59
  370. package/src/views/EnforcementView.tsx +0 -378
  371. package/src/views/PrimitivesConfig.tsx +0 -500
  372. package/src/views/QualityGates.tsx +0 -1012
  373. package/src/views/ScopeBoard.tsx +0 -454
  374. package/src/views/SessionTimeline.tsx +0 -516
  375. package/src/views/Settings.tsx +0 -183
  376. package/src/views/SourceControl.tsx +0 -95
  377. package/src/views/WorkflowVisualizer.tsx +0 -382
  378. package/tailwind.config.js +0 -161
  379. package/tsconfig.json +0 -25
  380. 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 { Server } from 'socket.io';
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: Server | null = null;
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 preset = { _preset: { name: 'default', savedAt: new Date().toISOString(), savedFrom: 'bundled' }, ...defaultConfig };
102
- fs.writeFileSync(defaultPresetPath, JSON.stringify(preset, null, 2));
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: Server): void {
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 {