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,350 @@
|
|
|
1
|
+
// Read side of the extension SQL migration runner (CV22.DS7.TS3 plateau 3a).
|
|
2
|
+
// Port of the read half of `src/memory/extensions/migrations.py`.
|
|
3
|
+
//
|
|
4
|
+
// `runtime status` and `runtime diagnose` need to answer one question per
|
|
5
|
+
// installed command-skill: are this extension's migration files pending,
|
|
6
|
+
// drifted, or clean? That is `inspect_migration_files`, the read-only
|
|
7
|
+
// companion to `run_migrations`. Only the read half is ported here.
|
|
8
|
+
//
|
|
9
|
+
// The WRITE half (`run_migrations`, `_split_statements`,
|
|
10
|
+
// `_extract_table_targets`, `_validate_prefix`) is deliberately absent. It
|
|
11
|
+
// belongs to the extension catalog, which the plan assigns to CV22.DS7.TS4;
|
|
12
|
+
// this module is placed where TS4 extends it rather than moves it.
|
|
13
|
+
//
|
|
14
|
+
// The checksum contract (docs/product/extensions/migrations.md): two files
|
|
15
|
+
// differing only in comments or whitespace hash the same; any structural
|
|
16
|
+
// difference, string literals included, does not. Both cores must agree
|
|
17
|
+
// exactly, or one of them cries drift on a file the other calls clean.
|
|
18
|
+
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import type { Database, WritableDatabase } from "#db/database.ts";
|
|
23
|
+
import { PYTHON_WHITESPACE_CLASS, pyStrip, sortByCodePoint } from "#util/pythonText.ts";
|
|
24
|
+
import { ExtensionMigrationError } from "./errors.ts";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Port of `_FILENAME_RE`. Python's `\d` matches the whole Unicode `Nd`
|
|
28
|
+
* category, not `[0-9]`, so `\p{Nd}` is the faithful class -- a filename
|
|
29
|
+
* numbered with Arabic-Indic digits is accepted by both cores or by neither.
|
|
30
|
+
*/
|
|
31
|
+
const FILENAME_RE = /^\p{Nd}{3,}_[a-z0-9_]+\.sql$/u;
|
|
32
|
+
|
|
33
|
+
const STRIP_COMMENTS_RE = /--[^\n]*|\/\*[\s\S]*?\*\//g;
|
|
34
|
+
const COLLAPSE_WHITESPACE_RE = new RegExp(`[${PYTHON_WHITESPACE_CLASS}]+`, "gu");
|
|
35
|
+
const TRIM_AROUND_PUNCT_RE = new RegExp(
|
|
36
|
+
`[${PYTHON_WHITESPACE_CLASS}]*([(),;])[${PYTHON_WHITESPACE_CLASS}]*`,
|
|
37
|
+
"gu",
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
/** Port of `table_prefix_for`: the `ext_<id>_` namespace an extension owns. */
|
|
41
|
+
export function tablePrefixFor(extensionId: string): string {
|
|
42
|
+
return `ext_${extensionId.replaceAll("-", "_")}_`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Port of `_normalise_for_checksum`: drop comments, collapse whitespace runs,
|
|
47
|
+
* and close the gaps around `(),;` so the same statement reformatted across
|
|
48
|
+
* indented lines hashes identically. String literals survive -- an `INSERT`
|
|
49
|
+
* value is real SQL content, and editing one must trip the drift guard.
|
|
50
|
+
*/
|
|
51
|
+
export function normaliseForChecksum(sql: string): string {
|
|
52
|
+
const withoutComments = sql.replace(STRIP_COMMENTS_RE, "");
|
|
53
|
+
const collapsed = withoutComments.replace(COLLAPSE_WHITESPACE_RE, " ");
|
|
54
|
+
return pyStrip(collapsed.replace(TRIM_AROUND_PUNCT_RE, "$1"));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Port of `_checksum`: SHA-256 over the normalised SQL. */
|
|
58
|
+
export function checksum(content: string): string {
|
|
59
|
+
return createHash("sha256").update(normaliseForChecksum(content), "utf8").digest("hex");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Port of `_raw_checksum`: SHA-256 over the bytes as written. Backwards
|
|
64
|
+
* compatibility only -- rows recorded before CV14.E2.S3 carry this hash, and
|
|
65
|
+
* matching it keeps existing installs from reporting drift on a file nobody
|
|
66
|
+
* touched.
|
|
67
|
+
*/
|
|
68
|
+
export function rawChecksum(content: string): string {
|
|
69
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Port of `_list_migration_files`. Non-`.sql` entries are skipped in silence;
|
|
74
|
+
* a `.sql` file that breaks the naming contract raises, because a migration
|
|
75
|
+
* the runner cannot order is a defect, not a file to ignore.
|
|
76
|
+
*/
|
|
77
|
+
export function listMigrationFiles(migrationsDir: string, extensionId: string): string[] {
|
|
78
|
+
if (!existsSync(migrationsDir)) return [];
|
|
79
|
+
const names = sortByCodePoint(readdirSync(migrationsDir));
|
|
80
|
+
const files: string[] = [];
|
|
81
|
+
for (const name of names) {
|
|
82
|
+
const full = join(migrationsDir, name);
|
|
83
|
+
if (!statSync(full).isFile()) continue;
|
|
84
|
+
// `Path.suffix` is empty for a dotfile with no other dot, so `.sql`
|
|
85
|
+
// itself is not a `.sql` file to either core.
|
|
86
|
+
const dot = name.lastIndexOf(".");
|
|
87
|
+
if (dot <= 0 || name.slice(dot) !== ".sql") continue;
|
|
88
|
+
if (!FILENAME_RE.test(name)) {
|
|
89
|
+
throw new ExtensionMigrationError(
|
|
90
|
+
`invalid migration filename: ${name} (expected ^[0-9]{3,}_[a-z0-9_]+\\.sql$)`,
|
|
91
|
+
extensionId,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
files.push(name);
|
|
95
|
+
}
|
|
96
|
+
return files;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** What `inspect_migration_files` returns: two ordered filename lists. */
|
|
100
|
+
export interface MigrationInspection {
|
|
101
|
+
pending: string[];
|
|
102
|
+
drifted: string[];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Port of `inspect_migration_files`: which files have never been applied, and
|
|
107
|
+
* which were applied and have since changed. Never writes bookkeeping and
|
|
108
|
+
* never runs a statement out of a migration file.
|
|
109
|
+
*/
|
|
110
|
+
export function inspectMigrationFiles(
|
|
111
|
+
db: Database,
|
|
112
|
+
extensionId: string,
|
|
113
|
+
migrationsDir: string,
|
|
114
|
+
): MigrationInspection {
|
|
115
|
+
const files = listMigrationFiles(migrationsDir, extensionId);
|
|
116
|
+
const applied = db.prepare(
|
|
117
|
+
"SELECT checksum FROM _ext_migrations WHERE extension_id = ? AND filename = ?",
|
|
118
|
+
);
|
|
119
|
+
const pending: string[] = [];
|
|
120
|
+
const drifted: string[] = [];
|
|
121
|
+
for (const name of files) {
|
|
122
|
+
const content = readFileSync(join(migrationsDir, name), "utf8");
|
|
123
|
+
const row = applied.get(extensionId, name);
|
|
124
|
+
if (row === undefined) {
|
|
125
|
+
pending.push(name);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const recorded = row.checksum as string;
|
|
129
|
+
if (recorded === checksum(content) || recorded === rawChecksum(content)) continue;
|
|
130
|
+
drifted.push(name);
|
|
131
|
+
}
|
|
132
|
+
return { pending, drifted };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// --- the WRITE half (CV22.DS7.TS4 plateau 3) ----------------------------------
|
|
136
|
+
//
|
|
137
|
+
// TS3 ported the read half and said: "The WRITE half (`run_migrations`,
|
|
138
|
+
// `_split_statements`, `_extract_table_targets`, `_validate_prefix`) is
|
|
139
|
+
// deliberately absent. It belongs to the extension catalog, which the plan
|
|
140
|
+
// assigns to CV22.DS7.TS4; this module is placed where TS4 extends it rather
|
|
141
|
+
// than moves it." This is that extension.
|
|
142
|
+
|
|
143
|
+
const DDL_TABLE_RE = new RegExp(
|
|
144
|
+
"\\b(?:" +
|
|
145
|
+
"CREATE\\s+(?:VIRTUAL\\s+)?TABLE|" +
|
|
146
|
+
"ALTER\\s+TABLE|" +
|
|
147
|
+
"DROP\\s+TABLE|" +
|
|
148
|
+
"INSERT\\s+(?:OR\\s+\\w+\\s+)?INTO|" +
|
|
149
|
+
"UPDATE|" +
|
|
150
|
+
"DELETE\\s+FROM" +
|
|
151
|
+
")" +
|
|
152
|
+
'\\s+(?:IF\\s+(?:NOT\\s+)?EXISTS\\s+)?"?([A-Za-z_][A-Za-z0-9_]*)"?',
|
|
153
|
+
"gi",
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const INDEX_TABLE_RE = new RegExp(
|
|
157
|
+
"\\bCREATE\\s+(?:UNIQUE\\s+)?INDEX\\s+" +
|
|
158
|
+
"(?:IF\\s+NOT\\s+EXISTS\\s+)?" +
|
|
159
|
+
'(?:"?[A-Za-z_][A-Za-z0-9_]*"?\\s+)?' +
|
|
160
|
+
'ON\\s+"?([A-Za-z_][A-Za-z0-9_]*)"?',
|
|
161
|
+
"gi",
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const STRIP_STRINGS_RE = /'(?:[^']|'')*'/g;
|
|
165
|
+
|
|
166
|
+
/** Port of `_strip_noise`: comments out, string literals flattened to `''`. */
|
|
167
|
+
function stripNoise(sql: string): string {
|
|
168
|
+
return sql.replace(STRIP_COMMENTS_RE, "").replace(STRIP_STRINGS_RE, "''");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Port of `_split_statements`.
|
|
173
|
+
*
|
|
174
|
+
* Written as a character walk, exactly as Python's is, because the shape of
|
|
175
|
+
* the bug it prevents is structural: `executescript` commits any open
|
|
176
|
+
* transaction, so the runner splits the script itself and feeds statements
|
|
177
|
+
* through one explicit savepoint. A regex split on `;` would cut inside a
|
|
178
|
+
* string literal -- the database-architect's plan-stage dissent, and the
|
|
179
|
+
* reason the corpus stages `INSERT ... VALUES ('a;b')` and `'it''s'`.
|
|
180
|
+
*
|
|
181
|
+
* Compound statements (`BEGIN ... END`) are NOT supported, in either core.
|
|
182
|
+
*/
|
|
183
|
+
export function splitStatements(sql: string): string[] {
|
|
184
|
+
const characters = Array.from(sql);
|
|
185
|
+
const statements: string[] = [];
|
|
186
|
+
let buffer = "";
|
|
187
|
+
let index = 0;
|
|
188
|
+
while (index < characters.length) {
|
|
189
|
+
const character = characters[index] as string;
|
|
190
|
+
if (character === "-" && characters[index + 1] === "-") {
|
|
191
|
+
const end = characters.indexOf("\n", index);
|
|
192
|
+
index = end === -1 ? characters.length : end;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (character === "/" && characters[index + 1] === "*") {
|
|
196
|
+
const end = sqlIndexOf(characters, "*/", index + 2);
|
|
197
|
+
index = end === -1 ? characters.length : end + 2;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (character === "'") {
|
|
201
|
+
buffer += character;
|
|
202
|
+
index += 1;
|
|
203
|
+
while (index < characters.length) {
|
|
204
|
+
const inner = characters[index] as string;
|
|
205
|
+
buffer += inner;
|
|
206
|
+
if (inner === "'") {
|
|
207
|
+
if (characters[index + 1] === "'") {
|
|
208
|
+
buffer += characters[index + 1];
|
|
209
|
+
index += 2;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
index += 1;
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
index += 1;
|
|
216
|
+
}
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (character === ";") {
|
|
220
|
+
const statement = pyStrip(buffer);
|
|
221
|
+
if (statement) statements.push(statement);
|
|
222
|
+
buffer = "";
|
|
223
|
+
index += 1;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
buffer += character;
|
|
227
|
+
index += 1;
|
|
228
|
+
}
|
|
229
|
+
const tail = pyStrip(buffer);
|
|
230
|
+
if (tail) statements.push(tail);
|
|
231
|
+
return statements;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** `str.find(needle, start)` over a code-point array. */
|
|
235
|
+
function sqlIndexOf(characters: readonly string[], needle: string, start: number): number {
|
|
236
|
+
const parts = Array.from(needle);
|
|
237
|
+
for (let index = start; index + parts.length <= characters.length; index += 1) {
|
|
238
|
+
if (parts.every((part, offset) => characters[index + offset] === part)) return index;
|
|
239
|
+
}
|
|
240
|
+
return -1;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Port of `_extract_table_targets`: every table a statement reads or writes. */
|
|
244
|
+
export function extractTableTargets(sql: string): string[] {
|
|
245
|
+
const cleaned = stripNoise(sql);
|
|
246
|
+
const tables: string[] = [];
|
|
247
|
+
for (const match of cleaned.matchAll(DDL_TABLE_RE))
|
|
248
|
+
tables.push((match[1] as string).toLowerCase());
|
|
249
|
+
for (const match of cleaned.matchAll(INDEX_TABLE_RE)) {
|
|
250
|
+
tables.push((match[1] as string).toLowerCase());
|
|
251
|
+
}
|
|
252
|
+
return tables;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Port of `_validate_prefix`: an extension may only touch its own namespace. */
|
|
256
|
+
export function validatePrefix(content: string, extensionId: string, filename: string): void {
|
|
257
|
+
const prefix = tablePrefixFor(extensionId);
|
|
258
|
+
for (const table of extractTableTargets(content)) {
|
|
259
|
+
if (!table.startsWith(prefix)) {
|
|
260
|
+
throw new ExtensionMigrationError(
|
|
261
|
+
`${filename} targets table '${table}' outside the required prefix '${prefix}*'`,
|
|
262
|
+
extensionId,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export interface RunMigrationsDeps {
|
|
269
|
+
/** Python's `_now()`: `datetime.now(timezone.utc).isoformat()`. */
|
|
270
|
+
readonly nowIso: () => string;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Port of `run_migrations`. Returns the number of newly applied files.
|
|
275
|
+
*
|
|
276
|
+
* Atomicity is a SAVEPOINT, not a transaction, and that is deliberate in both
|
|
277
|
+
* cores: SQLite implicitly commits an open deferred transaction before DDL, so
|
|
278
|
+
* `BEGIN`/`ROLLBACK` would not undo a half-applied `CREATE TABLE`. A savepoint
|
|
279
|
+
* does, bookkeeping row included.
|
|
280
|
+
*/
|
|
281
|
+
export function runMigrations(
|
|
282
|
+
db: WritableDatabase,
|
|
283
|
+
extensionId: string,
|
|
284
|
+
migrationsDir: string,
|
|
285
|
+
deps: RunMigrationsDeps,
|
|
286
|
+
): number {
|
|
287
|
+
const files = listMigrationFiles(migrationsDir, extensionId);
|
|
288
|
+
let applied = 0;
|
|
289
|
+
for (const name of files) {
|
|
290
|
+
const content = readFileSync(join(migrationsDir, name), "utf8");
|
|
291
|
+
const current = checksum(content);
|
|
292
|
+
const row = db
|
|
293
|
+
.prepare("SELECT checksum FROM _ext_migrations WHERE extension_id = ? AND filename = ?")
|
|
294
|
+
.get(extensionId, name);
|
|
295
|
+
|
|
296
|
+
if (row !== undefined) {
|
|
297
|
+
const recorded = row.checksum as string;
|
|
298
|
+
if (recorded === current) continue;
|
|
299
|
+
if (recorded === rawChecksum(content)) {
|
|
300
|
+
// A pre-CV14.E2.S3 row that still matches the file as written: upgrade
|
|
301
|
+
// the stored hash so the next run takes the fast path.
|
|
302
|
+
db.prepare(
|
|
303
|
+
"UPDATE _ext_migrations SET checksum = ? WHERE extension_id = ? AND filename = ?",
|
|
304
|
+
).run(current, extensionId, name);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
throw new ExtensionMigrationError(
|
|
308
|
+
`${name} has been edited since it was applied ` +
|
|
309
|
+
`(recorded checksum ${recorded.slice(0, 8)}..., ` +
|
|
310
|
+
`current checksum ${current.slice(0, 8)}...); never edit an ` +
|
|
311
|
+
"applied migration in ways that change its SQL semantics " +
|
|
312
|
+
"— comment and whitespace edits are tolerated, but a " +
|
|
313
|
+
"structural change requires a new migration file",
|
|
314
|
+
extensionId,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
validatePrefix(content, extensionId, name);
|
|
319
|
+
|
|
320
|
+
const savepoint = `ext_migration_${extensionId.replaceAll("-", "_")}`;
|
|
321
|
+
db.exec(`SAVEPOINT ${savepoint}`);
|
|
322
|
+
try {
|
|
323
|
+
for (const statement of splitStatements(content)) db.exec(statement);
|
|
324
|
+
db.prepare(
|
|
325
|
+
"INSERT INTO _ext_migrations (extension_id, filename, checksum, applied_at) " +
|
|
326
|
+
"VALUES (?, ?, ?, ?)",
|
|
327
|
+
).run(extensionId, name, current, deps.nowIso());
|
|
328
|
+
} catch (error) {
|
|
329
|
+
db.exec(`ROLLBACK TO SAVEPOINT ${savepoint}`);
|
|
330
|
+
db.exec(`RELEASE SAVEPOINT ${savepoint}`);
|
|
331
|
+
throw new ExtensionMigrationError(
|
|
332
|
+
`${name} failed to apply: ${sqliteMessage(error)}`,
|
|
333
|
+
extensionId,
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
db.exec(`RELEASE SAVEPOINT ${savepoint}`);
|
|
337
|
+
applied += 1;
|
|
338
|
+
}
|
|
339
|
+
return applied;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Python renders `sqlite3.Error` as its message alone. `node:sqlite` prefixes
|
|
344
|
+
* its own errors differently, so the text after `failed to apply:` is a
|
|
345
|
+
* RECORDED DIVERGENCE: the golden pins the stable prefix and the exit code,
|
|
346
|
+
* never the driver's sentence.
|
|
347
|
+
*/
|
|
348
|
+
function sqliteMessage(error: unknown): string {
|
|
349
|
+
return error instanceof Error ? error.message : String(error);
|
|
350
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { resolveExtractionModel } from "#providers/config.ts";
|
|
2
|
+
import type { LlmProvider, LlmResponse } from "#providers/llm.ts";
|
|
3
|
+
import {
|
|
4
|
+
fenceTranscript,
|
|
5
|
+
MAX_MEMORIES_PER_CONVERSATION,
|
|
6
|
+
MAX_TASKS_PER_CONVERSATION,
|
|
7
|
+
type SanitizeDropped,
|
|
8
|
+
sanitizeExtracted,
|
|
9
|
+
} from "./fencing.ts";
|
|
10
|
+
import { parseJsonResponse } from "./json.ts";
|
|
11
|
+
import { CURATION_PROMPT, EXTRACTION_PROMPT, TASK_EXTRACTION_PROMPT } from "./prompts.ts";
|
|
12
|
+
|
|
13
|
+
export interface ExtractionMessage {
|
|
14
|
+
role: string;
|
|
15
|
+
content: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ExtractedMemory {
|
|
19
|
+
title: string;
|
|
20
|
+
content: string;
|
|
21
|
+
context: string | null;
|
|
22
|
+
memory_type: string;
|
|
23
|
+
layer: string;
|
|
24
|
+
tags: string[];
|
|
25
|
+
journey: string | null;
|
|
26
|
+
persona: string | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ExistingMemoryForCuration {
|
|
30
|
+
title: string;
|
|
31
|
+
content: string;
|
|
32
|
+
memory_type: string;
|
|
33
|
+
layer: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ExtractedTask {
|
|
37
|
+
title: string;
|
|
38
|
+
due_date: string | null;
|
|
39
|
+
journey: string | null;
|
|
40
|
+
stage: string | null;
|
|
41
|
+
context: string | null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function formatTranscript(
|
|
45
|
+
messages: readonly ExtractionMessage[],
|
|
46
|
+
userName = "User",
|
|
47
|
+
): string {
|
|
48
|
+
return messages
|
|
49
|
+
.map((message) => {
|
|
50
|
+
const role = message.role === "user" ? userName : "Mirror";
|
|
51
|
+
return `**${role}:** ${message.content}`;
|
|
52
|
+
})
|
|
53
|
+
.join("\n\n");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function naiveSummary(messages: readonly ExtractionMessage[]): string {
|
|
57
|
+
return messages
|
|
58
|
+
.filter((message) => message.role === "user" || message.role === "assistant")
|
|
59
|
+
.map((message) => message.content.slice(0, 500))
|
|
60
|
+
.join(" ")
|
|
61
|
+
.slice(0, 2000);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type ExtractionStatusValue = "parse_failed" | "no_signal" | "ok";
|
|
65
|
+
|
|
66
|
+
export interface ExtractionStatus {
|
|
67
|
+
status: ExtractionStatusValue;
|
|
68
|
+
dropped?: SanitizeDropped;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ExtractMemoriesOutcome {
|
|
72
|
+
memories: ExtractedMemory[];
|
|
73
|
+
status: ExtractionStatus;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Extracts memories and reports why the result is what it is, mirroring
|
|
78
|
+
* Python's `extract_memories(status=...)` (AI-10, CV9.E2.S16). A caller can
|
|
79
|
+
* distinguish unreadable model output (parse_failed) from a genuinely empty
|
|
80
|
+
* result (no_signal) instead of both looking identical to an empty array.
|
|
81
|
+
*
|
|
82
|
+
* `"llm_failed"` is deliberately not part of `ExtractionStatusValue`: this
|
|
83
|
+
* function cannot structurally report it -- that value only exists when the
|
|
84
|
+
* call throws, which is the orchestration layer's (`runConversationExtraction`)
|
|
85
|
+
* concern, not this function's return shape.
|
|
86
|
+
*
|
|
87
|
+
* Documented divergence: Python's status dict is left untouched (no key set
|
|
88
|
+
* at all) for a truly-empty messages call -- a mutate-if-present out-param
|
|
89
|
+
* behavior this return-based design cannot replicate exactly. This returns
|
|
90
|
+
* `{ status: "no_signal" }` instead. Zero observable impact:
|
|
91
|
+
* `runConversationExtraction` already guards `messages.length < 4` before
|
|
92
|
+
* ever calling this, so the path is unreachable from the real orchestration
|
|
93
|
+
* call site.
|
|
94
|
+
*/
|
|
95
|
+
/**
|
|
96
|
+
* The ledger seam Python's `on_llm_call` parameter provides: invoked once per
|
|
97
|
+
* successful model call with the response and the assembled prompt, before
|
|
98
|
+
* the response is parsed. Never invoked when the call throws.
|
|
99
|
+
*/
|
|
100
|
+
export type OnLlmCall = (response: LlmResponse, prompt: string) => void;
|
|
101
|
+
|
|
102
|
+
export async function extractMemoriesWithStatus(
|
|
103
|
+
provider: LlmProvider,
|
|
104
|
+
messages: readonly ExtractionMessage[],
|
|
105
|
+
options: {
|
|
106
|
+
persona?: string | null;
|
|
107
|
+
journey?: string | null;
|
|
108
|
+
userName?: string;
|
|
109
|
+
onLlmCall?: OnLlmCall;
|
|
110
|
+
} = {},
|
|
111
|
+
): Promise<ExtractMemoriesOutcome> {
|
|
112
|
+
if (messages.length === 0) return { memories: [], status: { status: "no_signal" } };
|
|
113
|
+
const prompt = buildExtractionPrompt(messages, options.userName ?? "User");
|
|
114
|
+
const response = await provider.complete({
|
|
115
|
+
role: "extraction",
|
|
116
|
+
prompt,
|
|
117
|
+
model: resolveExtractionModel(),
|
|
118
|
+
temperature: 0.3,
|
|
119
|
+
});
|
|
120
|
+
options.onLlmCall?.(response, prompt);
|
|
121
|
+
const data = parseJsonResponse(response.content);
|
|
122
|
+
if (!Array.isArray(data)) {
|
|
123
|
+
return { memories: [], status: { status: "parse_failed" } };
|
|
124
|
+
}
|
|
125
|
+
const memories: ExtractedMemory[] = [];
|
|
126
|
+
for (const item of data) {
|
|
127
|
+
const memory = toExtractedMemory(item, options);
|
|
128
|
+
if (memory) memories.push(memory);
|
|
129
|
+
}
|
|
130
|
+
const { kept, dropped } = sanitizeExtracted(memories, MAX_MEMORIES_PER_CONVERSATION);
|
|
131
|
+
return { memories: kept, status: { status: kept.length > 0 ? "ok" : "no_signal", dropped } };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Thin, non-breaking wrapper over `extractMemoriesWithStatus`, returning only
|
|
135
|
+
* the memories array for existing callers -- mirrors Python's `extract_memories`
|
|
136
|
+
* default (`status=None`) shape. */
|
|
137
|
+
export async function extractMemories(
|
|
138
|
+
provider: LlmProvider,
|
|
139
|
+
messages: readonly ExtractionMessage[],
|
|
140
|
+
options: { persona?: string | null; journey?: string | null; userName?: string } = {},
|
|
141
|
+
): Promise<ExtractedMemory[]> {
|
|
142
|
+
return (await extractMemoriesWithStatus(provider, messages, options)).memories;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function extractTasks(
|
|
146
|
+
provider: LlmProvider,
|
|
147
|
+
messages: readonly ExtractionMessage[],
|
|
148
|
+
options: { journey?: string | null; userName?: string; onLlmCall?: OnLlmCall } = {},
|
|
149
|
+
): Promise<ExtractedTask[]> {
|
|
150
|
+
if (messages.length === 0) return [];
|
|
151
|
+
const prompt = buildTaskExtractionPrompt(messages, options.userName ?? "User");
|
|
152
|
+
const response = await provider.complete({
|
|
153
|
+
role: "task_extraction",
|
|
154
|
+
prompt,
|
|
155
|
+
model: resolveExtractionModel(),
|
|
156
|
+
temperature: 0.3,
|
|
157
|
+
});
|
|
158
|
+
options.onLlmCall?.(response, prompt);
|
|
159
|
+
const data = parseJsonResponse(response.content);
|
|
160
|
+
if (!Array.isArray(data)) return [];
|
|
161
|
+
const tasks: ExtractedTask[] = [];
|
|
162
|
+
for (const item of data) {
|
|
163
|
+
const task = toExtractedTask(item, options.journey ?? null);
|
|
164
|
+
if (task) tasks.push(task);
|
|
165
|
+
}
|
|
166
|
+
return tasks.length > MAX_TASKS_PER_CONVERSATION
|
|
167
|
+
? tasks.slice(0, MAX_TASKS_PER_CONVERSATION)
|
|
168
|
+
: tasks;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function curateAgainstExisting(
|
|
172
|
+
provider: LlmProvider,
|
|
173
|
+
candidates: readonly ExtractedMemory[],
|
|
174
|
+
existing: readonly ExistingMemoryForCuration[],
|
|
175
|
+
options: { onLlmCall?: OnLlmCall } = {},
|
|
176
|
+
): Promise<ExtractedMemory[]> {
|
|
177
|
+
if (candidates.length === 0) return [];
|
|
178
|
+
if (existing.length === 0) return [...candidates];
|
|
179
|
+
try {
|
|
180
|
+
const prompt = buildCurationPrompt(candidates, existing);
|
|
181
|
+
const response = await provider.complete({
|
|
182
|
+
role: "curation",
|
|
183
|
+
prompt,
|
|
184
|
+
model: resolveExtractionModel(),
|
|
185
|
+
temperature: 0.2,
|
|
186
|
+
});
|
|
187
|
+
options.onLlmCall?.(response, prompt);
|
|
188
|
+
const data = parseJsonResponse(response.content);
|
|
189
|
+
if (!Array.isArray(data)) return [...candidates];
|
|
190
|
+
const curated: ExtractedMemory[] = [];
|
|
191
|
+
for (const item of data) {
|
|
192
|
+
const memory = toExtractedMemory(item, {});
|
|
193
|
+
if (memory) curated.push(memory);
|
|
194
|
+
}
|
|
195
|
+
return sanitizeExtracted(curated, MAX_MEMORIES_PER_CONVERSATION).kept;
|
|
196
|
+
} catch {
|
|
197
|
+
return [...candidates];
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function toExtractedMemory(
|
|
202
|
+
item: unknown,
|
|
203
|
+
defaults: { persona?: string | null; journey?: string | null },
|
|
204
|
+
): ExtractedMemory | null {
|
|
205
|
+
if (!isRecord(item)) return null;
|
|
206
|
+
if (
|
|
207
|
+
typeof item.title !== "string" ||
|
|
208
|
+
typeof item.content !== "string" ||
|
|
209
|
+
typeof item.memory_type !== "string"
|
|
210
|
+
) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
title: item.title,
|
|
215
|
+
content: item.content,
|
|
216
|
+
context: typeof item.context === "string" ? item.context : null,
|
|
217
|
+
memory_type: item.memory_type,
|
|
218
|
+
layer: typeof item.layer === "string" ? item.layer : "ego",
|
|
219
|
+
tags: Array.isArray(item.tags) ? item.tags.map((tag) => String(tag)) : [],
|
|
220
|
+
journey: typeof item.journey === "string" ? item.journey : (defaults.journey ?? null),
|
|
221
|
+
persona: typeof item.persona === "string" ? item.persona : (defaults.persona ?? null),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function toExtractedTask(item: unknown, journey: string | null): ExtractedTask | null {
|
|
226
|
+
if (!isRecord(item) || typeof item.title !== "string") return null;
|
|
227
|
+
return {
|
|
228
|
+
title: item.title,
|
|
229
|
+
due_date: typeof item.due_date === "string" ? item.due_date : null,
|
|
230
|
+
journey: typeof item.journey === "string" ? item.journey : journey,
|
|
231
|
+
stage: typeof item.stage === "string" ? item.stage : null,
|
|
232
|
+
context: typeof item.context === "string" ? item.context : null,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Python's `_format_candidates`. Each candidate emits its header line, a
|
|
238
|
+
* `Content:` line, an optional `Context:` line, and a trailing blank entry, all
|
|
239
|
+
* joined with newlines -- so the block ends with a newline. The optional
|
|
240
|
+
* context line and that trailing newline are both part of the prompt bytes.
|
|
241
|
+
*/
|
|
242
|
+
function formatCandidates(candidates: readonly ExtractedMemory[]): string {
|
|
243
|
+
const lines: string[] = [];
|
|
244
|
+
candidates.forEach((candidate, index) => {
|
|
245
|
+
lines.push(
|
|
246
|
+
`${index + 1}. **${candidate.title}** (${candidate.memory_type}/${candidate.layer})`,
|
|
247
|
+
);
|
|
248
|
+
lines.push(` Content: ${candidate.content}`);
|
|
249
|
+
if (candidate.context) lines.push(` Context: ${candidate.context}`);
|
|
250
|
+
lines.push("");
|
|
251
|
+
});
|
|
252
|
+
return lines.join("\n");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** Python's `_format_existing`: header line, truncated body, trailing blank. */
|
|
256
|
+
function formatExisting(existing: readonly ExistingMemoryForCuration[]): string {
|
|
257
|
+
const lines: string[] = [];
|
|
258
|
+
for (const memory of existing) {
|
|
259
|
+
lines.push(`- **${memory.title}** (${memory.memory_type}/${memory.layer})`);
|
|
260
|
+
lines.push(` ${memory.content.slice(0, 200)}`);
|
|
261
|
+
lines.push("");
|
|
262
|
+
}
|
|
263
|
+
return lines.join("\n");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Assemble the exact extraction prompt Python sends.
|
|
268
|
+
*
|
|
269
|
+
* Note the asymmetry with the close-tail surfaces: extraction and task
|
|
270
|
+
* extraction carry no post-fence reminder, while title/tags/summary do. That
|
|
271
|
+
* is Python's shape, reproduced rather than harmonized.
|
|
272
|
+
*/
|
|
273
|
+
export function buildExtractionPrompt(
|
|
274
|
+
messages: readonly ExtractionMessage[],
|
|
275
|
+
userName = "User",
|
|
276
|
+
): string {
|
|
277
|
+
return EXTRACTION_PROMPT + fenceTranscript(formatTranscript(messages, userName));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Assemble the exact task-extraction prompt Python sends. */
|
|
281
|
+
export function buildTaskExtractionPrompt(
|
|
282
|
+
messages: readonly ExtractionMessage[],
|
|
283
|
+
userName = "User",
|
|
284
|
+
): string {
|
|
285
|
+
return TASK_EXTRACTION_PROMPT + fenceTranscript(formatTranscript(messages, userName));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Assemble the exact curation prompt Python sends, section headers included. */
|
|
289
|
+
export function buildCurationPrompt(
|
|
290
|
+
candidates: readonly ExtractedMemory[],
|
|
291
|
+
existing: readonly ExistingMemoryForCuration[],
|
|
292
|
+
): string {
|
|
293
|
+
return (
|
|
294
|
+
CURATION_PROMPT +
|
|
295
|
+
"## Candidate memories (from this conversation)\n\n" +
|
|
296
|
+
formatCandidates(candidates) +
|
|
297
|
+
"\n## Existing similar memories (already stored)\n\n" +
|
|
298
|
+
formatExisting(existing)
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
303
|
+
return typeof value === "object" && value !== null;
|
|
304
|
+
}
|