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