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
@@ -5,6 +5,14 @@
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
  import { fileURLToPath } from 'url';
8
+ import { GLOBAL_PRIMITIVES_DIR, ensureOrbitalHome, unregisterProject } from './global-config.js';
9
+ import { loadManifest, saveManifest, createManifest, hashFile, buildTemplateInventory, templateFileRecord, userFileRecord, isSelfHosting, getSymlinkTarget, createBackup, refreshFileStatuses, reverseRemapPath, safeBackupFile, safeCopyTemplate, } from './manifest.js';
10
+ import { needsLegacyMigration, migrateFromLegacy } from './migrate-legacy.js';
11
+ import { computeUpdatePlan, loadRenameMap, formatPlan, getFilesToBackup } from './update-planner.js';
12
+ import { syncSettingsHooks, removeAllOrbitalHooks, getTemplateChecksum } from './settings-sync.js';
13
+ import { migrateConfig } from './config-migrator.js';
14
+ import { validate, formatValidationReport } from './validator.js';
15
+ import { getPackageVersion as _getPackageVersionUtil } from './utils/package-info.js';
8
16
  const __filename = fileURLToPath(import.meta.url);
9
17
  const __dirname = path.dirname(__filename);
10
18
  // Walk up from __dirname until we find the package root (identified by templates/).
@@ -431,13 +439,116 @@ function cleanEmptyDirs(dir) {
431
439
  fs.rmdirSync(dir);
432
440
  }
433
441
  }
442
+ // ─── Manifest Building ──────────────────────────────────────
443
+ /** Build a manifest by scanning .claude/ and classifying files against templates. */
444
+ function buildInitManifest(projectRoot, claudeDir, preset) {
445
+ const selfHosting = isSelfHosting(projectRoot);
446
+ const templateInventory = buildTemplateInventory(TEMPLATES_DIR);
447
+ const pkg = getPackageVersion();
448
+ const manifest = createManifest(pkg, preset);
449
+ // Classify all template files
450
+ for (const [relPath, templateHash] of templateInventory) {
451
+ const absPath = path.join(claudeDir, relPath);
452
+ if (selfHosting) {
453
+ const symlinkTarget = getSymlinkTarget(claudeDir, relPath);
454
+ if (symlinkTarget) {
455
+ manifest.files[relPath] = templateFileRecord(templateHash, symlinkTarget);
456
+ continue;
457
+ }
458
+ }
459
+ if (fs.existsSync(absPath)) {
460
+ const fileHash = hashFile(absPath);
461
+ if (fileHash === templateHash) {
462
+ manifest.files[relPath] = templateFileRecord(templateHash);
463
+ }
464
+ else {
465
+ // File exists but doesn't match template — classify as outdated
466
+ // (no prior install to compare against, so we can't know if user edited it)
467
+ manifest.files[relPath] = {
468
+ origin: 'template',
469
+ status: 'outdated',
470
+ templateHash,
471
+ installedHash: fileHash,
472
+ };
473
+ }
474
+ }
475
+ // File doesn't exist — it may not have been copied (e.g. skipped on non-force init)
476
+ }
477
+ // Scan managed directories for user-created files
478
+ for (const dir of ['hooks', 'skills', 'agents']) {
479
+ const dirPath = path.join(claudeDir, dir);
480
+ if (!fs.existsSync(dirPath))
481
+ continue;
482
+ walkDirForManifest(dirPath, dir, (relPath, absPath) => {
483
+ if (manifest.files[relPath])
484
+ return; // Already classified
485
+ const fileHash = hashFile(absPath);
486
+ manifest.files[relPath] = userFileRecord(fileHash);
487
+ });
488
+ }
489
+ // Record settings hooks checksum
490
+ const settingsHooksPath = path.join(TEMPLATES_DIR, 'settings-hooks.json');
491
+ if (fs.existsSync(settingsHooksPath)) {
492
+ manifest.settingsHooksChecksum = getTemplateChecksum(settingsHooksPath);
493
+ }
494
+ // Record gitignore entries
495
+ manifest.gitignoreEntries = [
496
+ 'scopes/',
497
+ '.claude/orbital/',
498
+ '.claude/orbital-events/',
499
+ '.claude/config/workflow-manifest.sh',
500
+ ];
501
+ return manifest;
502
+ }
503
+ function getPackageVersion() {
504
+ return _getPackageVersionUtil(PACKAGE_ROOT);
505
+ }
506
+ /** Recursively walk a directory for manifest building. */
507
+ function walkDirForManifest(dirPath, prefix, fn) {
508
+ if (!fs.existsSync(dirPath))
509
+ return;
510
+ for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
511
+ if (entry.name.startsWith('.'))
512
+ continue;
513
+ const absPath = path.join(dirPath, entry.name);
514
+ const relPath = `${prefix}/${entry.name}`;
515
+ // Follow symlinks: use stat() to check if target is a directory
516
+ const stat = fs.statSync(absPath);
517
+ if (stat.isDirectory()) {
518
+ walkDirForManifest(absPath, relPath, fn);
519
+ }
520
+ else {
521
+ fn(relPath, absPath);
522
+ }
523
+ }
524
+ }
434
525
  // ─── Exports ─────────────────────────────────────────────────
526
+ /** Seed ~/.orbital/primitives/ from package templates. Called by init, update, and server startup. */
527
+ export function seedGlobalPrimitives() {
528
+ ensureOrbitalHome();
529
+ for (const type of ['hooks', 'skills', 'agents']) {
530
+ const src = path.join(TEMPLATES_DIR, type);
531
+ const dest = path.join(GLOBAL_PRIMITIVES_DIR, type);
532
+ if (fs.existsSync(src)) {
533
+ copyDirSync(src, dest, { overwrite: true });
534
+ }
535
+ }
536
+ }
435
537
  export { TEMPLATES_DIR, ensureDir };
538
+ // Re-export manifest utilities for CLI access via loadSharedModule()
539
+ export { loadManifest, saveManifest, hashFile, buildTemplateInventory, refreshFileStatuses, summarizeManifest } from './manifest.js';
540
+ export { validate, formatValidationReport } from './validator.js';
436
541
  export function runInit(projectRoot, options = {}) {
437
542
  const force = options.force ?? false;
543
+ const quiet = options.quiet ?? false;
544
+ const selectedPreset = options.preset || 'default';
438
545
  const claudeDir = path.join(projectRoot, '.claude');
439
- console.log(`\nOrbital Command — init`);
440
- console.log(`Project root: ${projectRoot}\n`);
546
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
547
+ const log = quiet ? (..._args) => { } : console.log.bind(console);
548
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
549
+ const warn = quiet ? (..._args) => { } : console.warn.bind(console);
550
+ log(`\nOrbital Command — init`);
551
+ log(`Project root: ${projectRoot}\n`);
441
552
  // 1. Create directories
442
553
  const dirs = [
443
554
  path.join(claudeDir, 'orbital-events'),
@@ -447,24 +558,26 @@ export function runInit(projectRoot, options = {}) {
447
558
  ];
448
559
  for (const dir of dirs) {
449
560
  const wasCreated = ensureDir(dir);
450
- console.log(` ${wasCreated ? 'Created' : 'Exists '} ${path.relative(projectRoot, dir)}/`);
561
+ log(` ${wasCreated ? 'Created' : 'Exists '} ${path.relative(projectRoot, dir)}/`);
451
562
  }
452
- // 1b. Create scopes/ subdirectories from the default workflow preset
563
+ // 1b. Create scopes/ subdirectories from the selected workflow preset
564
+ const selectedPresetPath = path.join(TEMPLATES_DIR, 'presets', `${selectedPreset}.json`);
453
565
  const defaultPresetPath = path.join(TEMPLATES_DIR, 'presets', 'default.json');
566
+ const presetPath = fs.existsSync(selectedPresetPath) ? selectedPresetPath : defaultPresetPath;
454
567
  let scopeDirs = ['icebox'];
455
568
  try {
456
- const preset = JSON.parse(fs.readFileSync(defaultPresetPath, 'utf8'));
569
+ const preset = JSON.parse(fs.readFileSync(presetPath, 'utf8'));
457
570
  if (preset.lists && Array.isArray(preset.lists)) {
458
571
  scopeDirs = preset.lists.filter((l) => l.hasDirectory).map((l) => l.id);
459
572
  }
460
573
  }
461
574
  catch {
462
- console.warn(' Warning: could not load default preset, creating scopes/icebox/ only');
575
+ warn(' Warning: could not load preset, creating scopes/icebox/ only');
463
576
  }
464
577
  for (const dirId of scopeDirs) {
465
578
  const scopeDir = path.join(projectRoot, 'scopes', dirId);
466
579
  const wasCreated = ensureDir(scopeDir);
467
- console.log(` ${wasCreated ? 'Created' : 'Exists '} scopes/${dirId}/`);
580
+ log(` ${wasCreated ? 'Created' : 'Exists '} scopes/${dirId}/`);
468
581
  }
469
582
  // 1c. Copy scope template
470
583
  const scopeTemplateSrc = path.join(TEMPLATES_DIR, 'scopes', '_template.md');
@@ -472,10 +585,10 @@ export function runInit(projectRoot, options = {}) {
472
585
  if (fs.existsSync(scopeTemplateSrc)) {
473
586
  if (force || !fs.existsSync(scopeTemplateDest)) {
474
587
  fs.copyFileSync(scopeTemplateSrc, scopeTemplateDest);
475
- console.log(` ${force ? 'Reset ' : 'Created'} scopes/_template.md`);
588
+ log(` ${force ? 'Reset ' : 'Created'} scopes/_template.md`);
476
589
  }
477
590
  else {
478
- console.log(` Exists scopes/_template.md`);
591
+ log(` Exists scopes/_template.md`);
479
592
  }
480
593
  }
481
594
  // 2. Copy orbital.config.json template
@@ -485,85 +598,114 @@ export function runInit(projectRoot, options = {}) {
485
598
  if (configIsNew) {
486
599
  if (fs.existsSync(configSrc)) {
487
600
  fs.copyFileSync(configSrc, configDest);
488
- console.log(` Created .claude/orbital.config.json`);
601
+ // Apply wizard-collected or auto-detected values
602
+ try {
603
+ const config = JSON.parse(fs.readFileSync(configDest, 'utf8'));
604
+ config.projectName = options.projectName || path.basename(projectRoot)
605
+ .replace(/[-_]+/g, ' ')
606
+ .replace(/\b\w/g, c => c.toUpperCase());
607
+ if (options.serverPort)
608
+ config.serverPort = options.serverPort;
609
+ if (options.clientPort)
610
+ config.clientPort = options.clientPort;
611
+ fs.writeFileSync(configDest, JSON.stringify(config, null, 2) + '\n', 'utf8');
612
+ }
613
+ catch { /* leave template default */ }
614
+ log(` Created .claude/orbital.config.json`);
489
615
  }
490
616
  else {
491
617
  const defaultConfig = {
492
- serverPort: 4444,
493
- clientPort: 4445,
494
- projectName: path.basename(projectRoot),
618
+ serverPort: options.serverPort || 4444,
619
+ clientPort: options.clientPort || 4445,
620
+ projectName: options.projectName || path.basename(projectRoot),
495
621
  };
496
622
  fs.writeFileSync(configDest, JSON.stringify(defaultConfig, null, 2) + '\n', 'utf8');
497
- console.log(` Created .claude/orbital.config.json (default)`);
623
+ log(` Created .claude/orbital.config.json (default)`);
498
624
  }
499
- // Auto-detect project commands from package.json
500
- const pkgJsonPath = path.join(projectRoot, 'package.json');
501
- if (fs.existsSync(pkgJsonPath)) {
625
+ // Apply wizard-provided commands or auto-detect from package.json
626
+ if (options.commands) {
502
627
  try {
503
- const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
504
- const scripts = pkg.scripts || {};
505
628
  const config = JSON.parse(fs.readFileSync(configDest, 'utf8'));
506
629
  if (!config.commands)
507
630
  config.commands = {};
508
- let detected = 0;
509
- if (scripts.typecheck || scripts['type-check']) {
510
- config.commands.typeCheck = `npm run ${scripts.typecheck ? 'typecheck' : 'type-check'}`;
511
- detected++;
512
- }
513
- if (scripts.lint) {
514
- config.commands.lint = 'npm run lint';
515
- detected++;
516
- }
517
- if (scripts.build) {
518
- config.commands.build = 'npm run build';
519
- detected++;
520
- }
521
- if (scripts.test) {
522
- config.commands.test = 'npm run test';
523
- detected++;
524
- }
525
- if (detected > 0) {
526
- fs.writeFileSync(configDest, JSON.stringify(config, null, 2) + '\n', 'utf8');
527
- console.log(` Detected ${detected} project command(s) from package.json`);
528
- }
631
+ Object.assign(config.commands, options.commands);
632
+ const commandCount = Object.values(options.commands).filter(v => v !== null).length;
633
+ fs.writeFileSync(configDest, JSON.stringify(config, null, 2) + '\n', 'utf8');
634
+ if (commandCount > 0)
635
+ log(` Applied ${commandCount} project command(s)`);
529
636
  }
530
637
  catch { /* leave defaults */ }
531
638
  }
639
+ else {
640
+ // Auto-detect project commands from package.json
641
+ const pkgJsonPath = path.join(projectRoot, 'package.json');
642
+ if (fs.existsSync(pkgJsonPath)) {
643
+ try {
644
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
645
+ const scripts = pkg.scripts || {};
646
+ const config = JSON.parse(fs.readFileSync(configDest, 'utf8'));
647
+ if (!config.commands)
648
+ config.commands = {};
649
+ let detected = 0;
650
+ if (scripts.typecheck || scripts['type-check']) {
651
+ config.commands.typeCheck = `npm run ${scripts.typecheck ? 'typecheck' : 'type-check'}`;
652
+ detected++;
653
+ }
654
+ if (scripts.lint) {
655
+ config.commands.lint = 'npm run lint';
656
+ detected++;
657
+ }
658
+ if (scripts.build) {
659
+ config.commands.build = 'npm run build';
660
+ detected++;
661
+ }
662
+ if (scripts.test) {
663
+ config.commands.test = 'npm run test';
664
+ detected++;
665
+ }
666
+ if (detected > 0) {
667
+ fs.writeFileSync(configDest, JSON.stringify(config, null, 2) + '\n', 'utf8');
668
+ log(` Detected ${detected} project command(s) from package.json`);
669
+ }
670
+ }
671
+ catch { /* leave defaults */ }
672
+ }
673
+ }
532
674
  }
533
675
  else {
534
- console.log(` Exists .claude/orbital.config.json`);
676
+ log(` Exists .claude/orbital.config.json`);
535
677
  }
536
678
  // 3. Copy hooks
537
- console.log('');
679
+ log('');
538
680
  const hooksSrc = path.join(TEMPLATES_DIR, 'hooks');
539
681
  const hooksDest = path.join(claudeDir, 'hooks');
540
682
  if (force) {
541
683
  const pruned = pruneStaleEntries(hooksSrc, hooksDest);
542
684
  if (pruned > 0)
543
- console.log(` Pruned ${pruned} stale hook entries`);
685
+ log(` Pruned ${pruned} stale hook entries`);
544
686
  }
545
687
  const hooksResult = copyDirSync(hooksSrc, hooksDest, { overwrite: force });
546
- console.log(` Hooks ${hooksResult.created.length} copied, ${hooksResult.skipped.length} skipped`);
688
+ log(` Hooks ${hooksResult.created.length} copied, ${hooksResult.skipped.length} skipped`);
547
689
  // 4. Copy skills
548
690
  const skillsSrc = path.join(TEMPLATES_DIR, 'skills');
549
691
  const skillsDest = path.join(claudeDir, 'skills');
550
692
  if (force) {
551
693
  const pruned = pruneStaleEntries(skillsSrc, skillsDest);
552
694
  if (pruned > 0)
553
- console.log(` Pruned ${pruned} stale skill entries`);
695
+ log(` Pruned ${pruned} stale skill entries`);
554
696
  }
555
697
  const skillsResult = copyDirSync(skillsSrc, skillsDest, { overwrite: force });
556
- console.log(` Skills ${skillsResult.created.length} copied, ${skillsResult.skipped.length} skipped`);
698
+ log(` Skills ${skillsResult.created.length} copied, ${skillsResult.skipped.length} skipped`);
557
699
  // 5. Copy agents
558
700
  const agentsSrc = path.join(TEMPLATES_DIR, 'agents');
559
701
  const agentsDest = path.join(claudeDir, 'agents');
560
702
  if (force) {
561
703
  const pruned = pruneStaleEntries(agentsSrc, agentsDest);
562
704
  if (pruned > 0)
563
- console.log(` Pruned ${pruned} stale agent entries`);
705
+ log(` Pruned ${pruned} stale agent entries`);
564
706
  }
565
707
  const agentsResult = copyDirSync(agentsSrc, agentsDest, { overwrite: force });
566
- console.log(` Agents ${agentsResult.created.length} copied, ${agentsResult.skipped.length} skipped`);
708
+ log(` Agents ${agentsResult.created.length} copied, ${agentsResult.skipped.length} skipped`);
567
709
  // 6. Copy workflow presets
568
710
  const presetsSrc = path.join(TEMPLATES_DIR, 'presets');
569
711
  const presetsDest = path.join(claudeDir, 'config', 'workflows');
@@ -571,23 +713,23 @@ export function runInit(projectRoot, options = {}) {
571
713
  if (force) {
572
714
  const pruned = pruneStaleEntries(presetsSrc, presetsDest);
573
715
  if (pruned > 0)
574
- console.log(` Pruned ${pruned} stale preset entries`);
716
+ log(` Pruned ${pruned} stale preset entries`);
575
717
  }
576
718
  const presetsResult = copyDirSync(presetsSrc, presetsDest, { overwrite: force });
577
- console.log(` Presets ${presetsResult.created.length} copied, ${presetsResult.skipped.length} skipped`);
719
+ log(` Presets ${presetsResult.created.length} copied, ${presetsResult.skipped.length} skipped`);
578
720
  }
579
721
  // 6b. Reset active workflow config when --force, or create if missing
580
722
  const activeWorkflowDest = path.join(claudeDir, 'config', 'workflow.json');
581
723
  if (force) {
582
- fs.copyFileSync(defaultPresetPath, activeWorkflowDest);
583
- console.log(` Reset .claude/config/workflow.json (default workflow)`);
724
+ fs.copyFileSync(presetPath, activeWorkflowDest);
725
+ log(` Reset .claude/config/workflow.json (${selectedPreset} workflow)`);
584
726
  }
585
727
  else if (!fs.existsSync(activeWorkflowDest)) {
586
- fs.copyFileSync(defaultPresetPath, activeWorkflowDest);
587
- console.log(` Created .claude/config/workflow.json`);
728
+ fs.copyFileSync(presetPath, activeWorkflowDest);
729
+ log(` Created .claude/config/workflow.json (${selectedPreset})`);
588
730
  }
589
731
  else {
590
- console.log(` Exists .claude/config/workflow.json`);
732
+ log(` Exists .claude/config/workflow.json`);
591
733
  }
592
734
  // 7. Copy agent-triggers.json
593
735
  const triggersSrc = path.join(TEMPLATES_DIR, 'config', 'agent-triggers.json');
@@ -595,10 +737,10 @@ export function runInit(projectRoot, options = {}) {
595
737
  if (fs.existsSync(triggersSrc)) {
596
738
  if (force || !fs.existsSync(triggersDest)) {
597
739
  fs.copyFileSync(triggersSrc, triggersDest);
598
- console.log(` Created .claude/config/agent-triggers.json`);
740
+ log(` Created .claude/config/agent-triggers.json`);
599
741
  }
600
742
  else {
601
- console.log(` Exists .claude/config/agent-triggers.json`);
743
+ log(` Exists .claude/config/agent-triggers.json`);
602
744
  }
603
745
  }
604
746
  // 7b. Copy quick/ templates
@@ -606,14 +748,14 @@ export function runInit(projectRoot, options = {}) {
606
748
  const quickDest = path.join(claudeDir, 'quick');
607
749
  if (fs.existsSync(quickSrc)) {
608
750
  const quickResult = copyDirSync(quickSrc, quickDest, { overwrite: force });
609
- console.log(` Quick ${quickResult.created.length} copied, ${quickResult.skipped.length} skipped`);
751
+ log(` Quick ${quickResult.created.length} copied, ${quickResult.skipped.length} skipped`);
610
752
  }
611
753
  // 7c. Copy anti-patterns/ templates
612
754
  const antiSrc = path.join(TEMPLATES_DIR, 'anti-patterns');
613
755
  const antiDest = path.join(claudeDir, 'anti-patterns');
614
756
  if (fs.existsSync(antiSrc)) {
615
757
  const antiResult = copyDirSync(antiSrc, antiDest, { overwrite: force });
616
- console.log(` Anti-pat ${antiResult.created.length} copied, ${antiResult.skipped.length} skipped`);
758
+ log(` Anti-pat ${antiResult.created.length} copied, ${antiResult.skipped.length} skipped`);
617
759
  }
618
760
  // 7d. Copy lessons-learned.md
619
761
  const lessonsSrc = path.join(TEMPLATES_DIR, 'lessons-learned.md');
@@ -621,191 +763,434 @@ export function runInit(projectRoot, options = {}) {
621
763
  if (fs.existsSync(lessonsSrc)) {
622
764
  if (force || !fs.existsSync(lessonsDest)) {
623
765
  fs.copyFileSync(lessonsSrc, lessonsDest);
624
- console.log(` Created .claude/lessons-learned.md`);
766
+ log(` Created .claude/lessons-learned.md`);
625
767
  }
626
768
  else {
627
- console.log(` Exists .claude/lessons-learned.md`);
769
+ log(` Exists .claude/lessons-learned.md`);
628
770
  }
629
771
  }
630
772
  // 7e. Generate workflow manifest
631
773
  const manifestOk = writeManifest(claudeDir);
632
- console.log(` ${manifestOk ? 'Created' : 'Skipped'} .claude/config/workflow-manifest.sh`);
774
+ log(` ${manifestOk ? 'Created' : 'Skipped'} .claude/config/workflow-manifest.sh`);
633
775
  // 7f. Generate INDEX.md
634
776
  const indexDest = path.join(claudeDir, 'INDEX.md');
635
777
  if (force || !fs.existsSync(indexDest)) {
636
778
  const indexContent = generateIndexMd(projectRoot, claudeDir);
637
779
  fs.writeFileSync(indexDest, indexContent, 'utf8');
638
- console.log(` ${force ? 'Reset ' : 'Created'} .claude/INDEX.md`);
780
+ log(` ${force ? 'Reset ' : 'Created'} .claude/INDEX.md`);
639
781
  }
640
782
  else {
641
- console.log(` Exists .claude/INDEX.md`);
783
+ log(` Exists .claude/INDEX.md`);
642
784
  }
643
785
  // 8. Merge hook registrations into settings.local.json
644
- console.log('');
786
+ log('');
645
787
  const settingsTarget = path.join(claudeDir, 'settings.local.json');
646
788
  const settingsSrc = path.join(TEMPLATES_DIR, 'settings-hooks.json');
647
789
  mergeSettingsHooks(settingsTarget, settingsSrc);
648
- console.log(` Merged hook registrations into .claude/settings.local.json`);
790
+ log(` Merged hook registrations into .claude/settings.local.json`);
649
791
  // 9. Update .gitignore
650
792
  const gitignoreUpdated = updateGitignore(projectRoot);
651
- console.log(` ${gitignoreUpdated ? 'Updated' : 'Exists '} .gitignore (Orbital patterns)`);
793
+ log(` ${gitignoreUpdated ? 'Updated' : 'Exists '} .gitignore (Orbital patterns)`);
652
794
  // 10. Make hook scripts executable
653
795
  chmodScripts(hooksDest);
654
- console.log(` chmod hook scripts set to executable`);
796
+ log(` chmod hook scripts set to executable`);
797
+ // 11. Seed global primitives from package templates
798
+ seedGlobalPrimitives();
799
+ log(` Seeded global primitives (~/.orbital/primitives/)`);
800
+ // 12. Build and save manifest
801
+ const manifest = buildInitManifest(projectRoot, claudeDir, selectedPreset);
802
+ saveManifest(projectRoot, manifest);
803
+ log(` Created .claude/orbital-manifest.json (${Object.keys(manifest.files).length} files tracked)`);
655
804
  // Summary
656
805
  const totalCreated = hooksResult.created.length + skillsResult.created.length + agentsResult.created.length;
657
806
  const totalSkipped = hooksResult.skipped.length + skillsResult.skipped.length + agentsResult.skipped.length;
658
- console.log(`\nDone. ${totalCreated} files installed, ${totalSkipped} skipped (use --force to overwrite).`);
807
+ log(`\nDone. ${totalCreated} files installed, ${totalSkipped} skipped (use --force to overwrite).`);
659
808
  }
660
- export function runUpdate(projectRoot) {
809
+ export function runUpdate(projectRoot, options = {}) {
810
+ const { dryRun = false } = options;
661
811
  const claudeDir = path.join(projectRoot, '.claude');
662
- console.log(`\nOrbital Command update`);
812
+ const newVersion = getPackageVersion();
813
+ console.log(`\nOrbital Command — update${dryRun ? ' (dry run)' : ''}`);
663
814
  console.log(`Project root: ${projectRoot}\n`);
664
- // 1. Copy hooks (overwrite) prune stale entries first
665
- const hooksSrc = path.join(TEMPLATES_DIR, 'hooks');
666
- const hooksDest = path.join(claudeDir, 'hooks');
667
- const hooksPruned = pruneStaleEntries(hooksSrc, hooksDest);
668
- if (hooksPruned > 0)
669
- console.log(` Pruned ${hooksPruned} stale hook entries`);
670
- const hooksResult = copyDirSync(hooksSrc, hooksDest, { overwrite: true });
671
- console.log(` Hooks ${hooksResult.created.length} updated`);
672
- // 2. Copy skills (overwrite) prune stale entries first
673
- const skillsSrc = path.join(TEMPLATES_DIR, 'skills');
674
- const skillsDest = path.join(claudeDir, 'skills');
675
- const skillsPruned = pruneStaleEntries(skillsSrc, skillsDest);
676
- if (skillsPruned > 0)
677
- console.log(` Pruned ${skillsPruned} stale skill entries`);
678
- const skillsResult = copyDirSync(skillsSrc, skillsDest, { overwrite: true });
679
- console.log(` Skills ${skillsResult.created.length} updated`);
680
- // 3. Copy agents (overwrite) — prune stale entries first
681
- const agentsSrc = path.join(TEMPLATES_DIR, 'agents');
682
- const agentsDest = path.join(claudeDir, 'agents');
683
- const agentsPruned = pruneStaleEntries(agentsSrc, agentsDest);
684
- if (agentsPruned > 0)
685
- console.log(` Pruned ${agentsPruned} stale agent entries`);
686
- const agentsResult = copyDirSync(agentsSrc, agentsDest, { overwrite: true });
687
- console.log(` Agents ${agentsResult.created.length} updated`);
688
- // 4. Update workflow presets — prune stale entries first
689
- const presetsSrc = path.join(TEMPLATES_DIR, 'presets');
690
- const presetsDest = path.join(claudeDir, 'config', 'workflows');
691
- if (fs.existsSync(presetsSrc) && fs.readdirSync(presetsSrc).length > 0) {
692
- const presetsPruned = pruneStaleEntries(presetsSrc, presetsDest);
693
- if (presetsPruned > 0)
694
- console.log(` Pruned ${presetsPruned} stale preset entries`);
695
- const presetsResult = copyDirSync(presetsSrc, presetsDest, { overwrite: true });
696
- console.log(` Presets ${presetsResult.created.length} updated`);
815
+ // 1. Load or create manifest (auto-migrate legacy installs)
816
+ let manifest = loadManifest(projectRoot);
817
+ if (!manifest) {
818
+ if (needsLegacyMigration(projectRoot)) {
819
+ console.log(' Migrating from legacy install...');
820
+ const result = migrateFromLegacy(projectRoot, TEMPLATES_DIR, newVersion);
821
+ console.log(` Migrated ${result.synced} synced, ${result.modified} modified, ${result.userOwned} user-owned files`);
822
+ if (result.importedPins > 0)
823
+ console.log(` Imported ${result.importedPins} pinned files from orbital-sync.json`);
824
+ manifest = loadManifest(projectRoot);
825
+ }
826
+ if (!manifest) {
827
+ console.log(' No manifest found. Run `orbital init` first.');
828
+ return;
829
+ }
697
830
  }
698
- // 5. Update quick/, anti-patterns/, lessons-learned, scope template
699
- const quickSrc = path.join(TEMPLATES_DIR, 'quick');
700
- const quickDest = path.join(claudeDir, 'quick');
701
- if (fs.existsSync(quickSrc)) {
702
- const quickResult = copyDirSync(quickSrc, quickDest, { overwrite: true });
703
- console.log(` Quick ${quickResult.created.length} updated`);
831
+ const oldVersion = manifest.packageVersion;
832
+ // 1b. Refresh file statuses so outdated vs modified is accurate
833
+ refreshFileStatuses(manifest, claudeDir);
834
+ // 2. Compute update plan
835
+ const renameMap = loadRenameMap(TEMPLATES_DIR, oldVersion, newVersion);
836
+ const plan = computeUpdatePlan({
837
+ templatesDir: TEMPLATES_DIR,
838
+ claudeDir,
839
+ manifest,
840
+ newVersion,
841
+ renameMap,
842
+ });
843
+ // 3. Dry-run mode — print plan and exit
844
+ if (dryRun) {
845
+ console.log(formatPlan(plan, oldVersion, newVersion));
846
+ return;
704
847
  }
705
- const antiSrc = path.join(TEMPLATES_DIR, 'anti-patterns');
706
- const antiDest = path.join(claudeDir, 'anti-patterns');
707
- if (fs.existsSync(antiSrc)) {
708
- const antiResult = copyDirSync(antiSrc, antiDest, { overwrite: true });
709
- console.log(` Anti-pat ${antiResult.created.length} updated`);
848
+ if (plan.isEmpty && oldVersion === newVersion) {
849
+ console.log(' Everything up to date. No changes needed.');
710
850
  }
711
- const lessonsSrc = path.join(TEMPLATES_DIR, 'lessons-learned.md');
712
- const lessonsDest = path.join(claudeDir, 'lessons-learned.md');
713
- if (fs.existsSync(lessonsSrc) && !fs.existsSync(lessonsDest)) {
714
- fs.copyFileSync(lessonsSrc, lessonsDest);
715
- console.log(` Created .claude/lessons-learned.md`);
851
+ // 4. Create backup of files that will be modified
852
+ const filesToBackup = getFilesToBackup(plan);
853
+ if (filesToBackup.length > 0) {
854
+ const backupDir = createBackup(claudeDir, filesToBackup);
855
+ if (backupDir) {
856
+ console.log(` Backup ${filesToBackup.length} files → ${path.relative(claudeDir, backupDir)}/`);
857
+ }
858
+ }
859
+ // 5. Execute plan
860
+ const templateInventory = buildTemplateInventory(TEMPLATES_DIR);
861
+ // 5a. Handle renames
862
+ for (const { from, to } of plan.toRename) {
863
+ const fromPath = path.join(claudeDir, from);
864
+ const toPath = path.join(claudeDir, to);
865
+ const toDir = path.dirname(toPath);
866
+ if (!fs.existsSync(toDir))
867
+ fs.mkdirSync(toDir, { recursive: true });
868
+ if (fs.existsSync(fromPath)) {
869
+ safeBackupFile(fromPath);
870
+ const stat = fs.lstatSync(fromPath);
871
+ if (stat.isSymbolicLink()) {
872
+ // Recreate symlink at new path pointing to remapped template
873
+ const target = fs.readlinkSync(fromPath);
874
+ fs.unlinkSync(fromPath);
875
+ fs.symlinkSync(target, toPath);
876
+ }
877
+ else {
878
+ fs.renameSync(fromPath, toPath);
879
+ }
880
+ }
881
+ // Transfer manifest record
882
+ const record = manifest.files[from];
883
+ if (record) {
884
+ delete manifest.files[from];
885
+ const newHash = templateInventory.get(to);
886
+ manifest.files[to] = { ...record, templateHash: newHash, installedHash: newHash || record.installedHash };
887
+ }
888
+ console.log(` RENAME ${from} → ${to}`);
889
+ }
890
+ // 5b. Add new files
891
+ for (const filePath of plan.toAdd) {
892
+ const templateHash = templateInventory.get(filePath);
893
+ if (!templateHash)
894
+ continue;
895
+ const destPath = path.join(claudeDir, filePath);
896
+ const destDir = path.dirname(destPath);
897
+ if (!fs.existsSync(destDir))
898
+ fs.mkdirSync(destDir, { recursive: true });
899
+ copyTemplateFile(TEMPLATES_DIR, filePath, destPath);
900
+ manifest.files[filePath] = templateFileRecord(templateHash);
901
+ console.log(` ADD ${filePath}`);
902
+ }
903
+ // 5c. Update changed synced/outdated files
904
+ for (const filePath of plan.toUpdate) {
905
+ const templateHash = templateInventory.get(filePath);
906
+ if (!templateHash)
907
+ continue;
908
+ const destPath = path.join(claudeDir, filePath);
909
+ safeBackupFile(destPath);
910
+ copyTemplateFile(TEMPLATES_DIR, filePath, destPath);
911
+ manifest.files[filePath] = templateFileRecord(templateHash);
912
+ console.log(` UPDATE ${filePath}`);
913
+ }
914
+ // 5d. Remove deleted files
915
+ for (const filePath of plan.toRemove) {
916
+ const absPath = path.join(claudeDir, filePath);
917
+ if (fs.existsSync(absPath)) {
918
+ safeBackupFile(absPath);
919
+ fs.unlinkSync(absPath);
920
+ }
921
+ delete manifest.files[filePath];
922
+ console.log(` REMOVE ${filePath}`);
923
+ }
924
+ // 5e. Update pinned/modified file records (record new template hash without touching file)
925
+ for (const { file, reason, newTemplateHash } of plan.toSkip) {
926
+ if (manifest.files[file]) {
927
+ manifest.files[file].templateHash = newTemplateHash;
928
+ }
929
+ if (reason === 'modified') {
930
+ console.log(` SKIP ${file} (user modified)`);
931
+ }
932
+ else {
933
+ console.log(` SKIP ${file} (pinned)`);
934
+ }
716
935
  }
936
+ // 5f. Clean up empty directories
937
+ for (const dir of ['hooks', 'skills', 'agents', 'config/workflows']) {
938
+ const dirPath = path.join(claudeDir, dir);
939
+ if (fs.existsSync(dirPath))
940
+ cleanEmptyDirs(dirPath);
941
+ }
942
+ // 6. Bidirectional settings hook sync
943
+ const settingsTarget = path.join(claudeDir, 'settings.local.json');
944
+ const settingsSrc = path.join(TEMPLATES_DIR, 'settings-hooks.json');
945
+ const syncResult = syncSettingsHooks(settingsTarget, settingsSrc, manifest.settingsHooksChecksum, renameMap);
946
+ if (!syncResult.skipped) {
947
+ console.log(` Settings +${syncResult.added} -${syncResult.removed} hooks (${syncResult.updated} renamed)`);
948
+ manifest.settingsHooksChecksum = getTemplateChecksum(settingsSrc);
949
+ }
950
+ // 7. Config migrations
951
+ const configPath = path.join(claudeDir, 'orbital.config.json');
952
+ const migrationResult = migrateConfig(configPath, manifest.appliedMigrations);
953
+ if (migrationResult.applied.length > 0) {
954
+ manifest.appliedMigrations.push(...migrationResult.applied);
955
+ console.log(` Config ${migrationResult.applied.length} migration(s) applied`);
956
+ }
957
+ if (migrationResult.defaultsFilled.length > 0) {
958
+ console.log(` Config ${migrationResult.defaultsFilled.length} default(s) filled`);
959
+ }
960
+ // 8. Regenerate derived artifacts (always)
961
+ const workflowManifestOk = writeManifest(claudeDir);
962
+ console.log(` ${workflowManifestOk ? 'Updated' : 'Skipped'} .claude/config/workflow-manifest.sh`);
963
+ const indexContent = generateIndexMd(projectRoot, claudeDir);
964
+ fs.writeFileSync(path.join(claudeDir, 'INDEX.md'), indexContent, 'utf8');
965
+ console.log(` Updated .claude/INDEX.md`);
966
+ // 9. Update agent-triggers.json (template-managed)
967
+ const triggersSrc = path.join(TEMPLATES_DIR, 'config', 'agent-triggers.json');
968
+ const triggersDest = path.join(claudeDir, 'config', 'agent-triggers.json');
969
+ if (fs.existsSync(triggersSrc)) {
970
+ fs.copyFileSync(triggersSrc, triggersDest);
971
+ console.log(` Updated .claude/config/agent-triggers.json`);
972
+ }
973
+ // 10. Update scope template
717
974
  const scopeTemplateSrc = path.join(TEMPLATES_DIR, 'scopes', '_template.md');
718
975
  const scopeTemplateDest = path.join(projectRoot, 'scopes', '_template.md');
719
976
  if (fs.existsSync(scopeTemplateSrc)) {
720
977
  ensureDir(path.join(projectRoot, 'scopes'));
721
978
  fs.copyFileSync(scopeTemplateSrc, scopeTemplateDest);
722
- console.log(` Updated scopes/_template.md`);
723
979
  }
724
- // 5b. Regenerate workflow manifest
725
- const manifestOk = writeManifest(claudeDir);
726
- console.log(` ${manifestOk ? 'Updated' : 'Skipped'} .claude/config/workflow-manifest.sh`);
727
- // 6. Re-merge settings hooks
728
- const settingsTarget = path.join(claudeDir, 'settings.local.json');
729
- const settingsSrc = path.join(TEMPLATES_DIR, 'settings-hooks.json');
730
- mergeSettingsHooks(settingsTarget, settingsSrc);
731
- console.log(` Merged hook registrations into .claude/settings.local.json`);
732
- // 7. Make hook scripts executable
733
- chmodScripts(hooksDest);
734
- console.log(` chmod hook scripts set to executable`);
735
- console.log(`\nUpdate complete.\n`);
980
+ // 11. Make hook scripts executable
981
+ chmodScripts(path.join(claudeDir, 'hooks'));
982
+ // 12. Refresh global primitives
983
+ seedGlobalPrimitives();
984
+ // 13. Update manifest metadata
985
+ manifest.previousPackageVersion = oldVersion;
986
+ manifest.packageVersion = newVersion;
987
+ manifest.updatedAt = new Date().toISOString();
988
+ saveManifest(projectRoot, manifest);
989
+ // 14. Validate
990
+ const report = validate(projectRoot, newVersion);
991
+ if (report.errors > 0) {
992
+ console.log(`\n Validation: ${report.errors} errors found`);
993
+ console.log(formatValidationReport(report));
994
+ }
995
+ const totalChanges = plan.toAdd.length + plan.toUpdate.length + plan.toRemove.length + plan.toRename.length;
996
+ console.log(`\nUpdate complete. ${totalChanges} file changes, ${plan.toSkip.length} skipped.\n`);
736
997
  }
737
- export function runUninstall(projectRoot) {
998
+ export function runUninstall(projectRoot, options = {}) {
999
+ const { dryRun = false, keepConfig = false } = options;
738
1000
  const claudeDir = path.join(projectRoot, '.claude');
739
- console.log(`\nOrbital Command — uninstall`);
1001
+ console.log(`\nOrbital Command — uninstall${dryRun ? ' (dry run)' : ''}`);
740
1002
  console.log(`Project root: ${projectRoot}\n`);
741
- let removedCount = 0;
742
- // 1. Remove orbital hooks from settings.local.json
743
- const settingsPath = path.join(claudeDir, 'settings.local.json');
744
- if (fs.existsSync(settingsPath)) {
745
- try {
746
- const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
747
- if (settings.hooks) {
748
- for (const [event] of Object.entries(settings.hooks)) {
749
- for (const group of settings.hooks[event]) {
750
- if (group.hooks) {
751
- const before = group.hooks.length;
752
- group.hooks = group.hooks.filter((h) => !h._orbital);
753
- removedCount += before - group.hooks.length;
754
- }
755
- }
756
- settings.hooks[event] = settings.hooks[event].filter((g) => g.hooks && g.hooks.length > 0);
757
- if (settings.hooks[event].length === 0) {
758
- delete settings.hooks[event];
759
- }
760
- }
761
- if (Object.keys(settings.hooks).length === 0) {
762
- delete settings.hooks;
763
- }
764
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
765
- console.log(` Removed ${removedCount} orbital hook registrations from settings.local.json`);
766
- }
1003
+ const manifest = loadManifest(projectRoot);
1004
+ // Fall back to legacy uninstall if no manifest
1005
+ if (!manifest) {
1006
+ console.log(' No manifest found — falling back to legacy uninstall.');
1007
+ runLegacyUninstall(projectRoot);
1008
+ return;
1009
+ }
1010
+ // Compute what to remove vs preserve
1011
+ const toRemove = [];
1012
+ const toPreserve = [];
1013
+ for (const [filePath, record] of Object.entries(manifest.files)) {
1014
+ if (record.origin === 'user') {
1015
+ toPreserve.push(filePath);
767
1016
  }
768
- catch {
769
- console.warn(' Warning: could not parse settings.local.json');
1017
+ else if (record.status === 'modified' || record.status === 'outdated') {
1018
+ toPreserve.push(filePath);
1019
+ }
1020
+ else {
1021
+ toRemove.push(filePath);
770
1022
  }
771
1023
  }
772
- // 2. Delete hooks that came from templates
773
- const hookFiles = listTemplateFiles(path.join(TEMPLATES_DIR, 'hooks'), path.join(claudeDir, 'hooks'));
774
- let hooksRemoved = 0;
775
- for (const f of hookFiles) {
776
- if (fs.existsSync(f)) {
777
- fs.unlinkSync(f);
778
- hooksRemoved++;
1024
+ if (dryRun) {
1025
+ console.log(' Files to REMOVE:');
1026
+ for (const f of toRemove)
1027
+ console.log(` ${f}`);
1028
+ if (toPreserve.length > 0) {
1029
+ console.log(' Files to PRESERVE:');
1030
+ for (const f of toPreserve)
1031
+ console.log(` ${f} (${manifest.files[f].origin}/${manifest.files[f].status})`);
779
1032
  }
1033
+ console.log(`\n Would also remove: settings hooks, generated artifacts, config files, gitignore entries`);
1034
+ console.log(` No changes made. Run without --dry-run to apply.`);
1035
+ return;
780
1036
  }
781
- console.log(` Removed ${hooksRemoved} hook scripts`);
782
- // 3. Delete skills that came from templates
783
- const skillFiles = listTemplateFiles(path.join(TEMPLATES_DIR, 'skills'), path.join(claudeDir, 'skills'));
784
- let skillsRemoved = 0;
785
- for (const f of skillFiles) {
786
- if (fs.existsSync(f)) {
787
- fs.unlinkSync(f);
788
- skillsRemoved++;
1037
+ // 1. Remove _orbital hooks from settings.local.json
1038
+ const settingsPath = path.join(claudeDir, 'settings.local.json');
1039
+ const removedHooks = removeAllOrbitalHooks(settingsPath);
1040
+ console.log(` Removed ${removedHooks} orbital hook registrations`);
1041
+ // 2. Delete template files (synced + pinned, not modified or user-owned)
1042
+ let filesRemoved = 0;
1043
+ for (const filePath of toRemove) {
1044
+ const absPath = path.join(claudeDir, filePath);
1045
+ if (fs.existsSync(absPath)) {
1046
+ fs.unlinkSync(absPath);
1047
+ filesRemoved++;
789
1048
  }
790
1049
  }
791
- const skillsDest = path.join(claudeDir, 'skills');
792
- if (fs.existsSync(skillsDest))
793
- cleanEmptyDirs(skillsDest);
794
- console.log(` Removed ${skillsRemoved} skill files`);
795
- // 4. Delete agents that came from templates
796
- const agentFiles = listTemplateFiles(path.join(TEMPLATES_DIR, 'agents'), path.join(claudeDir, 'agents'));
797
- let agentsRemoved = 0;
798
- for (const f of agentFiles) {
799
- if (fs.existsSync(f)) {
800
- fs.unlinkSync(f);
801
- agentsRemoved++;
1050
+ console.log(` Removed ${filesRemoved} template files`);
1051
+ if (toPreserve.length > 0) {
1052
+ console.log(` Preserved ${toPreserve.length} user/modified files`);
1053
+ }
1054
+ // 3. Clean up empty directories
1055
+ for (const dir of ['hooks', 'skills', 'agents', 'config/workflows', 'quick', 'anti-patterns']) {
1056
+ const dirPath = path.join(claudeDir, dir);
1057
+ if (fs.existsSync(dirPath))
1058
+ cleanEmptyDirs(dirPath);
1059
+ }
1060
+ // 4. Remove generated artifacts
1061
+ for (const artifact of manifest.generatedArtifacts) {
1062
+ const artifactPath = path.join(claudeDir, artifact);
1063
+ if (fs.existsSync(artifactPath)) {
1064
+ fs.unlinkSync(artifactPath);
1065
+ console.log(` Removed .claude/${artifact}`);
802
1066
  }
803
1067
  }
804
- const agentsDest = path.join(claudeDir, 'agents');
805
- if (fs.existsSync(agentsDest))
806
- cleanEmptyDirs(agentsDest);
807
- console.log(` Removed ${agentsRemoved} agent files`);
808
- const total = removedCount + hooksRemoved + skillsRemoved + agentsRemoved;
1068
+ // 5. Remove template-sourced config files
1069
+ const configFiles = [
1070
+ 'config/agent-triggers.json',
1071
+ 'config/workflow.json',
1072
+ 'lessons-learned.md',
1073
+ ];
1074
+ for (const file of configFiles) {
1075
+ const filePath = path.join(claudeDir, file);
1076
+ if (fs.existsSync(filePath)) {
1077
+ fs.unlinkSync(filePath);
1078
+ console.log(` Removed .claude/${file}`);
1079
+ }
1080
+ }
1081
+ // Remove config/workflows/ directory entirely
1082
+ const workflowsDir = path.join(claudeDir, 'config', 'workflows');
1083
+ if (fs.existsSync(workflowsDir)) {
1084
+ fs.rmSync(workflowsDir, { recursive: true, force: true });
1085
+ console.log(` Removed .claude/config/workflows/`);
1086
+ }
1087
+ // 6. Remove gitignore entries
1088
+ removeGitignoreEntries(projectRoot, manifest.gitignoreEntries);
1089
+ console.log(` Cleaned .gitignore`);
1090
+ // 7. Deregister from global registry
1091
+ if (unregisterProject(projectRoot)) {
1092
+ console.log(` Removed project from ~/.orbital/config.json`);
1093
+ }
1094
+ // 8. Remove orbital config and manifest (unless --keep-config)
1095
+ if (!keepConfig) {
1096
+ const toClean = ['orbital.config.json', 'orbital-manifest.json', 'orbital-sync.json'];
1097
+ for (const file of toClean) {
1098
+ const filePath = path.join(claudeDir, file);
1099
+ if (fs.existsSync(filePath))
1100
+ fs.unlinkSync(filePath);
1101
+ }
1102
+ // Remove backups directory
1103
+ const backupsDir = path.join(claudeDir, '.orbital-backups');
1104
+ if (fs.existsSync(backupsDir))
1105
+ fs.rmSync(backupsDir, { recursive: true, force: true });
1106
+ console.log(` Removed orbital config and manifest`);
1107
+ }
1108
+ else {
1109
+ // Still remove the manifest — it's invalid after uninstall
1110
+ const manifestPath = path.join(claudeDir, 'orbital-manifest.json');
1111
+ if (fs.existsSync(manifestPath))
1112
+ fs.unlinkSync(manifestPath);
1113
+ console.log(` Kept orbital.config.json (--keep-config)`);
1114
+ }
1115
+ // Clean up remaining empty directories
1116
+ for (const dir of ['config', 'quick', 'anti-patterns', 'review-verdicts']) {
1117
+ const dirPath = path.join(claudeDir, dir);
1118
+ if (fs.existsSync(dirPath))
1119
+ cleanEmptyDirs(dirPath);
1120
+ }
1121
+ const total = removedHooks + filesRemoved;
809
1122
  console.log(`\nUninstall complete. ${total} items removed.`);
1123
+ if (toPreserve.length > 0) {
1124
+ console.log(`Note: ${toPreserve.length} user/modified files were preserved.`);
1125
+ }
810
1126
  console.log(`Note: scopes/ and .claude/orbital-events/ were preserved.\n`);
811
1127
  }
1128
+ /** Legacy uninstall for projects without a manifest (backward compat). */
1129
+ function runLegacyUninstall(projectRoot) {
1130
+ const claudeDir = path.join(projectRoot, '.claude');
1131
+ // Remove orbital hooks from settings.local.json
1132
+ const settingsPath = path.join(claudeDir, 'settings.local.json');
1133
+ const removedHooks = removeAllOrbitalHooks(settingsPath);
1134
+ console.log(` Removed ${removedHooks} orbital hook registrations`);
1135
+ // Delete hooks/skills/agents that match template files
1136
+ for (const dir of ['hooks', 'skills', 'agents']) {
1137
+ const templateFiles = listTemplateFiles(path.join(TEMPLATES_DIR, dir), path.join(claudeDir, dir));
1138
+ let removed = 0;
1139
+ for (const f of templateFiles) {
1140
+ if (fs.existsSync(f)) {
1141
+ fs.unlinkSync(f);
1142
+ removed++;
1143
+ }
1144
+ }
1145
+ const dirPath = path.join(claudeDir, dir);
1146
+ if (fs.existsSync(dirPath))
1147
+ cleanEmptyDirs(dirPath);
1148
+ console.log(` Removed ${removed} ${dir} files`);
1149
+ }
1150
+ console.log(`\nLegacy uninstall complete.`);
1151
+ console.log(`Note: scopes/ and .claude/orbital-events/ were preserved.\n`);
1152
+ }
1153
+ /** Remove Orbital-added entries from .gitignore. */
1154
+ function removeGitignoreEntries(projectRoot, entries) {
1155
+ const gitignorePath = path.join(projectRoot, '.gitignore');
1156
+ if (!fs.existsSync(gitignorePath))
1157
+ return;
1158
+ let content = fs.readFileSync(gitignorePath, 'utf-8');
1159
+ const marker = '# Orbital Command';
1160
+ // Try to remove the entire block
1161
+ const markerIdx = content.indexOf(marker);
1162
+ if (markerIdx !== -1) {
1163
+ // Find the block boundaries: from the marker to the next non-empty/non-orbital line
1164
+ const before = content.slice(0, markerIdx).replace(/\n+$/, '');
1165
+ const after = content.slice(markerIdx);
1166
+ const lines = after.split('\n');
1167
+ let endIdx = 0;
1168
+ for (let i = 0; i < lines.length; i++) {
1169
+ const line = lines[i].trim();
1170
+ if (i === 0) {
1171
+ endIdx = i + 1;
1172
+ continue;
1173
+ } // skip the marker line
1174
+ if (line === '' || entries.includes(line)) {
1175
+ endIdx = i + 1;
1176
+ continue;
1177
+ }
1178
+ break;
1179
+ }
1180
+ const remaining = lines.slice(endIdx).join('\n');
1181
+ content = before + (remaining ? '\n' + remaining : '') + '\n';
1182
+ fs.writeFileSync(gitignorePath, content, 'utf-8');
1183
+ }
1184
+ }
1185
+ /** Copy a single template file to a destination, resolving the template path. */
1186
+ function copyTemplateFile(templatesDir, claudeRelPath, destPath) {
1187
+ const templateRelPath = reverseRemapPath(claudeRelPath);
1188
+ const srcPath = path.join(templatesDir, templateRelPath);
1189
+ if (!fs.existsSync(srcPath)) {
1190
+ throw new Error(`Template file not found: ${templateRelPath}`);
1191
+ }
1192
+ const destDir = path.dirname(destPath);
1193
+ if (!fs.existsSync(destDir))
1194
+ fs.mkdirSync(destDir, { recursive: true });
1195
+ safeCopyTemplate(srcPath, destPath);
1196
+ }