orbital-command 0.1.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/orbital.js +676 -53
- package/dist/assets/PrimitivesConfig-CrmQXYh4.js +32 -0
- package/dist/assets/QualityGates-BbasOsF3.js +21 -0
- package/dist/assets/SessionTimeline-CGeJsVvy.js +1 -0
- package/dist/assets/Settings-oiM496mc.js +12 -0
- package/dist/assets/SourceControl-B1fP2nJL.js +41 -0
- package/dist/assets/WorkflowVisualizer-CWLYf-f0.js +74 -0
- package/dist/assets/arrow-down-CPy85_J6.js +6 -0
- package/dist/assets/charts-DbDg0Psc.js +68 -0
- package/dist/assets/circle-x-Cwz6ZQDV.js +6 -0
- package/dist/assets/file-text-C46Xr65c.js +6 -0
- package/dist/assets/formatDistanceToNow-BMqsSP44.js +1 -0
- package/dist/assets/globe-Cn2yNZUD.js +6 -0
- package/dist/assets/index-Aj4sV8Al.css +1 -0
- package/dist/assets/index-Bc9dK3MW.js +354 -0
- package/dist/assets/key-OPaNTWJ5.js +6 -0
- package/dist/assets/minus-GMsbpKym.js +6 -0
- package/dist/assets/shield-DwAFkDYI.js +6 -0
- package/dist/assets/ui-BmsSg9jU.js +53 -0
- package/dist/assets/useWorkflowEditor-BJkTX_NR.js +16 -0
- package/dist/assets/{vendor-Dzv9lrRc.js → vendor-Bqt8AJn2.js} +1 -1
- package/dist/assets/zap-DfbUoOty.js +11 -0
- package/dist/favicon.svg +1 -0
- package/dist/index.html +6 -5
- package/dist/server/server/__tests__/data-routes.test.js +124 -0
- package/dist/server/server/__tests__/helpers/db.js +17 -0
- package/dist/server/server/__tests__/helpers/mock-emitter.js +8 -0
- package/dist/server/server/__tests__/scope-routes.test.js +137 -0
- package/dist/server/server/__tests__/sprint-routes.test.js +102 -0
- package/dist/server/server/__tests__/workflow-routes.test.js +107 -0
- package/dist/server/server/config-migrator.js +138 -0
- package/dist/server/server/config.js +17 -2
- package/dist/server/server/database.js +27 -12
- package/dist/server/server/global-config.js +143 -0
- package/dist/server/server/index.js +882 -252
- package/dist/server/server/init.js +579 -194
- package/dist/server/server/launch.js +29 -0
- package/dist/server/server/manifest-types.js +8 -0
- package/dist/server/server/manifest.js +454 -0
- package/dist/server/server/migrate-legacy.js +229 -0
- package/dist/server/server/parsers/event-parser.test.js +117 -0
- package/dist/server/server/parsers/scope-parser.js +74 -28
- package/dist/server/server/parsers/scope-parser.test.js +230 -0
- package/dist/server/server/project-context.js +255 -0
- package/dist/server/server/project-emitter.js +41 -0
- package/dist/server/server/project-manager.js +297 -0
- package/dist/server/server/routes/config-routes.js +1 -3
- package/dist/server/server/routes/data-routes.js +22 -110
- package/dist/server/server/routes/dispatch-routes.js +15 -9
- package/dist/server/server/routes/git-routes.js +74 -0
- package/dist/server/server/routes/manifest-routes.js +319 -0
- package/dist/server/server/routes/scope-routes.js +37 -23
- package/dist/server/server/routes/sync-routes.js +134 -0
- package/dist/server/server/routes/version-routes.js +1 -15
- package/dist/server/server/routes/workflow-routes.js +9 -3
- package/dist/server/server/schema.js +2 -0
- package/dist/server/server/services/batch-orchestrator.js +26 -16
- package/dist/server/server/services/claude-session-service.js +17 -14
- package/dist/server/server/services/deploy-service.test.js +119 -0
- package/dist/server/server/services/event-service.js +64 -1
- package/dist/server/server/services/event-service.test.js +191 -0
- package/dist/server/server/services/gate-service.test.js +105 -0
- package/dist/server/server/services/git-service.js +108 -4
- package/dist/server/server/services/github-service.js +110 -2
- package/dist/server/server/services/readiness-service.test.js +190 -0
- package/dist/server/server/services/scope-cache.js +5 -1
- package/dist/server/server/services/scope-cache.test.js +142 -0
- package/dist/server/server/services/scope-service.js +217 -126
- package/dist/server/server/services/scope-service.test.js +137 -0
- package/dist/server/server/services/sprint-orchestrator.js +7 -6
- package/dist/server/server/services/sprint-service.js +21 -1
- package/dist/server/server/services/sprint-service.test.js +238 -0
- package/dist/server/server/services/sync-service.js +434 -0
- package/dist/server/server/services/sync-types.js +2 -0
- package/dist/server/server/services/telemetry-service.js +143 -0
- package/dist/server/server/services/workflow-service.js +26 -5
- package/dist/server/server/services/workflow-service.test.js +159 -0
- package/dist/server/server/settings-sync.js +284 -0
- package/dist/server/server/update-planner.js +279 -0
- package/dist/server/server/utils/cc-hooks-parser.js +3 -0
- package/dist/server/server/utils/cc-hooks-parser.test.js +86 -0
- package/dist/server/server/utils/dispatch-utils.js +77 -20
- package/dist/server/server/utils/dispatch-utils.test.js +182 -0
- package/dist/server/server/utils/logger.js +37 -3
- package/dist/server/server/utils/package-info.js +30 -0
- package/dist/server/server/utils/route-helpers.js +10 -0
- package/dist/server/server/utils/terminal-launcher.js +79 -25
- package/dist/server/server/utils/worktree-manager.js +13 -4
- package/dist/server/server/validator.js +230 -0
- package/dist/server/server/watchers/global-watcher.js +63 -0
- package/dist/server/server/watchers/scope-watcher.js +27 -12
- package/dist/server/server/wizard/config-editor.js +237 -0
- package/dist/server/server/wizard/detect.js +96 -0
- package/dist/server/server/wizard/doctor.js +115 -0
- package/dist/server/server/wizard/index.js +155 -0
- package/dist/server/server/wizard/phases/confirm.js +39 -0
- package/dist/server/server/wizard/phases/project-setup.js +90 -0
- package/dist/server/server/wizard/phases/setup-wizard.js +66 -0
- package/dist/server/server/wizard/phases/welcome.js +35 -0
- package/dist/server/server/wizard/phases/workflow-setup.js +22 -0
- package/dist/server/server/wizard/types.js +29 -0
- package/dist/server/server/wizard/ui.js +74 -0
- package/dist/server/shared/__fixtures__/workflow-configs.js +75 -0
- package/dist/server/shared/default-workflow.json +65 -0
- package/dist/server/shared/onboarding-tour.test.js +81 -0
- package/dist/server/shared/project-colors.js +24 -0
- package/dist/server/shared/workflow-config.test.js +84 -0
- package/dist/server/shared/workflow-engine.test.js +302 -0
- package/dist/server/shared/workflow-normalizer.js +101 -0
- package/dist/server/shared/workflow-normalizer.test.js +100 -0
- package/dist/server/src/components/onboarding/tour-steps.js +84 -0
- package/package.json +20 -15
- package/schemas/orbital.config.schema.json +16 -1
- package/scripts/postinstall.js +55 -7
- package/server/__tests__/data-routes.test.ts +149 -0
- package/server/__tests__/helpers/db.ts +19 -0
- package/server/__tests__/helpers/mock-emitter.ts +10 -0
- package/server/__tests__/scope-routes.test.ts +157 -0
- package/server/__tests__/sprint-routes.test.ts +118 -0
- package/server/__tests__/workflow-routes.test.ts +120 -0
- package/server/config-migrator.ts +163 -0
- package/server/config.ts +26 -2
- package/server/database.ts +35 -18
- package/server/global-config.ts +200 -0
- package/server/index.ts +975 -287
- package/server/init.ts +625 -182
- package/server/launch.ts +32 -0
- package/server/manifest-types.ts +145 -0
- package/server/manifest.ts +494 -0
- package/server/migrate-legacy.ts +290 -0
- package/server/parsers/event-parser.test.ts +135 -0
- package/server/parsers/scope-parser.test.ts +270 -0
- package/server/parsers/scope-parser.ts +79 -31
- package/server/project-context.ts +309 -0
- package/server/project-emitter.ts +50 -0
- package/server/project-manager.ts +369 -0
- package/server/routes/config-routes.ts +3 -5
- package/server/routes/data-routes.ts +28 -141
- package/server/routes/dispatch-routes.ts +19 -11
- package/server/routes/git-routes.ts +77 -0
- package/server/routes/manifest-routes.ts +388 -0
- package/server/routes/scope-routes.ts +29 -25
- package/server/routes/sync-routes.ts +175 -0
- package/server/routes/version-routes.ts +1 -16
- package/server/routes/workflow-routes.ts +9 -3
- package/server/schema.ts +2 -0
- package/server/services/batch-orchestrator.ts +24 -16
- package/server/services/claude-session-service.ts +16 -14
- package/server/services/deploy-service.test.ts +145 -0
- package/server/services/deploy-service.ts +2 -2
- package/server/services/event-service.test.ts +242 -0
- package/server/services/event-service.ts +92 -3
- package/server/services/gate-service.test.ts +131 -0
- package/server/services/gate-service.ts +2 -2
- package/server/services/git-service.ts +137 -4
- package/server/services/github-service.ts +120 -2
- package/server/services/readiness-service.test.ts +217 -0
- package/server/services/scope-cache.test.ts +167 -0
- package/server/services/scope-cache.ts +4 -1
- package/server/services/scope-service.test.ts +169 -0
- package/server/services/scope-service.ts +220 -126
- package/server/services/sprint-orchestrator.ts +7 -7
- package/server/services/sprint-service.test.ts +271 -0
- package/server/services/sprint-service.ts +27 -3
- package/server/services/sync-service.ts +482 -0
- package/server/services/sync-types.ts +77 -0
- package/server/services/telemetry-service.ts +195 -0
- package/server/services/workflow-service.test.ts +190 -0
- package/server/services/workflow-service.ts +29 -9
- package/server/settings-sync.ts +359 -0
- package/server/update-planner.ts +346 -0
- package/server/utils/cc-hooks-parser.test.ts +96 -0
- package/server/utils/cc-hooks-parser.ts +4 -0
- package/server/utils/dispatch-utils.test.ts +245 -0
- package/server/utils/dispatch-utils.ts +97 -27
- package/server/utils/logger.ts +40 -3
- package/server/utils/package-info.ts +32 -0
- package/server/utils/route-helpers.ts +12 -0
- package/server/utils/terminal-launcher.ts +85 -25
- package/server/utils/worktree-manager.ts +9 -4
- package/server/validator.ts +270 -0
- package/server/watchers/global-watcher.ts +77 -0
- package/server/watchers/scope-watcher.ts +21 -9
- package/server/wizard/config-editor.ts +248 -0
- package/server/wizard/detect.ts +104 -0
- package/server/wizard/doctor.ts +114 -0
- package/server/wizard/index.ts +187 -0
- package/server/wizard/phases/confirm.ts +45 -0
- package/server/wizard/phases/project-setup.ts +106 -0
- package/server/wizard/phases/setup-wizard.ts +78 -0
- package/server/wizard/phases/welcome.ts +43 -0
- package/server/wizard/phases/workflow-setup.ts +28 -0
- package/server/wizard/types.ts +56 -0
- package/server/wizard/ui.ts +93 -0
- package/shared/__fixtures__/workflow-configs.ts +80 -0
- package/shared/default-workflow.json +65 -0
- package/shared/onboarding-tour.test.ts +94 -0
- package/shared/project-colors.ts +24 -0
- package/shared/workflow-config.test.ts +111 -0
- package/shared/workflow-config.ts +7 -0
- package/shared/workflow-engine.test.ts +388 -0
- package/shared/workflow-normalizer.test.ts +119 -0
- package/shared/workflow-normalizer.ts +118 -0
- package/templates/hooks/end-session.sh +3 -1
- package/templates/hooks/orbital-emit.sh +2 -2
- package/templates/hooks/orbital-report-deploy.sh +4 -4
- package/templates/hooks/orbital-report-gates.sh +4 -4
- package/templates/hooks/orbital-scope-update.sh +1 -1
- package/templates/hooks/scope-create-cleanup.sh +2 -2
- package/templates/hooks/scope-create-gate.sh +0 -1
- package/templates/hooks/scope-helpers.sh +18 -0
- package/templates/hooks/scope-prepare.sh +66 -11
- package/templates/migrations/renames.json +1 -0
- package/templates/orbital.config.json +7 -2
- package/templates/settings-hooks.json +1 -1
- package/templates/skills/git-commit/SKILL.md +9 -4
- package/templates/skills/git-dev/SKILL.md +8 -3
- package/templates/skills/git-main/SKILL.md +8 -2
- package/templates/skills/git-production/SKILL.md +6 -2
- package/templates/skills/git-staging/SKILL.md +8 -3
- package/templates/skills/scope-create/SKILL.md +17 -3
- package/templates/skills/scope-fix-review/SKILL.md +6 -3
- package/templates/skills/scope-implement/SKILL.md +4 -1
- package/templates/skills/scope-post-review/SKILL.md +63 -5
- package/templates/skills/scope-pre-review/SKILL.md +5 -2
- package/templates/skills/scope-verify/SKILL.md +5 -3
- package/templates/skills/test-code-review/SKILL.md +41 -33
- package/templates/skills/test-scaffold/SKILL.md +222 -0
- package/dist/assets/WorkflowVisualizer-BZ21PIIF.js +0 -84
- package/dist/assets/charts-D__PA1zp.js +0 -72
- package/dist/assets/index-D1G6i0nS.css +0 -1
- package/dist/assets/index-DpItvKpf.js +0 -419
- package/dist/assets/ui-BvF022GT.js +0 -53
- package/index.html +0 -15
- package/postcss.config.js +0 -6
- package/src/App.tsx +0 -33
- package/src/components/AgentBadge.tsx +0 -40
- package/src/components/BatchPreflightModal.tsx +0 -115
- package/src/components/CardDisplayToggle.tsx +0 -74
- package/src/components/ColumnHeaderActions.tsx +0 -55
- package/src/components/ColumnMenu.tsx +0 -99
- package/src/components/DeployHistory.tsx +0 -141
- package/src/components/DispatchModal.tsx +0 -164
- package/src/components/DispatchPopover.tsx +0 -139
- package/src/components/DragOverlay.tsx +0 -25
- package/src/components/DriftSidebar.tsx +0 -140
- package/src/components/EnvironmentStrip.tsx +0 -88
- package/src/components/ErrorBoundary.tsx +0 -62
- package/src/components/FilterChip.tsx +0 -105
- package/src/components/GateIndicator.tsx +0 -33
- package/src/components/IdeaDetailModal.tsx +0 -190
- package/src/components/IdeaFormDialog.tsx +0 -113
- package/src/components/KanbanColumn.tsx +0 -201
- package/src/components/MarkdownRenderer.tsx +0 -114
- package/src/components/NeonGrid.tsx +0 -128
- package/src/components/PromotionQueue.tsx +0 -89
- package/src/components/ScopeCard.tsx +0 -234
- package/src/components/ScopeDetailModal.tsx +0 -255
- package/src/components/ScopeFilterBar.tsx +0 -152
- package/src/components/SearchInput.tsx +0 -102
- package/src/components/SessionPanel.tsx +0 -335
- package/src/components/SprintContainer.tsx +0 -303
- package/src/components/SprintDependencyDialog.tsx +0 -78
- package/src/components/SprintPreflightModal.tsx +0 -138
- package/src/components/StatusBar.tsx +0 -168
- package/src/components/SwimCell.tsx +0 -67
- package/src/components/SwimLaneRow.tsx +0 -94
- package/src/components/SwimlaneBoardView.tsx +0 -108
- package/src/components/VersionBadge.tsx +0 -139
- package/src/components/ViewModeSelector.tsx +0 -114
- package/src/components/config/AgentChip.tsx +0 -53
- package/src/components/config/AgentCreateDialog.tsx +0 -321
- package/src/components/config/AgentEditor.tsx +0 -175
- package/src/components/config/DirectoryTree.tsx +0 -582
- package/src/components/config/FileEditor.tsx +0 -550
- package/src/components/config/HookChip.tsx +0 -50
- package/src/components/config/StageCard.tsx +0 -198
- package/src/components/config/TransitionZone.tsx +0 -173
- package/src/components/config/UnifiedWorkflowPipeline.tsx +0 -216
- package/src/components/config/WorkflowPipeline.tsx +0 -161
- package/src/components/source-control/BranchList.tsx +0 -93
- package/src/components/source-control/BranchPanel.tsx +0 -105
- package/src/components/source-control/CommitLog.tsx +0 -100
- package/src/components/source-control/CommitRow.tsx +0 -47
- package/src/components/source-control/GitHubPanel.tsx +0 -110
- package/src/components/source-control/GitHubSetupGuide.tsx +0 -52
- package/src/components/source-control/GitOverviewBar.tsx +0 -101
- package/src/components/source-control/PullRequestList.tsx +0 -69
- package/src/components/source-control/WorktreeList.tsx +0 -80
- package/src/components/ui/badge.tsx +0 -41
- package/src/components/ui/button.tsx +0 -55
- package/src/components/ui/card.tsx +0 -78
- package/src/components/ui/dialog.tsx +0 -94
- package/src/components/ui/popover.tsx +0 -33
- package/src/components/ui/scroll-area.tsx +0 -54
- package/src/components/ui/separator.tsx +0 -28
- package/src/components/ui/tabs.tsx +0 -52
- package/src/components/ui/toggle-switch.tsx +0 -35
- package/src/components/ui/tooltip.tsx +0 -27
- package/src/components/workflow/AddEdgeDialog.tsx +0 -217
- package/src/components/workflow/AddListDialog.tsx +0 -201
- package/src/components/workflow/ChecklistEditor.tsx +0 -239
- package/src/components/workflow/CommandPrefixManager.tsx +0 -118
- package/src/components/workflow/ConfigSettingsPanel.tsx +0 -189
- package/src/components/workflow/DirectionSelector.tsx +0 -133
- package/src/components/workflow/DispatchConfigPanel.tsx +0 -180
- package/src/components/workflow/EdgeDetailPanel.tsx +0 -236
- package/src/components/workflow/EdgePropertyEditor.tsx +0 -251
- package/src/components/workflow/EditToolbar.tsx +0 -138
- package/src/components/workflow/HookDetailPanel.tsx +0 -250
- package/src/components/workflow/HookExecutionLog.tsx +0 -24
- package/src/components/workflow/HookSourceModal.tsx +0 -129
- package/src/components/workflow/HooksDashboard.tsx +0 -363
- package/src/components/workflow/ListPropertyEditor.tsx +0 -251
- package/src/components/workflow/MigrationPreviewDialog.tsx +0 -237
- package/src/components/workflow/MovementRulesPanel.tsx +0 -188
- package/src/components/workflow/NodeDetailPanel.tsx +0 -245
- package/src/components/workflow/PresetSelector.tsx +0 -414
- package/src/components/workflow/SkillCommandBuilder.tsx +0 -174
- package/src/components/workflow/WorkflowEdgeComponent.tsx +0 -145
- package/src/components/workflow/WorkflowNode.tsx +0 -147
- package/src/components/workflow/graphLayout.ts +0 -186
- package/src/components/workflow/mergeHooks.ts +0 -85
- package/src/components/workflow/useEditHistory.ts +0 -88
- package/src/components/workflow/useWorkflowEditor.ts +0 -262
- package/src/components/workflow/validateConfig.ts +0 -70
- package/src/hooks/useActiveDispatches.ts +0 -198
- package/src/hooks/useBoardSettings.ts +0 -170
- package/src/hooks/useCardDisplay.ts +0 -57
- package/src/hooks/useCcHooks.ts +0 -24
- package/src/hooks/useConfigTree.ts +0 -51
- package/src/hooks/useEnforcementRules.ts +0 -46
- package/src/hooks/useEvents.ts +0 -59
- package/src/hooks/useFileEditor.ts +0 -165
- package/src/hooks/useGates.ts +0 -57
- package/src/hooks/useIdeaActions.ts +0 -53
- package/src/hooks/useKanbanDnd.ts +0 -410
- package/src/hooks/useOrbitalConfig.ts +0 -54
- package/src/hooks/usePipeline.ts +0 -47
- package/src/hooks/usePipelineData.ts +0 -338
- package/src/hooks/useReconnect.ts +0 -25
- package/src/hooks/useScopeFilters.ts +0 -125
- package/src/hooks/useScopeSessions.ts +0 -44
- package/src/hooks/useScopes.ts +0 -67
- package/src/hooks/useSearch.ts +0 -67
- package/src/hooks/useSettings.tsx +0 -187
- package/src/hooks/useSocket.ts +0 -25
- package/src/hooks/useSourceControl.ts +0 -105
- package/src/hooks/useSprintPreflight.ts +0 -55
- package/src/hooks/useSprints.ts +0 -154
- package/src/hooks/useStatusBarHighlight.ts +0 -18
- package/src/hooks/useSwimlaneBoardSettings.ts +0 -104
- package/src/hooks/useTheme.ts +0 -9
- package/src/hooks/useTransitionReadiness.ts +0 -53
- package/src/hooks/useVersion.ts +0 -155
- package/src/hooks/useViolations.ts +0 -65
- package/src/hooks/useWorkflow.tsx +0 -125
- package/src/hooks/useZoomModifier.ts +0 -19
- package/src/index.css +0 -797
- package/src/layouts/DashboardLayout.tsx +0 -113
- package/src/lib/collisionDetection.ts +0 -20
- package/src/lib/scope-fields.ts +0 -61
- package/src/lib/swimlane.ts +0 -146
- package/src/lib/utils.ts +0 -15
- package/src/main.tsx +0 -19
- package/src/socket.ts +0 -11
- package/src/types/index.ts +0 -497
- package/src/views/AgentFeed.tsx +0 -339
- package/src/views/DeployPipeline.tsx +0 -59
- package/src/views/EnforcementView.tsx +0 -378
- package/src/views/PrimitivesConfig.tsx +0 -500
- package/src/views/QualityGates.tsx +0 -1012
- package/src/views/ScopeBoard.tsx +0 -454
- package/src/views/SessionTimeline.tsx +0 -516
- package/src/views/Settings.tsx +0 -183
- package/src/views/SourceControl.tsx +0 -95
- package/src/views/WorkflowVisualizer.tsx +0 -382
- package/tailwind.config.js +0 -161
- package/tsconfig.json +0 -25
- package/vite.config.ts +0 -49
|
@@ -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
|
-
|
|
440
|
-
|
|
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
|
-
|
|
561
|
+
log(` ${wasCreated ? 'Created' : 'Exists '} ${path.relative(projectRoot, dir)}/`);
|
|
451
562
|
}
|
|
452
|
-
// 1b. Create scopes/ subdirectories from the
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
588
|
+
log(` ${force ? 'Reset ' : 'Created'} scopes/_template.md`);
|
|
476
589
|
}
|
|
477
590
|
else {
|
|
478
|
-
|
|
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
|
-
|
|
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
|
-
|
|
623
|
+
log(` Created .claude/orbital.config.json (default)`);
|
|
498
624
|
}
|
|
499
|
-
//
|
|
500
|
-
|
|
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
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
-
|
|
676
|
+
log(` Exists .claude/orbital.config.json`);
|
|
535
677
|
}
|
|
536
678
|
// 3. Copy hooks
|
|
537
|
-
|
|
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
|
-
|
|
685
|
+
log(` Pruned ${pruned} stale hook entries`);
|
|
544
686
|
}
|
|
545
687
|
const hooksResult = copyDirSync(hooksSrc, hooksDest, { overwrite: force });
|
|
546
|
-
|
|
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
|
-
|
|
695
|
+
log(` Pruned ${pruned} stale skill entries`);
|
|
554
696
|
}
|
|
555
697
|
const skillsResult = copyDirSync(skillsSrc, skillsDest, { overwrite: force });
|
|
556
|
-
|
|
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
|
-
|
|
705
|
+
log(` Pruned ${pruned} stale agent entries`);
|
|
564
706
|
}
|
|
565
707
|
const agentsResult = copyDirSync(agentsSrc, agentsDest, { overwrite: force });
|
|
566
|
-
|
|
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
|
-
|
|
716
|
+
log(` Pruned ${pruned} stale preset entries`);
|
|
575
717
|
}
|
|
576
718
|
const presetsResult = copyDirSync(presetsSrc, presetsDest, { overwrite: force });
|
|
577
|
-
|
|
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(
|
|
583
|
-
|
|
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(
|
|
587
|
-
|
|
728
|
+
fs.copyFileSync(presetPath, activeWorkflowDest);
|
|
729
|
+
log(` Created .claude/config/workflow.json (${selectedPreset})`);
|
|
588
730
|
}
|
|
589
731
|
else {
|
|
590
|
-
|
|
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
|
-
|
|
740
|
+
log(` Created .claude/config/agent-triggers.json`);
|
|
599
741
|
}
|
|
600
742
|
else {
|
|
601
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
766
|
+
log(` Created .claude/lessons-learned.md`);
|
|
625
767
|
}
|
|
626
768
|
else {
|
|
627
|
-
|
|
769
|
+
log(` Exists .claude/lessons-learned.md`);
|
|
628
770
|
}
|
|
629
771
|
}
|
|
630
772
|
// 7e. Generate workflow manifest
|
|
631
773
|
const manifestOk = writeManifest(claudeDir);
|
|
632
|
-
|
|
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
|
-
|
|
780
|
+
log(` ${force ? 'Reset ' : 'Created'} .claude/INDEX.md`);
|
|
639
781
|
}
|
|
640
782
|
else {
|
|
641
|
-
|
|
783
|
+
log(` Exists .claude/INDEX.md`);
|
|
642
784
|
}
|
|
643
785
|
// 8. Merge hook registrations into settings.local.json
|
|
644
|
-
|
|
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
|
-
|
|
790
|
+
log(` Merged hook registrations into .claude/settings.local.json`);
|
|
649
791
|
// 9. Update .gitignore
|
|
650
792
|
const gitignoreUpdated = updateGitignore(projectRoot);
|
|
651
|
-
|
|
793
|
+
log(` ${gitignoreUpdated ? 'Updated' : 'Exists '} .gitignore (Orbital patterns)`);
|
|
652
794
|
// 10. Make hook scripts executable
|
|
653
795
|
chmodScripts(hooksDest);
|
|
654
|
-
|
|
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
|
-
|
|
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
|
-
|
|
812
|
+
const newVersion = getPackageVersion();
|
|
813
|
+
console.log(`\nOrbital Command — update${dryRun ? ' (dry run)' : ''}`);
|
|
663
814
|
console.log(`Project root: ${projectRoot}\n`);
|
|
664
|
-
// 1.
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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
|
-
|
|
706
|
-
|
|
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
|
-
|
|
712
|
-
const
|
|
713
|
-
if (
|
|
714
|
-
|
|
715
|
-
|
|
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
|
-
//
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
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
|
-
|
|
742
|
-
//
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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
|
-
|
|
769
|
-
|
|
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
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
if (
|
|
777
|
-
|
|
778
|
-
|
|
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
|
-
|
|
782
|
-
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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
|
-
|
|
792
|
-
if (
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
//
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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
|
+
}
|