mirror-mind 0.0.1 → 0.32.1
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/.claude/hooks/log-session-end.sh +85 -0
- package/.claude/hooks/log-user-prompt.sh +85 -0
- package/.claude/hooks/mirror-inject.sh +85 -0
- package/.claude/hooks/session-start.sh +85 -0
- package/.gemini/hooks/log-assistant.sh +85 -0
- package/.gemini/hooks/log-user.sh +85 -0
- package/.gemini/hooks/session-end.sh +85 -0
- package/.gemini/hooks/session-start.sh +85 -0
- package/.pi/extensions/mirror-logger.ts +561 -0
- package/.pi/skills/mm-backup/SKILL.md +21 -0
- package/.pi/skills/mm-build/SKILL.md +1206 -0
- package/.pi/skills/mm-consolidate/SKILL.md +112 -0
- package/.pi/skills/mm-consult/SKILL.md +70 -0
- package/.pi/skills/mm-conversations/SKILL.md +16 -0
- package/.pi/skills/mm-discard/SKILL.md +39 -0
- package/.pi/skills/mm-explore/SKILL.md +255 -0
- package/.pi/skills/mm-help/SKILL.md +74 -0
- package/.pi/skills/mm-identity/SKILL.md +96 -0
- package/.pi/skills/mm-journal/SKILL.md +16 -0
- package/.pi/skills/mm-journey/SKILL.md +23 -0
- package/.pi/skills/mm-journeys/SKILL.md +15 -0
- package/.pi/skills/mm-memories/SKILL.md +17 -0
- package/.pi/skills/mm-mirror/SKILL.md +98 -0
- package/.pi/skills/mm-mute/SKILL.md +18 -0
- package/.pi/skills/mm-new/SKILL.md +18 -0
- package/.pi/skills/mm-recall/SKILL.md +17 -0
- package/.pi/skills/mm-release-notes/SKILL.md +52 -0
- package/.pi/skills/mm-seed/SKILL.md +19 -0
- package/.pi/skills/mm-shadow/SKILL.md +117 -0
- package/.pi/skills/mm-soul/SKILL.md +502 -0
- package/.pi/skills/mm-tasks/SKILL.md +24 -0
- package/.pi/skills/mm-update/SKILL.md +32 -0
- package/.pi/skills/mm-week/SKILL.md +28 -0
- package/.pi/skills/mm-welcome/SKILL.md +24 -0
- package/AGENTS.md +188 -0
- package/README.md +126 -9
- package/bin/loader.js +67 -0
- package/bin/mirror-hook.js +7 -0
- package/bin/mirror.js +7 -0
- package/docs/releases/index.md +99 -0
- package/docs/releases/pending-cutoffs.md +510 -0
- package/docs/releases/v0.10.0.md +123 -0
- package/docs/releases/v0.10.1.md +66 -0
- package/docs/releases/v0.10.10.md +51 -0
- package/docs/releases/v0.10.11.md +39 -0
- package/docs/releases/v0.10.12.md +57 -0
- package/docs/releases/v0.10.13.md +35 -0
- package/docs/releases/v0.10.14.md +42 -0
- package/docs/releases/v0.10.15.md +53 -0
- package/docs/releases/v0.10.16.md +35 -0
- package/docs/releases/v0.10.17.md +43 -0
- package/docs/releases/v0.10.2.md +64 -0
- package/docs/releases/v0.10.3.md +78 -0
- package/docs/releases/v0.10.4.md +42 -0
- package/docs/releases/v0.10.5.md +57 -0
- package/docs/releases/v0.10.6.md +47 -0
- package/docs/releases/v0.10.7.md +53 -0
- package/docs/releases/v0.10.8.md +49 -0
- package/docs/releases/v0.10.9.md +37 -0
- package/docs/releases/v0.11.0.md +86 -0
- package/docs/releases/v0.12.0.md +67 -0
- package/docs/releases/v0.13.0.md +64 -0
- package/docs/releases/v0.14.0.md +68 -0
- package/docs/releases/v0.15.0.md +48 -0
- package/docs/releases/v0.16.0.md +50 -0
- package/docs/releases/v0.16.1.md +55 -0
- package/docs/releases/v0.16.2.md +36 -0
- package/docs/releases/v0.17.0.md +50 -0
- package/docs/releases/v0.17.1.md +58 -0
- package/docs/releases/v0.17.2.md +42 -0
- package/docs/releases/v0.17.3.md +58 -0
- package/docs/releases/v0.18.0.md +84 -0
- package/docs/releases/v0.18.1.md +51 -0
- package/docs/releases/v0.18.2.md +69 -0
- package/docs/releases/v0.19.0.md +85 -0
- package/docs/releases/v0.19.1.md +46 -0
- package/docs/releases/v0.20.0.md +85 -0
- package/docs/releases/v0.21.0.md +61 -0
- package/docs/releases/v0.21.1.md +47 -0
- package/docs/releases/v0.22.0.md +77 -0
- package/docs/releases/v0.22.1.md +42 -0
- package/docs/releases/v0.23.0.md +105 -0
- package/docs/releases/v0.24.0.md +125 -0
- package/docs/releases/v0.25.0.md +107 -0
- package/docs/releases/v0.26.0.md +139 -0
- package/docs/releases/v0.26.1.md +65 -0
- package/docs/releases/v0.26.2.md +43 -0
- package/docs/releases/v0.27.0.md +63 -0
- package/docs/releases/v0.28.0.md +59 -0
- package/docs/releases/v0.29.0.md +61 -0
- package/docs/releases/v0.29.1.md +59 -0
- package/docs/releases/v0.29.2.md +59 -0
- package/docs/releases/v0.29.3.md +93 -0
- package/docs/releases/v0.30.0.md +75 -0
- package/docs/releases/v0.30.1.md +93 -0
- package/docs/releases/v0.30.2.md +66 -0
- package/docs/releases/v0.31.0.md +122 -0
- package/docs/releases/v0.31.1.md +142 -0
- package/docs/releases/v0.31.10.md +118 -0
- package/docs/releases/v0.31.11.md +88 -0
- package/docs/releases/v0.31.12.md +105 -0
- package/docs/releases/v0.31.13.md +99 -0
- package/docs/releases/v0.31.14.md +97 -0
- package/docs/releases/v0.31.2.md +115 -0
- package/docs/releases/v0.31.3.md +142 -0
- package/docs/releases/v0.31.4.md +117 -0
- package/docs/releases/v0.31.5.md +155 -0
- package/docs/releases/v0.31.6.md +132 -0
- package/docs/releases/v0.31.7.md +132 -0
- package/docs/releases/v0.31.8.md +120 -0
- package/docs/releases/v0.31.9.md +120 -0
- package/docs/releases/v0.32.0.md +261 -0
- package/docs/releases/v0.32.1.md +59 -0
- package/docs/releases/v0.8.0.md +118 -0
- package/docs/releases/v0.9.0.md +98 -0
- package/docs/releases/v0.9.1.md +83 -0
- package/package.json +102 -6
- package/plugins/mirror-mind/.claude-plugin/plugin.json +13 -0
- package/plugins/mirror-mind/hooks/hooks.json +50 -0
- package/plugins/mirror-mind/hooks/log-session-end.sh +98 -0
- package/plugins/mirror-mind/hooks/log-user-prompt.sh +98 -0
- package/plugins/mirror-mind/hooks/mirror-inject.sh +98 -0
- package/plugins/mirror-mind/hooks/session-start.sh +98 -0
- package/plugins/mirror-mind/mcp/launch.sh +52 -0
- package/plugins/mirror-mind/skills/mm-backup/SKILL.md +27 -0
- package/plugins/mirror-mind/skills/mm-build/SKILL.md +1206 -0
- package/plugins/mirror-mind/skills/mm-consolidate/SKILL.md +112 -0
- package/plugins/mirror-mind/skills/mm-consult/SKILL.md +83 -0
- package/plugins/mirror-mind/skills/mm-conversations/SKILL.md +17 -0
- package/plugins/mirror-mind/skills/mm-discard/SKILL.md +40 -0
- package/plugins/mirror-mind/skills/mm-explore/SKILL.md +241 -0
- package/plugins/mirror-mind/skills/mm-help/SKILL.md +104 -0
- package/plugins/mirror-mind/skills/mm-identity/SKILL.md +96 -0
- package/plugins/mirror-mind/skills/mm-journal/SKILL.md +21 -0
- package/plugins/mirror-mind/skills/mm-journey/SKILL.md +37 -0
- package/plugins/mirror-mind/skills/mm-journeys/SKILL.md +15 -0
- package/plugins/mirror-mind/skills/mm-memories/SKILL.md +20 -0
- package/plugins/mirror-mind/skills/mm-mirror/SKILL.md +65 -0
- package/plugins/mirror-mind/skills/mm-mute/SKILL.md +18 -0
- package/plugins/mirror-mind/skills/mm-new/SKILL.md +18 -0
- package/plugins/mirror-mind/skills/mm-recall/SKILL.md +17 -0
- package/plugins/mirror-mind/skills/mm-release-notes/SKILL.md +31 -0
- package/plugins/mirror-mind/skills/mm-seed/SKILL.md +15 -0
- package/plugins/mirror-mind/skills/mm-shadow/SKILL.md +117 -0
- package/plugins/mirror-mind/skills/mm-soul/SKILL.md +488 -0
- package/plugins/mirror-mind/skills/mm-tasks/SKILL.md +36 -0
- package/plugins/mirror-mind/skills/mm-update/SKILL.md +30 -0
- package/plugins/mirror-mind/skills/mm-week/SKILL.md +46 -0
- package/plugins/mirror-mind/skills/mm-welcome/SKILL.md +22 -0
- package/scripts/codex-hooks/session-end.sh +85 -0
- package/scripts/codex-hooks/session-start.sh +85 -0
- package/scripts/codex-mirror.sh +48 -0
- package/templates/identity/README.md +119 -0
- package/templates/identity/ego/behavior.yaml +52 -0
- package/templates/identity/ego/constraints.yaml +15 -0
- package/templates/identity/ego/identity.yaml +43 -0
- package/templates/identity/journeys/personal-growth.yaml +39 -0
- package/templates/identity/organization/identity.yaml +24 -0
- package/templates/identity/organization/principles.yaml +17 -0
- package/templates/identity/personas/coach.yaml +94 -0
- package/templates/identity/personas/designer.yaml +93 -0
- package/templates/identity/personas/doctor.yaml +96 -0
- package/templates/identity/personas/engineer.yaml +77 -0
- package/templates/identity/personas/financial.yaml +97 -0
- package/templates/identity/personas/prompt-engineer.yaml +122 -0
- package/templates/identity/personas/researcher.yaml +88 -0
- package/templates/identity/personas/strategist.yaml +91 -0
- package/templates/identity/personas/teacher.yaml +95 -0
- package/templates/identity/personas/therapist.yaml +91 -0
- package/templates/identity/personas/thinker.yaml +74 -0
- package/templates/identity/personas/writer.yaml +76 -0
- package/templates/identity/self/config.yaml +16 -0
- package/templates/identity/self/soul.yaml +105 -0
- package/templates/identity/user/identity.yaml +30 -0
- package/ts/src/backup/staging.ts +62 -0
- package/ts/src/backup/zipBackup.ts +267 -0
- package/ts/src/backup/zipReader.ts +109 -0
- package/ts/src/backup/zipWriter.ts +148 -0
- package/ts/src/builder/activeCheckpoint.ts +134 -0
- package/ts/src/builder/aggregateStatus.ts +34 -0
- package/ts/src/builder/approve.ts +149 -0
- package/ts/src/builder/argv.ts +292 -0
- package/ts/src/builder/ariadMethod.ts +536 -0
- package/ts/src/builder/artifacts/artifactSurfaces.ts +106 -0
- package/ts/src/builder/artifacts/artifactWriter.ts +113 -0
- package/ts/src/builder/artifacts/closureArtifacts.ts +199 -0
- package/ts/src/builder/artifacts/deliveryStoryArtifacts.ts +316 -0
- package/ts/src/builder/artifacts/planArtifacts.ts +242 -0
- package/ts/src/builder/artifacts/scaffoldSections.ts +273 -0
- package/ts/src/builder/artifacts/scaffoldState.ts +227 -0
- package/ts/src/builder/artifacts/storyFiles.ts +61 -0
- package/ts/src/builder/artifacts/storyIndex.ts +166 -0
- package/ts/src/builder/cadence.ts +59 -0
- package/ts/src/builder/card.ts +192 -0
- package/ts/src/builder/checkpointRefused.ts +66 -0
- package/ts/src/builder/closure.ts +964 -0
- package/ts/src/builder/commands.ts +2053 -0
- package/ts/src/builder/cursorPosition.ts +176 -0
- package/ts/src/builder/cursorTransitions.ts +77 -0
- package/ts/src/builder/deliveryCursor.ts +577 -0
- package/ts/src/builder/deliveryStoryClosure.ts +521 -0
- package/ts/src/builder/deliveryStoryPlan.ts +536 -0
- package/ts/src/builder/deliveryStoryReady.ts +160 -0
- package/ts/src/builder/deliveryStoryRoadmapClosure.ts +151 -0
- package/ts/src/builder/expand.ts +336 -0
- package/ts/src/builder/flowUnit.ts +339 -0
- package/ts/src/builder/homeSurface.ts +122 -0
- package/ts/src/builder/implementationGuard.ts +125 -0
- package/ts/src/builder/index.ts +11 -0
- package/ts/src/builder/lifecycleRefusal.ts +92 -0
- package/ts/src/builder/lifecycleRibbon.ts +159 -0
- package/ts/src/builder/load.ts +313 -0
- package/ts/src/builder/methodAdoption.ts +113 -0
- package/ts/src/builder/methodDefinition.ts +429 -0
- package/ts/src/builder/methodInspection.ts +371 -0
- package/ts/src/builder/plan.ts +408 -0
- package/ts/src/builder/planPreauthorization.ts +265 -0
- package/ts/src/builder/prepare.ts +225 -0
- package/ts/src/builder/projectPaths.ts +64 -0
- package/ts/src/builder/pull.ts +356 -0
- package/ts/src/builder/pullCandidates.ts +463 -0
- package/ts/src/builder/pullCandidatesRender.ts +238 -0
- package/ts/src/builder/refinementField.ts +93 -0
- package/ts/src/builder/releaseIntent.ts +173 -0
- package/ts/src/builder/resumeState.ts +84 -0
- package/ts/src/builder/resumeSurface.ts +149 -0
- package/ts/src/builder/roadmapGrammar.ts +300 -0
- package/ts/src/builder/roadmapScan.ts +97 -0
- package/ts/src/builder/roadmapScope.ts +210 -0
- package/ts/src/builder/scopePhrases.ts +146 -0
- package/ts/src/builder/storyPaths.ts +196 -0
- package/ts/src/builder/storyPlanPreauthorization.ts +287 -0
- package/ts/src/builder/surfaceProtocol.ts +42 -0
- package/ts/src/builder/templateGeneration.ts +148 -0
- package/ts/src/builder/transition.ts +178 -0
- package/ts/src/consult/args.ts +99 -0
- package/ts/src/consult/core.ts +122 -0
- package/ts/src/consult/modelCatalog.ts +55 -0
- package/ts/src/consult/render.ts +47 -0
- package/ts/src/conversation/append.ts +414 -0
- package/ts/src/conversation/backfill.ts +387 -0
- package/ts/src/conversation/closeTail.ts +467 -0
- package/ts/src/conversation/extraction.ts +333 -0
- package/ts/src/conversation/extractionDriver.ts +130 -0
- package/ts/src/conversation/extractionRun.ts +70 -0
- package/ts/src/conversation/journeyInference.ts +165 -0
- package/ts/src/conversation/journeyRepair.ts +114 -0
- package/ts/src/conversation/lifecycleFaces.ts +197 -0
- package/ts/src/conversation/lifecycleWrites.ts +313 -0
- package/ts/src/conversation/listing.ts +77 -0
- package/ts/src/conversation/logger.ts +576 -0
- package/ts/src/conversation/loggerCli.ts +282 -0
- package/ts/src/conversation/loggerRuntime.ts +259 -0
- package/ts/src/conversation/metadataLifecycle.ts +443 -0
- package/ts/src/conversation/recall.ts +66 -0
- package/ts/src/conversation/sessionComposites.ts +258 -0
- package/ts/src/conversation/sessionImport.ts +90 -0
- package/ts/src/conversation/transcriptBackfill.ts +141 -0
- package/ts/src/cultivation/applyActions.ts +241 -0
- package/ts/src/cultivation/cluster.ts +86 -0
- package/ts/src/cultivation/consolidationStore.ts +281 -0
- package/ts/src/cultivation/promptContext.ts +62 -0
- package/ts/src/cultivation/propose.ts +305 -0
- package/ts/src/cultivation/scan.ts +155 -0
- package/ts/src/db/backupGate.ts +47 -0
- package/ts/src/db/bootstrap.ts +108 -0
- package/ts/src/db/bootstrapLock.ts +223 -0
- package/ts/src/db/copyGuard.ts +60 -0
- package/ts/src/db/database.ts +397 -0
- package/ts/src/db/decode.ts +66 -0
- package/ts/src/db/ftsIntegrity.ts +41 -0
- package/ts/src/db/migrateOnOpen.ts +214 -0
- package/ts/src/db/migrations.ts +576 -0
- package/ts/src/db/readyOnOpen.ts +35 -0
- package/ts/src/db/rowDecode.ts +42 -0
- package/ts/src/db/schema.ts +432 -0
- package/ts/src/db/schemaInventory.ts +225 -0
- package/ts/src/db/schemaInventorySnapshot.ts +2107 -0
- package/ts/src/db/schemaState.ts +91 -0
- package/ts/src/db/schemaTsDivergence.ts +114 -0
- package/ts/src/descriptor/descriptorRead.ts +49 -0
- package/ts/src/docs/docsLint.ts +265 -0
- package/ts/src/explorer/handoff.ts +522 -0
- package/ts/src/explorer/render.ts +335 -0
- package/ts/src/explorer/story.ts +769 -0
- package/ts/src/explorer/transition.ts +72 -0
- package/ts/src/extensions/bindings.ts +170 -0
- package/ts/src/extensions/catalog.ts +249 -0
- package/ts/src/extensions/catalogCommands.ts +513 -0
- package/ts/src/extensions/catalogWrites.ts +652 -0
- package/ts/src/extensions/contextRuntime.ts +325 -0
- package/ts/src/extensions/dispatch.ts +374 -0
- package/ts/src/extensions/errors.ts +24 -0
- package/ts/src/extensions/manifest.ts +356 -0
- package/ts/src/extensions/migrations.ts +350 -0
- package/ts/src/extraction/conversation.ts +304 -0
- package/ts/src/extraction/conversationMetadata.ts +206 -0
- package/ts/src/extraction/fencing.ts +106 -0
- package/ts/src/extraction/json.ts +17 -0
- package/ts/src/extraction/prompts.ts +470 -0
- package/ts/src/frontDoor/args.ts +30 -0
- package/ts/src/frontDoor/argvShape.ts +106 -0
- package/ts/src/frontDoor/buildLoadRuntime.ts +139 -0
- package/ts/src/frontDoor/buildRoute.ts +398 -0
- package/ts/src/frontDoor/cli.ts +1865 -0
- package/ts/src/frontDoor/cloneRoleGuard.ts +98 -0
- package/ts/src/frontDoor/consultRoute.ts +50 -0
- package/ts/src/frontDoor/contentTailRoute.ts +239 -0
- package/ts/src/frontDoor/conversationLoggerRoute.ts +73 -0
- package/ts/src/frontDoor/cultivationRoute.ts +210 -0
- package/ts/src/frontDoor/dbPath.ts +131 -0
- package/ts/src/frontDoor/dbSafetyToolsRoute.ts +240 -0
- package/ts/src/frontDoor/exploreRoute.ts +572 -0
- package/ts/src/frontDoor/extensionCatalogRoute.ts +252 -0
- package/ts/src/frontDoor/frontDoorLog.ts +57 -0
- package/ts/src/frontDoor/identityWrite.ts +36 -0
- package/ts/src/frontDoor/journeyWriteRoute.ts +26 -0
- package/ts/src/frontDoor/lifecycleWriteRoute.ts +110 -0
- package/ts/src/frontDoor/liveBackup.ts +100 -0
- package/ts/src/frontDoor/mirrorModeRoute.ts +163 -0
- package/ts/src/frontDoor/namedRefusal.ts +35 -0
- package/ts/src/frontDoor/nodeSupport.ts +24 -0
- package/ts/src/frontDoor/render/consolidate.ts +199 -0
- package/ts/src/frontDoor/render/conversations.ts +33 -0
- package/ts/src/frontDoor/render/cultivationShared.ts +21 -0
- package/ts/src/frontDoor/render/descriptor.ts +20 -0
- package/ts/src/frontDoor/render/detectPersona.ts +50 -0
- package/ts/src/frontDoor/render/icons.ts +13 -0
- package/ts/src/frontDoor/render/identity.ts +59 -0
- package/ts/src/frontDoor/render/identityRows.ts +17 -0
- package/ts/src/frontDoor/render/inspectPersona.ts +53 -0
- package/ts/src/frontDoor/render/journeyStatus.ts +78 -0
- package/ts/src/frontDoor/render/journeys.ts +79 -0
- package/ts/src/frontDoor/render/list.ts +41 -0
- package/ts/src/frontDoor/render/memories.ts +67 -0
- package/ts/src/frontDoor/render/recall.ts +49 -0
- package/ts/src/frontDoor/render/shadow.ts +155 -0
- package/ts/src/frontDoor/render/tasks.ts +99 -0
- package/ts/src/frontDoor/render/tasksImportSync.ts +63 -0
- package/ts/src/frontDoor/render/week.ts +113 -0
- package/ts/src/frontDoor/routing.ts +999 -0
- package/ts/src/frontDoor/runtimeRoute.ts +623 -0
- package/ts/src/frontDoor/searchRoute.ts +166 -0
- package/ts/src/frontDoor/seedPaths.ts +98 -0
- package/ts/src/frontDoor/soulRoute.ts +433 -0
- package/ts/src/frontDoor/tasksImportSyncRoute.ts +110 -0
- package/ts/src/frontDoor/tasksWriteRoute.ts +101 -0
- package/ts/src/frontDoor/usage.ts +176 -0
- package/ts/src/guards/claudePlugin.ts +252 -0
- package/ts/src/guards/packContents.ts +306 -0
- package/ts/src/guards/piSourcedSkills.ts +70 -0
- package/ts/src/guards/retiredRouteShadows.ts +57 -0
- package/ts/src/guards/retiredSurfaces.ts +564 -0
- package/ts/src/hooks/claude.ts +176 -0
- package/ts/src/hooks/codex.ts +45 -0
- package/ts/src/hooks/gemini.ts +126 -0
- package/ts/src/hooks/main.ts +109 -0
- package/ts/src/hooks/mirrorState.ts +71 -0
- package/ts/src/hooks/payload.ts +68 -0
- package/ts/src/hooks/runtime.ts +159 -0
- package/ts/src/identity/applyConsolidationIdentityUpdate.ts +67 -0
- package/ts/src/identity/descriptorGenerate.ts +162 -0
- package/ts/src/identity/identityEdit.ts +143 -0
- package/ts/src/identity/identityKey.ts +77 -0
- package/ts/src/identity/identityRead.ts +69 -0
- package/ts/src/identity/identityStore.ts +80 -0
- package/ts/src/identity/journeyListing.ts +53 -0
- package/ts/src/identity/personaInspect.ts +44 -0
- package/ts/src/identity/personaListing.ts +37 -0
- package/ts/src/identity/setIdentity.ts +64 -0
- package/ts/src/index.ts +231 -0
- package/ts/src/init/init.ts +111 -0
- package/ts/src/journey/journeyOptions.ts +131 -0
- package/ts/src/journey/journeyRemoval.ts +158 -0
- package/ts/src/journey/journeyStatus.ts +149 -0
- package/ts/src/journey/journeySyncFile.ts +87 -0
- package/ts/src/journey/journeyWrite.ts +203 -0
- package/ts/src/journey/parentJourney.ts +41 -0
- package/ts/src/journey/validateParentJourney.ts +55 -0
- package/ts/src/mcp/boundary.ts +151 -0
- package/ts/src/mcp/guards.ts +244 -0
- package/ts/src/mcp/main.ts +121 -0
- package/ts/src/mcp/payload.ts +141 -0
- package/ts/src/mcp/protocol.ts +141 -0
- package/ts/src/mcp/readModels.ts +241 -0
- package/ts/src/mcp/registry.ts +201 -0
- package/ts/src/mcp/serve.ts +120 -0
- package/ts/src/mcp/tools/deterministic.ts +137 -0
- package/ts/src/mcp/tools/providerCrossing.ts +187 -0
- package/ts/src/mcp/wire.ts +8 -0
- package/ts/src/memory/journal.ts +180 -0
- package/ts/src/memory/listing.ts +107 -0
- package/ts/src/memory/memoryWrite.ts +88 -0
- package/ts/src/memory/reinforcement.ts +35 -0
- package/ts/src/mirror/context.ts +161 -0
- package/ts/src/mirror/defaultResolution.ts +199 -0
- package/ts/src/mirror/orchestration.ts +364 -0
- package/ts/src/mirror/reception.ts +194 -0
- package/ts/src/mirror/render.ts +75 -0
- package/ts/src/mirror/runtimeSession.ts +205 -0
- package/ts/src/mode/operatingMode.ts +148 -0
- package/ts/src/observability/callOutcome.ts +115 -0
- package/ts/src/observability/inspectLedger.ts +329 -0
- package/ts/src/observability/ledgerHooks.ts +126 -0
- package/ts/src/observability/llmCalls.ts +154 -0
- package/ts/src/persona/detectPersona.ts +81 -0
- package/ts/src/planning/promptAssembly.ts +111 -0
- package/ts/src/planning/weekPending.ts +81 -0
- package/ts/src/planning/weekPlan.ts +183 -0
- package/ts/src/planning/weekSave.ts +117 -0
- package/ts/src/providers/config.ts +189 -0
- package/ts/src/providers/cost.ts +64 -0
- package/ts/src/providers/credits.ts +204 -0
- package/ts/src/providers/embedding.ts +321 -0
- package/ts/src/providers/familyProviders.ts +147 -0
- package/ts/src/providers/llm.ts +334 -0
- package/ts/src/providers/openrouter.ts +244 -0
- package/ts/src/providers/redaction.ts +110 -0
- package/ts/src/providers/replay.ts +15 -0
- package/ts/src/providers/transport.ts +410 -0
- package/ts/src/repair/encodingRepair.ts +253 -0
- package/ts/src/runtime/backup.ts +151 -0
- package/ts/src/runtime/channel.ts +146 -0
- package/ts/src/runtime/config.ts +168 -0
- package/ts/src/runtime/diagnose.ts +686 -0
- package/ts/src/runtime/git.ts +510 -0
- package/ts/src/runtime/installKind.ts +220 -0
- package/ts/src/runtime/migrate.ts +154 -0
- package/ts/src/runtime/packageIdentity.ts +102 -0
- package/ts/src/runtime/releaseNotes.ts +244 -0
- package/ts/src/runtime/status.ts +557 -0
- package/ts/src/runtime/strategies/clone.ts +200 -0
- package/ts/src/runtime/strategies/package.ts +328 -0
- package/ts/src/runtime/treeRoot.ts +30 -0
- package/ts/src/runtime/update.ts +445 -0
- package/ts/src/runtime/updateGate.ts +40 -0
- package/ts/src/runtime/updatePipeline.ts +159 -0
- package/ts/src/runtime/version.ts +36 -0
- package/ts/src/runtime/wiring.ts +130 -0
- package/ts/src/search/memorySearch.ts +338 -0
- package/ts/src/search/ranker.ts +154 -0
- package/ts/src/seed/seed.ts +256 -0
- package/ts/src/seed/seedYaml.ts +133 -0
- package/ts/src/soul/apply.ts +218 -0
- package/ts/src/soul/harvest.ts +267 -0
- package/ts/src/soul/prompts/soul_beauty_voice.md +103 -0
- package/ts/src/soul/prompts/soul_self_voice.md +219 -0
- package/ts/src/soul/prompts/soul_wisdom_voice.md +121 -0
- package/ts/src/soul/prompts.ts +85 -0
- package/ts/src/soul/render.ts +358 -0
- package/ts/src/soul/state.ts +212 -0
- package/ts/src/soul/transition.ts +37 -0
- package/ts/src/tasks/journeyPathParse.ts +113 -0
- package/ts/src/tasks/taskImportSync.ts +142 -0
- package/ts/src/tasks/taskStore.ts +312 -0
- package/ts/src/tasks/weekView.ts +120 -0
- package/ts/src/util/clipCodePoints.ts +26 -0
- package/ts/src/util/paths.ts +63 -0
- package/ts/src/util/program.ts +9 -0
- package/ts/src/util/pyGenerators.ts +144 -0
- package/ts/src/util/pythonText.ts +317 -0
- package/ts/src/util/semver.ts +45 -0
- package/ts/src/util/shellWord.ts +25 -0
- package/ts/src/util/slug.ts +64 -0
- package/ts/src/welcome/card.ts +339 -0
- package/ts/src/welcome/statusLine.ts +101 -0
- package/ts/src/welcome/updateCache.ts +129 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
// The extension catalog WRITES (CV22.DS7.TS4 plateau 5).
|
|
2
|
+
//
|
|
3
|
+
// Port of `sync_extensions_for_runtime`, `install_extension`,
|
|
4
|
+
// `uninstall_extension`, `expose_claude_runtime_skills`, and
|
|
5
|
+
// `cleanup_claude_runtime_skills` from `src/memory/cli/extensions.py`.
|
|
6
|
+
//
|
|
7
|
+
// These are the first commands in the family that touch the filesystem, and
|
|
8
|
+
// the corpus grades them by what they LEAVE ON DISK -- every file in the mirror
|
|
9
|
+
// home, the runtime target root, and the Claude project root -- not by what
|
|
10
|
+
// they print. Four measured facts shape the port:
|
|
11
|
+
//
|
|
12
|
+
// 1. **The catalog is written with Python's `json.dumps(doc, indent=2)`, so
|
|
13
|
+
// non-ASCII is ESCAPED.** A summary reading `Café notes — a fixture` lands
|
|
14
|
+
// as `Caf\u00e9 notes \u2014 a fixture`. `JSON.stringify` would write raw
|
|
15
|
+
// UTF-8 and silently change a file both cores read. And the key order is
|
|
16
|
+
// insertion order, not sorted.
|
|
17
|
+
// 2. **A Claude command name contains `:`, which is illegal on Windows**, so
|
|
18
|
+
// filesystem names go through `filesystemSkillDirName` while the CATALOG
|
|
19
|
+
// keeps the runtime command unchanged. `ext:notes` lives in `ext-notes/`.
|
|
20
|
+
// 3. **Install identifies an extension by its DIRECTORY NAME**, never by the
|
|
21
|
+
// manifest id. An extension in `ext-mismatch/` whose manifest says
|
|
22
|
+
// `id: mismatch` has its migrations checked against the prefix
|
|
23
|
+
// `ext_ext_mismatch_` and fails. Reading the id from the manifest would
|
|
24
|
+
// "fix" a behavior the installed world depends on.
|
|
25
|
+
// 4. **Install rebuilds each runtime catalog from EVERY installed
|
|
26
|
+
// extension**, not from the one being installed, while the printed report
|
|
27
|
+
// stays focused on that one. Feeding the writer a single manifest would
|
|
28
|
+
// drop every other extension from the catalog Pi and Claude discover
|
|
29
|
+
// through, leaving their SKILL.md files orphaned on disk.
|
|
30
|
+
|
|
31
|
+
import {
|
|
32
|
+
copyFileSync,
|
|
33
|
+
cpSync,
|
|
34
|
+
existsSync,
|
|
35
|
+
lstatSync,
|
|
36
|
+
mkdirSync,
|
|
37
|
+
readdirSync,
|
|
38
|
+
readFileSync,
|
|
39
|
+
realpathSync,
|
|
40
|
+
rmSync,
|
|
41
|
+
writeFileSync,
|
|
42
|
+
} from "node:fs";
|
|
43
|
+
import { basename, dirname, join } from "node:path";
|
|
44
|
+
import type { WritableDatabase } from "#db/database.ts";
|
|
45
|
+
import { PROGRAM } from "#util/program.ts";
|
|
46
|
+
import { pythonJsonDumpsIndentedOrdered } from "#util/pyGenerators.ts";
|
|
47
|
+
import type { BindingDeps } from "./bindings.ts";
|
|
48
|
+
import {
|
|
49
|
+
discoverExtensions,
|
|
50
|
+
extensionsRootForHome,
|
|
51
|
+
type RenderedCommand,
|
|
52
|
+
runtimeSkillsRootForHome,
|
|
53
|
+
} from "./catalog.ts";
|
|
54
|
+
import { readProvidersWithoutRuntime } from "./contextRuntime.ts";
|
|
55
|
+
import { readSubcommandListing } from "./dispatch.ts";
|
|
56
|
+
import { ExtensionError, ExtensionValidationError } from "./errors.ts";
|
|
57
|
+
import { type ExtensionManifest, loadExtensionManifest } from "./manifest.ts";
|
|
58
|
+
import { runMigrations } from "./migrations.ts";
|
|
59
|
+
|
|
60
|
+
/** Port of `_COPY_IGNORE_PATTERNS`: never shipped inside an installed tree. */
|
|
61
|
+
const COPY_IGNORE_EXACT = new Set([
|
|
62
|
+
".git",
|
|
63
|
+
"__pycache__",
|
|
64
|
+
".venv",
|
|
65
|
+
".pytest_cache",
|
|
66
|
+
".ruff_cache",
|
|
67
|
+
".mypy_cache",
|
|
68
|
+
"node_modules",
|
|
69
|
+
".DS_Store",
|
|
70
|
+
]);
|
|
71
|
+
const COPY_IGNORE_SUFFIX = ".pyc";
|
|
72
|
+
|
|
73
|
+
// A port of Python's `re.sub(r'[<>:"/\\|?*\x00-\x1f]', '-', ...)`, the rule that
|
|
74
|
+
// keeps a runtime command name legal as a Windows path segment. Dropping the
|
|
75
|
+
// control range would let a control character reach a directory name and
|
|
76
|
+
// diverge from the oracle on the one platform this guard exists for.
|
|
77
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: the control range is the ported rule
|
|
78
|
+
const ILLEGAL_PATH_CHARACTERS = /[<>:"/\\|?*\u0000-\u001f]/g;
|
|
79
|
+
|
|
80
|
+
export interface CatalogEntry {
|
|
81
|
+
id: string;
|
|
82
|
+
name: string;
|
|
83
|
+
category: string;
|
|
84
|
+
kind: string;
|
|
85
|
+
summary: string;
|
|
86
|
+
runtime: string;
|
|
87
|
+
command_name: string;
|
|
88
|
+
source_extension_dir: string;
|
|
89
|
+
manifest_path: string;
|
|
90
|
+
source_skill_path: string;
|
|
91
|
+
installed_skill_path: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Port of `_filesystem_skill_dir_name`. */
|
|
95
|
+
export function filesystemSkillDirName(commandName: string): string {
|
|
96
|
+
const safe = pyStripSpacesAndDots(commandName.replace(ILLEGAL_PATH_CHARACTERS, "-"));
|
|
97
|
+
return safe || "extension";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Python's `str.strip(" .")`: both ends, any mix of the two characters. */
|
|
101
|
+
function pyStripSpacesAndDots(text: string): string {
|
|
102
|
+
return text.replace(/^[ .]+/, "").replace(/[ .]+$/, "");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Port of `_legacy_filesystem_skill_dir_names`. */
|
|
106
|
+
export function legacyFilesystemSkillDirNames(commandName: string): string[] {
|
|
107
|
+
const safe = filesystemSkillDirName(commandName);
|
|
108
|
+
return commandName === safe ? [] : [commandName];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Port of `_skill_markdown_declares_command`. */
|
|
112
|
+
function skillMarkdownDeclaresCommand(skillPath: string, commandName: string): boolean {
|
|
113
|
+
let content: string;
|
|
114
|
+
try {
|
|
115
|
+
content = readFileSync(skillPath, "utf8");
|
|
116
|
+
} catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
const names = new Set([commandName, filesystemSkillDirName(commandName)]);
|
|
120
|
+
for (const name of names) {
|
|
121
|
+
for (const marker of [`name: "${name}"`, `name: '${name}'`, `name: ${name}`]) {
|
|
122
|
+
if (content.includes(marker)) return true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Port of `_remove_legacy_skill_dir_if_owned`.
|
|
130
|
+
*
|
|
131
|
+
* Deliberately conservative, and the conservatism is the point: it deletes a
|
|
132
|
+
* directory named after the raw command only when the SKILL.md inside declares
|
|
133
|
+
* that same command. A `ext:notes/` directory a human wrote for something else
|
|
134
|
+
* is left alone.
|
|
135
|
+
*/
|
|
136
|
+
export function removeLegacySkillDirIfOwned(root: string, commandName: string): string[] {
|
|
137
|
+
const removed: string[] = [];
|
|
138
|
+
for (const legacyName of legacyFilesystemSkillDirNames(commandName)) {
|
|
139
|
+
const legacyDir = join(root, legacyName);
|
|
140
|
+
if (!isDirectory(legacyDir)) continue;
|
|
141
|
+
if (!skillMarkdownDeclaresCommand(join(legacyDir, "SKILL.md"), commandName)) continue;
|
|
142
|
+
rmSync(legacyDir, { recursive: true, force: true });
|
|
143
|
+
removed.push(legacyDir);
|
|
144
|
+
}
|
|
145
|
+
return removed;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isDirectory(path: string): boolean {
|
|
149
|
+
try {
|
|
150
|
+
return lstatSync(path).isDirectory();
|
|
151
|
+
} catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Port of `_catalog_entry_for_manifest`. */
|
|
157
|
+
function catalogEntryForManifest(
|
|
158
|
+
manifest: ExtensionManifest,
|
|
159
|
+
runtime: string,
|
|
160
|
+
targetSkillPath: string,
|
|
161
|
+
): CatalogEntry {
|
|
162
|
+
const runtimeData = manifest.runtimes[runtime];
|
|
163
|
+
return {
|
|
164
|
+
id: manifest.id,
|
|
165
|
+
name: manifest.name,
|
|
166
|
+
category: manifest.category,
|
|
167
|
+
kind: manifest.kind,
|
|
168
|
+
summary: manifest.summary,
|
|
169
|
+
runtime,
|
|
170
|
+
command_name: runtimeData?.commandName ?? "",
|
|
171
|
+
source_extension_dir: manifest.root,
|
|
172
|
+
manifest_path: manifest.manifestPath,
|
|
173
|
+
source_skill_path: runtimeData?.skillPath ?? "",
|
|
174
|
+
installed_skill_path: targetSkillPath,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Port of `_write_catalog` + `_catalog_document`. */
|
|
179
|
+
function writeCatalog(
|
|
180
|
+
path: string,
|
|
181
|
+
runtime: string,
|
|
182
|
+
targetRoot: string,
|
|
183
|
+
items: readonly CatalogEntry[],
|
|
184
|
+
deps: BindingDeps,
|
|
185
|
+
): void {
|
|
186
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
187
|
+
const document = {
|
|
188
|
+
schema_version: "1",
|
|
189
|
+
runtime,
|
|
190
|
+
target_root: targetRoot,
|
|
191
|
+
generated_at: deps.nowIso(),
|
|
192
|
+
extensions: items,
|
|
193
|
+
};
|
|
194
|
+
writeFileSync(path, `${pythonJsonDumpsIndentedOrdered(document)}\n`, "utf8");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Port of `_load_catalog`: a missing or corrupt catalog is an EMPTY one. */
|
|
198
|
+
function loadCatalogItems(path: string): unknown[] {
|
|
199
|
+
if (!existsSync(path)) return [];
|
|
200
|
+
let data: unknown;
|
|
201
|
+
try {
|
|
202
|
+
data = JSON.parse(readFileSync(path, "utf8"));
|
|
203
|
+
} catch {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
if (Array.isArray(data)) return data;
|
|
207
|
+
if (typeof data !== "object" || data === null) return [];
|
|
208
|
+
const extensions = (data as Record<string, unknown>).extensions;
|
|
209
|
+
return Array.isArray(extensions) ? extensions : [];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Port of `_prune_catalog_for_extension`. */
|
|
213
|
+
function pruneCatalogForExtension(
|
|
214
|
+
catalogPath: string,
|
|
215
|
+
extensionId: string,
|
|
216
|
+
runtime: string,
|
|
217
|
+
targetRoot: string,
|
|
218
|
+
deps: BindingDeps,
|
|
219
|
+
): void {
|
|
220
|
+
const items = loadCatalogItems(catalogPath).filter(
|
|
221
|
+
(item) =>
|
|
222
|
+
typeof item === "object" &&
|
|
223
|
+
item !== null &&
|
|
224
|
+
!Array.isArray(item) &&
|
|
225
|
+
(item as Record<string, unknown>).id !== extensionId,
|
|
226
|
+
) as CatalogEntry[];
|
|
227
|
+
writeCatalog(catalogPath, runtime, targetRoot, items, deps);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Port of `sync_extensions_for_runtime`. */
|
|
231
|
+
export function syncExtensionsForRuntime(
|
|
232
|
+
manifests: readonly ExtensionManifest[],
|
|
233
|
+
runtime: string,
|
|
234
|
+
targetRoot: string,
|
|
235
|
+
deps: BindingDeps,
|
|
236
|
+
): CatalogEntry[] {
|
|
237
|
+
mkdirSync(targetRoot, { recursive: true });
|
|
238
|
+
const synced: CatalogEntry[] = [];
|
|
239
|
+
for (const manifest of manifests) {
|
|
240
|
+
const runtimeData = manifest.runtimes[runtime];
|
|
241
|
+
if (!runtimeData) continue;
|
|
242
|
+
const skillPath = runtimeData.skillPath;
|
|
243
|
+
if (!skillPath) continue;
|
|
244
|
+
const commandName = runtimeData.commandName;
|
|
245
|
+
const targetDir = join(targetRoot, filesystemSkillDirName(commandName));
|
|
246
|
+
mkdirSync(targetDir, { recursive: true });
|
|
247
|
+
const targetSkillPath = join(targetDir, "SKILL.md");
|
|
248
|
+
copyFileSync(skillPath, targetSkillPath);
|
|
249
|
+
removeLegacySkillDirIfOwned(targetRoot, commandName);
|
|
250
|
+
synced.push(catalogEntryForManifest(manifest, runtime, targetSkillPath));
|
|
251
|
+
}
|
|
252
|
+
writeCatalog(join(targetRoot, "extensions.json"), runtime, targetRoot, synced, deps);
|
|
253
|
+
return synced;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Port of `_should_copy_source_tree`.
|
|
258
|
+
*
|
|
259
|
+
* The symlink branch is a refusal, not an optimization: the production layout
|
|
260
|
+
* links an installed extension straight at its source repository, and
|
|
261
|
+
* `copytree(dirs_exist_ok=True)` writes THROUGH such a link, mutating a
|
|
262
|
+
* directory the user never asked to change.
|
|
263
|
+
*/
|
|
264
|
+
export function shouldCopySourceTree(sourceDir: string, targetDir: string): boolean {
|
|
265
|
+
if (existsSync(targetDir) && realpathSync(sourceDir) === realpathSync(targetDir)) return false;
|
|
266
|
+
if (isSymlink(targetDir)) {
|
|
267
|
+
throw new ExtensionValidationError(
|
|
268
|
+
`installed extension path is a symlink pointing outside the install ` +
|
|
269
|
+
`source: ${targetDir} -> ${realpathSync(targetDir)}. Refusing to install ` +
|
|
270
|
+
`through it, which would modify the link target. Remove the symlink ` +
|
|
271
|
+
`first, or run \`${PROGRAM} ext ${basename(targetDir)} migrate\` to ` +
|
|
272
|
+
`re-run migrations without copying.`,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function isSymlink(path: string): boolean {
|
|
279
|
+
try {
|
|
280
|
+
return lstatSync(path).isSymbolicLink();
|
|
281
|
+
} catch {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Port of `shutil.copytree(..., ignore=ignore_patterns(*_COPY_IGNORE_PATTERNS))`. */
|
|
287
|
+
function copyTreeIgnoring(sourceDir: string, targetDir: string): void {
|
|
288
|
+
cpSync(sourceDir, targetDir, {
|
|
289
|
+
recursive: true,
|
|
290
|
+
force: true,
|
|
291
|
+
filter: (source) => {
|
|
292
|
+
const name = basename(source);
|
|
293
|
+
return !COPY_IGNORE_EXACT.has(name) && !name.endsWith(COPY_IGNORE_SUFFIX);
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export interface InstallReport {
|
|
299
|
+
extensionId: string;
|
|
300
|
+
sourceDir: string;
|
|
301
|
+
installedDir: string;
|
|
302
|
+
synced: Array<readonly [string, CatalogEntry[]]>;
|
|
303
|
+
migrationsApplied: number;
|
|
304
|
+
/**
|
|
305
|
+
* Capabilities the manifest documents but declares no runtime for.
|
|
306
|
+
*
|
|
307
|
+
* Install SUCCEEDS with these present. A skill-only extension, or one being
|
|
308
|
+
* migrated a capability at a time, is legal -- refusing here would block the
|
|
309
|
+
* very incremental migration CV22.DS10.TS2 exists to enable. The report says
|
|
310
|
+
* so out loud instead, because the alternative is a user discovering it at
|
|
311
|
+
* the first refusal a week later.
|
|
312
|
+
*/
|
|
313
|
+
unmigrated: string[];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export interface InstallOptions {
|
|
317
|
+
extensionId: string;
|
|
318
|
+
sourceRoot: string;
|
|
319
|
+
mirrorHome: string;
|
|
320
|
+
runtime: string | null;
|
|
321
|
+
db: WritableDatabase;
|
|
322
|
+
deps: BindingDeps;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Port of `install_extension` + `_post_install_command_skill`. */
|
|
326
|
+
export function installExtension(options: InstallOptions): InstallReport {
|
|
327
|
+
const { extensionId, mirrorHome, deps } = options;
|
|
328
|
+
const sourceDir = join(options.sourceRoot, extensionId);
|
|
329
|
+
loadExtensionManifest(sourceDir);
|
|
330
|
+
|
|
331
|
+
const targetExtensionsRoot = extensionsRootForHome(mirrorHome);
|
|
332
|
+
const targetExtensionDir = join(targetExtensionsRoot, extensionId);
|
|
333
|
+
mkdirSync(targetExtensionsRoot, { recursive: true });
|
|
334
|
+
if (shouldCopySourceTree(sourceDir, targetExtensionDir)) {
|
|
335
|
+
copyTreeIgnoring(sourceDir, targetExtensionDir);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const installedManifest = loadExtensionManifest(targetExtensionDir);
|
|
339
|
+
const runtimes =
|
|
340
|
+
options.runtime !== null ? [options.runtime] : Object.keys(installedManifest.runtimes).sort();
|
|
341
|
+
|
|
342
|
+
// A prompt-skill has no Python at all: no migrations, no register.
|
|
343
|
+
let migrationsApplied = 0;
|
|
344
|
+
if (installedManifest.kind === "command-skill") {
|
|
345
|
+
try {
|
|
346
|
+
migrationsApplied = runMigrations(
|
|
347
|
+
options.db,
|
|
348
|
+
extensionId,
|
|
349
|
+
join(targetExtensionDir, "migrations"),
|
|
350
|
+
deps,
|
|
351
|
+
);
|
|
352
|
+
} catch (error) {
|
|
353
|
+
if (!(error instanceof ExtensionError)) throw error;
|
|
354
|
+
throw new ExtensionValidationError(
|
|
355
|
+
`migrations failed for extension/${extensionId}: ${error.message}`,
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Fact 4: rebuild from everything installed, report only this one.
|
|
361
|
+
const installed = discoverExtensions(targetExtensionsRoot).manifests;
|
|
362
|
+
const synced: Array<readonly [string, CatalogEntry[]]> = [];
|
|
363
|
+
for (const runtimeName of runtimes) {
|
|
364
|
+
const runtimeTargetRoot = runtimeSkillsRootForHome(mirrorHome, runtimeName);
|
|
365
|
+
const all = syncExtensionsForRuntime(installed, runtimeName, runtimeTargetRoot, deps);
|
|
366
|
+
synced.push([runtimeName, all.filter((entry) => entry.id === extensionId)] as const);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
extensionId,
|
|
371
|
+
sourceDir,
|
|
372
|
+
installedDir: targetExtensionDir,
|
|
373
|
+
synced,
|
|
374
|
+
migrationsApplied,
|
|
375
|
+
unmigrated: unmigratedCapabilities(targetExtensionDir),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* What still needs a runtime, in one list, labelled by kind.
|
|
381
|
+
*
|
|
382
|
+
* Each module reads its own manifest section -- `dispatch` owns
|
|
383
|
+
* `cli.subcommands[]`, `contextRuntime` owns `mirror_context_providers[]` --
|
|
384
|
+
* and install merely composes them. Putting a second manifest parser here is
|
|
385
|
+
* how the three readers would drift.
|
|
386
|
+
*/
|
|
387
|
+
function unmigratedCapabilities(extensionDir: string): string[] {
|
|
388
|
+
const subcommands = readSubcommandListing(extensionDir)
|
|
389
|
+
.filter((entry) => !entry.hasRuntime)
|
|
390
|
+
.map((entry) => `subcommand '${entry.name}'`);
|
|
391
|
+
const providers = readProvidersWithoutRuntime(extensionDir).map(
|
|
392
|
+
(id) => `context provider '${id}'`,
|
|
393
|
+
);
|
|
394
|
+
return [...subcommands, ...providers];
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export interface UninstallReport {
|
|
398
|
+
extensionId: string;
|
|
399
|
+
installedDir: string;
|
|
400
|
+
bindingsRemoved: number;
|
|
401
|
+
removed: Array<readonly [string, string[]]>;
|
|
402
|
+
sourceRemoved: boolean;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** Port of `uninstall_extension`. */
|
|
406
|
+
export function uninstallExtension(options: {
|
|
407
|
+
extensionId: string;
|
|
408
|
+
mirrorHome: string;
|
|
409
|
+
runtime: string | null;
|
|
410
|
+
db: WritableDatabase;
|
|
411
|
+
deps: BindingDeps;
|
|
412
|
+
}): UninstallReport {
|
|
413
|
+
const { extensionId, mirrorHome, deps } = options;
|
|
414
|
+
const installedDir = join(extensionsRootForHome(mirrorHome), extensionId);
|
|
415
|
+
if (!existsSync(installedDir)) {
|
|
416
|
+
throw new ExtensionValidationError(`installed extension not found: ${installedDir}`);
|
|
417
|
+
}
|
|
418
|
+
const manifest = loadExtensionManifest(installedDir);
|
|
419
|
+
const runtimes =
|
|
420
|
+
options.runtime !== null ? [options.runtime] : Object.keys(manifest.runtimes).sort();
|
|
421
|
+
|
|
422
|
+
const removed: Array<readonly [string, string[]]> = [];
|
|
423
|
+
for (const runtimeName of runtimes) {
|
|
424
|
+
const runtimeData = manifest.runtimes[runtimeName];
|
|
425
|
+
if (!runtimeData) {
|
|
426
|
+
removed.push([runtimeName, []] as const);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
const runtimeRoot = runtimeSkillsRootForHome(mirrorHome, runtimeName);
|
|
430
|
+
const commandName = runtimeData.commandName;
|
|
431
|
+
const targetDirs = [
|
|
432
|
+
join(runtimeRoot, filesystemSkillDirName(commandName)),
|
|
433
|
+
...legacyFilesystemSkillDirNames(commandName).map((name) => join(runtimeRoot, name)),
|
|
434
|
+
];
|
|
435
|
+
const removedPaths: string[] = [];
|
|
436
|
+
for (const targetDir of targetDirs) {
|
|
437
|
+
if (!existsSync(targetDir)) continue;
|
|
438
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
439
|
+
removedPaths.push(targetDir);
|
|
440
|
+
}
|
|
441
|
+
pruneCatalogForExtension(
|
|
442
|
+
join(runtimeRoot, "extensions.json"),
|
|
443
|
+
extensionId,
|
|
444
|
+
runtimeName,
|
|
445
|
+
runtimeRoot,
|
|
446
|
+
deps,
|
|
447
|
+
);
|
|
448
|
+
removed.push([runtimeName, removedPaths] as const);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// D4: the code goes, the DATA stays. Bindings cannot resolve without the
|
|
452
|
+
// source tree, so they are deleted; the extension's own `ext_<id>_*` tables
|
|
453
|
+
// are deliberately preserved for a future purge command to decide about.
|
|
454
|
+
let bindingsRemoved = 0;
|
|
455
|
+
if (options.runtime === null) {
|
|
456
|
+
if (manifest.kind === "command-skill") {
|
|
457
|
+
const result = options.db
|
|
458
|
+
.prepare("DELETE FROM _ext_bindings WHERE extension_id = ?")
|
|
459
|
+
.run(extensionId);
|
|
460
|
+
bindingsRemoved = Number(result.changes) || 0;
|
|
461
|
+
}
|
|
462
|
+
rmSync(installedDir, { recursive: true, force: true });
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
extensionId,
|
|
467
|
+
installedDir,
|
|
468
|
+
bindingsRemoved,
|
|
469
|
+
removed,
|
|
470
|
+
sourceRemoved: options.runtime === null,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export interface ClaudeCleanupReport {
|
|
475
|
+
projectRoot: string;
|
|
476
|
+
claudeSkillsRoot: string;
|
|
477
|
+
overlayCatalogPath: string;
|
|
478
|
+
removed: string[];
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export interface ClaudeExposeReport extends ClaudeCleanupReport {
|
|
482
|
+
exposed: Array<{ commandName: string; sourceSkillPath: string; targetSkillPath: string }>;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function claudeSkillsRootFor(projectRoot: string): string {
|
|
486
|
+
return join(projectRoot, ".claude", "skills");
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** Port of `_load_claude_overlay_catalog`: a LIST, or nothing. */
|
|
490
|
+
function loadOverlayCatalog(path: string): unknown[] {
|
|
491
|
+
if (!existsSync(path)) return [];
|
|
492
|
+
try {
|
|
493
|
+
const data = JSON.parse(readFileSync(path, "utf8"));
|
|
494
|
+
return Array.isArray(data) ? data : [];
|
|
495
|
+
} catch {
|
|
496
|
+
return [];
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/** Port of `cleanup_claude_runtime_skills`. */
|
|
501
|
+
export function cleanupClaudeRuntimeSkills(projectRoot: string): ClaudeCleanupReport {
|
|
502
|
+
const claudeSkillsRoot = claudeSkillsRootFor(projectRoot);
|
|
503
|
+
const overlayCatalogPath = join(claudeSkillsRoot, "extensions.external.json");
|
|
504
|
+
const removed: string[] = [];
|
|
505
|
+
|
|
506
|
+
for (const item of loadOverlayCatalog(overlayCatalogPath)) {
|
|
507
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) continue;
|
|
508
|
+
const targetSkillPath = (item as Record<string, unknown>).target_skill_path;
|
|
509
|
+
if (typeof targetSkillPath !== "string") continue;
|
|
510
|
+
if (existsSync(targetSkillPath)) {
|
|
511
|
+
rmSync(targetSkillPath);
|
|
512
|
+
removed.push(targetSkillPath);
|
|
513
|
+
}
|
|
514
|
+
// Only an EMPTY parent is removed: a directory holding anything else is
|
|
515
|
+
// not ours to delete.
|
|
516
|
+
const parent = dirname(targetSkillPath);
|
|
517
|
+
if (existsSync(parent) && readdirSync(parent).length === 0) rmSync(parent, { recursive: true });
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (existsSync(overlayCatalogPath)) rmSync(overlayCatalogPath);
|
|
521
|
+
|
|
522
|
+
return { projectRoot, claudeSkillsRoot, overlayCatalogPath, removed };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/** Port of `expose_claude_runtime_skills`. */
|
|
526
|
+
export function exposeClaudeRuntimeSkills(
|
|
527
|
+
projectRoot: string,
|
|
528
|
+
catalog: { extensions: unknown[] },
|
|
529
|
+
): ClaudeExposeReport {
|
|
530
|
+
const claudeSkillsRoot = claudeSkillsRootFor(projectRoot);
|
|
531
|
+
mkdirSync(claudeSkillsRoot, { recursive: true });
|
|
532
|
+
const cleanup = cleanupClaudeRuntimeSkills(projectRoot);
|
|
533
|
+
|
|
534
|
+
const exposed: ClaudeExposeReport["exposed"] = [];
|
|
535
|
+
for (const item of catalog.extensions) {
|
|
536
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) continue;
|
|
537
|
+
const record = item as Record<string, unknown>;
|
|
538
|
+
const commandName = record.command_name;
|
|
539
|
+
const installedSkillPath = record.installed_skill_path;
|
|
540
|
+
if (typeof commandName !== "string" || typeof installedSkillPath !== "string") continue;
|
|
541
|
+
if (!existsSync(installedSkillPath)) continue;
|
|
542
|
+
const targetDir = join(claudeSkillsRoot, filesystemSkillDirName(commandName));
|
|
543
|
+
mkdirSync(targetDir, { recursive: true });
|
|
544
|
+
const targetSkillPath = join(targetDir, "SKILL.md");
|
|
545
|
+
copyFileSync(installedSkillPath, targetSkillPath);
|
|
546
|
+
cleanup.removed.push(...removeLegacySkillDirIfOwned(claudeSkillsRoot, commandName));
|
|
547
|
+
exposed.push({ commandName, sourceSkillPath: installedSkillPath, targetSkillPath });
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const overlayCatalogPath = join(claudeSkillsRoot, "extensions.external.json");
|
|
551
|
+
writeFileSync(
|
|
552
|
+
overlayCatalogPath,
|
|
553
|
+
`${pythonJsonDumpsIndentedOrdered(
|
|
554
|
+
exposed.map((entry) => ({
|
|
555
|
+
command_name: entry.commandName,
|
|
556
|
+
source_skill_path: entry.sourceSkillPath,
|
|
557
|
+
target_skill_path: entry.targetSkillPath,
|
|
558
|
+
})),
|
|
559
|
+
)}\n`,
|
|
560
|
+
"utf8",
|
|
561
|
+
);
|
|
562
|
+
return {
|
|
563
|
+
projectRoot,
|
|
564
|
+
claudeSkillsRoot,
|
|
565
|
+
overlayCatalogPath,
|
|
566
|
+
removed: cleanup.removed,
|
|
567
|
+
exposed,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/** Port of `load_runtime_catalog`: the STRICT reader, which refuses. */
|
|
572
|
+
export function loadRuntimeCatalog(runtime: string, mirrorHome: string): { extensions: unknown[] } {
|
|
573
|
+
const catalogPath = join(runtimeSkillsRootForHome(mirrorHome, runtime), "extensions.json");
|
|
574
|
+
if (!existsSync(catalogPath)) {
|
|
575
|
+
throw new ExtensionValidationError(`runtime catalog not found: ${catalogPath}`);
|
|
576
|
+
}
|
|
577
|
+
let parsed: unknown = {};
|
|
578
|
+
try {
|
|
579
|
+
parsed = JSON.parse(readFileSync(catalogPath, "utf8"));
|
|
580
|
+
} catch {
|
|
581
|
+
parsed = { schema_version: "1", extensions: [] };
|
|
582
|
+
}
|
|
583
|
+
const catalog = (
|
|
584
|
+
Array.isArray(parsed)
|
|
585
|
+
? { schema_version: "0", extensions: parsed }
|
|
586
|
+
: typeof parsed === "object" && parsed !== null
|
|
587
|
+
? (parsed as Record<string, unknown>)
|
|
588
|
+
: { schema_version: "1", extensions: [] }
|
|
589
|
+
) as Record<string, unknown>;
|
|
590
|
+
if (catalog.schema_version !== "1") {
|
|
591
|
+
throw new ExtensionValidationError(
|
|
592
|
+
`unsupported runtime catalog schema '${String(catalog.schema_version)}' in ${catalogPath}`,
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
if (catalog.runtime !== runtime) {
|
|
596
|
+
throw new ExtensionValidationError(
|
|
597
|
+
`runtime catalog ${catalogPath} does not match runtime '${runtime}'`,
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
if (!Array.isArray(catalog.extensions)) {
|
|
601
|
+
throw new ExtensionValidationError(
|
|
602
|
+
`invalid runtime catalog extensions payload in ${catalogPath}`,
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
return { extensions: catalog.extensions };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/** The `install` report, in Python's print order. */
|
|
609
|
+
export function renderInstallReport(report: InstallReport): RenderedCommand {
|
|
610
|
+
const lines = [
|
|
611
|
+
`Installed extension/${report.extensionId}`,
|
|
612
|
+
` source: ${report.sourceDir}`,
|
|
613
|
+
` installed: ${report.installedDir}`,
|
|
614
|
+
];
|
|
615
|
+
for (const [runtimeName, items] of report.synced) {
|
|
616
|
+
lines.push(` runtime ${runtimeName}:`);
|
|
617
|
+
for (const item of items) {
|
|
618
|
+
lines.push(` ${item.command_name} -> ${item.installed_skill_path}`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
// On stderr, not stdout: the install SUCCEEDED, and a warning must not land
|
|
622
|
+
// in the bytes a caller parses as the install report.
|
|
623
|
+
const stderr =
|
|
624
|
+
report.unmigrated.length === 0
|
|
625
|
+
? ""
|
|
626
|
+
: [
|
|
627
|
+
`Warning: extension/${report.extensionId} declares no runtime for ` +
|
|
628
|
+
`${report.unmigrated.length} capability(ies):`,
|
|
629
|
+
...report.unmigrated.map((entry) => ` ${entry}`),
|
|
630
|
+
"These will refuse until they declare a runtime. " +
|
|
631
|
+
"See docs/releases/pending-cutoffs.md.",
|
|
632
|
+
"",
|
|
633
|
+
].join("\n");
|
|
634
|
+
return { stdout: `${lines.join("\n")}\n`, stderr, exitCode: 0 };
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/** The `uninstall` report, in Python's print order. */
|
|
638
|
+
export function renderUninstallReport(report: UninstallReport): RenderedCommand {
|
|
639
|
+
const lines = [
|
|
640
|
+
`Uninstalled extension/${report.extensionId}`,
|
|
641
|
+
` installed: ${report.installedDir}`,
|
|
642
|
+
];
|
|
643
|
+
if (report.sourceRemoved) lines.push(" source tree: removed");
|
|
644
|
+
if (report.bindingsRemoved) {
|
|
645
|
+
lines.push(` bindings: ${report.bindingsRemoved} row(s) removed`);
|
|
646
|
+
}
|
|
647
|
+
for (const [runtimeName, items] of report.removed) {
|
|
648
|
+
lines.push(` runtime ${runtimeName}:`);
|
|
649
|
+
for (const item of items) lines.push(` removed ${item}`);
|
|
650
|
+
}
|
|
651
|
+
return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 };
|
|
652
|
+
}
|