mirror-mind 0.0.1 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/log-assistant-reply.sh +85 -0
- 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 +646 -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 +100 -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.33.0.md +164 -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 +17 -0
- package/plugins/mirror-mind/hooks/hooks.json +62 -0
- package/plugins/mirror-mind/hooks/log-assistant-reply.sh +98 -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/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 +67 -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/hookPayload.ts +51 -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 +851 -0
- package/ts/src/conversation/loggerCli.ts +321 -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 +1872 -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 +83 -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 +1001 -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/sessionStartWelcomeRoute.ts +166 -0
- package/ts/src/frontDoor/soulRoute.ts +433 -0
- package/ts/src/frontDoor/stdinSource.ts +37 -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 +298 -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 +214 -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 +73 -0
- package/ts/src/hooks/runtime.ts +179 -0
- package/ts/src/hooks/runtimeDetection.ts +34 -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/mirrorForm.ts +43 -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,130 @@
|
|
|
1
|
+
// How each runtime is wired to an installed Mirror Mind. CV22.DS10.US3 plateau 3.
|
|
2
|
+
//
|
|
3
|
+
// `mirror init` prints these for the runtimes it finds on the PATH, so the
|
|
4
|
+
// person who just created a home is handed the next line rather than a page.
|
|
5
|
+
// Printing is not automating: the `runtime install <runtime>` non-goal stands,
|
|
6
|
+
// and every step here is one the person runs and can read first.
|
|
7
|
+
//
|
|
8
|
+
// Each step was verified at plateau 3 against the runtime's own documentation
|
|
9
|
+
// or, where the docs were silent, against the runtime itself from a scratch
|
|
10
|
+
// configuration directory (test guide, route 3):
|
|
11
|
+
//
|
|
12
|
+
// Pi `pi install <path>` loads a package from a local path without
|
|
13
|
+
// copying (D6); the manifest's `pi` key names the extension and
|
|
14
|
+
// the skills. One step.
|
|
15
|
+
// Claude Code a plugin directory under `~/.claude/skills/` loads every
|
|
16
|
+
// session as `<name>@skills-dir` with its skills, hooks, and MCP
|
|
17
|
+
// server (`claude plugin list` / `details`, 2.1.283). One step.
|
|
18
|
+
// The Operating Instructions arrive through the plugin's
|
|
19
|
+
// SessionStart hook (D13).
|
|
20
|
+
// Gemini CLI `gemini skills link --consent <dir>` links every skill in the
|
|
21
|
+
// directory into `~/.gemini/skills/` (0.61.0; a nested directory
|
|
22
|
+
// symlink is NOT discovered, the user tier is one level deep),
|
|
23
|
+
// and hooks live in `~/.gemini/settings.json`, absolute paths.
|
|
24
|
+
// Two steps. The Operating Instructions arrive through the
|
|
25
|
+
// SessionStart hook (D13).
|
|
26
|
+
// Codex `~/.codex/skills/<dir>` is discovered recursively, and
|
|
27
|
+
// `~/.codex/AGENTS.md` is the global instructions file
|
|
28
|
+
// (`codex debug prompt-input`, 0.157.0). Two steps, and the
|
|
29
|
+
// session is run through the shipped wrapper, since Codex has
|
|
30
|
+
// no hooks this integration uses.
|
|
31
|
+
|
|
32
|
+
import { join } from "node:path";
|
|
33
|
+
import { commandOnPath } from "#util/paths.ts";
|
|
34
|
+
|
|
35
|
+
export type RuntimeName = "pi" | "claude" | "gemini" | "codex";
|
|
36
|
+
|
|
37
|
+
export interface RuntimeWiring {
|
|
38
|
+
/** The runtime, as a person names it. */
|
|
39
|
+
readonly title: string;
|
|
40
|
+
/** The command whose presence on the PATH means the runtime is installed. */
|
|
41
|
+
readonly command: string;
|
|
42
|
+
/** What to run or edit, one entry per step, built from the package root. */
|
|
43
|
+
readonly steps: readonly ((root: string) => readonly string[])[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Gemini CLI's four hooks, as the user-level settings.json wants them. */
|
|
47
|
+
function geminiHooksSettings(root: string): string {
|
|
48
|
+
const hook = (event: string, name: string, script: string) =>
|
|
49
|
+
` "${event}": [{ "hooks": [{ "name": "mirror-${name}", "type": "command", "command": "${join(root, ".gemini/hooks", script)}", "timeout": 30000 }] }]`;
|
|
50
|
+
return [
|
|
51
|
+
"{",
|
|
52
|
+
' "hooks": {',
|
|
53
|
+
[
|
|
54
|
+
hook("SessionStart", "session-start", "session-start.sh"),
|
|
55
|
+
hook("BeforeAgent", "log-user", "log-user.sh"),
|
|
56
|
+
hook("AfterAgent", "log-assistant", "log-assistant.sh"),
|
|
57
|
+
hook("SessionEnd", "session-end", "session-end.sh"),
|
|
58
|
+
].join(",\n"),
|
|
59
|
+
" }",
|
|
60
|
+
"}",
|
|
61
|
+
].join("\n");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const RUNTIME_WIRING: Readonly<Record<RuntimeName, RuntimeWiring>> = {
|
|
65
|
+
pi: {
|
|
66
|
+
title: "Pi",
|
|
67
|
+
command: "pi",
|
|
68
|
+
steps: [(root) => [`pi install "${root}"`]],
|
|
69
|
+
},
|
|
70
|
+
claude: {
|
|
71
|
+
title: "Claude Code",
|
|
72
|
+
command: "claude",
|
|
73
|
+
steps: [
|
|
74
|
+
(root) => [
|
|
75
|
+
`ln -s "${join(root, "plugins/mirror-mind")}" ~/.claude/skills/mirror-mind`,
|
|
76
|
+
"(the plugin loads every session: its skills, the hooks, and the MCP server)",
|
|
77
|
+
],
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
gemini: {
|
|
81
|
+
title: "Gemini CLI",
|
|
82
|
+
command: "gemini",
|
|
83
|
+
steps: [
|
|
84
|
+
(root) => [`gemini skills link --consent "${join(root, ".pi/skills")}"`],
|
|
85
|
+
(root) => [
|
|
86
|
+
"merge into ~/.gemini/settings.json:",
|
|
87
|
+
...geminiHooksSettings(root)
|
|
88
|
+
.split("\n")
|
|
89
|
+
.map((line) => ` ${line}`),
|
|
90
|
+
],
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
codex: {
|
|
94
|
+
title: "Codex",
|
|
95
|
+
command: "codex",
|
|
96
|
+
steps: [
|
|
97
|
+
(root) => [`ln -s "${join(root, ".pi/skills")}" ~/.codex/skills/mirror-mind`],
|
|
98
|
+
(root) => [
|
|
99
|
+
`ln -s "${join(root, "AGENTS.md")}" ~/.codex/AGENTS.md`,
|
|
100
|
+
"(or append that file to the ~/.codex/AGENTS.md you already have)",
|
|
101
|
+
`then start Codex through "${join(root, "scripts/codex-mirror.sh")}" instead of codex`,
|
|
102
|
+
],
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const ORDER: readonly RuntimeName[] = ["pi", "claude", "gemini", "codex"];
|
|
108
|
+
|
|
109
|
+
/** The runtimes whose command is on the PATH, in display order. */
|
|
110
|
+
export function detectRuntimes(
|
|
111
|
+
env: NodeJS.ProcessEnv,
|
|
112
|
+
isExecutable: (path: string) => boolean,
|
|
113
|
+
): RuntimeName[] {
|
|
114
|
+
return ORDER.filter(
|
|
115
|
+
(runtime) => commandOnPath(RUNTIME_WIRING[runtime].command, env, isExecutable) !== null,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** The wiring for one runtime, numbered when there is more than one step. */
|
|
120
|
+
export function runtimeWiringLines(runtime: RuntimeName, root: string): string[] {
|
|
121
|
+
const wiring = RUNTIME_WIRING[runtime];
|
|
122
|
+
const lines: string[] = [`${wiring.title}:`];
|
|
123
|
+
wiring.steps.forEach((step, index) => {
|
|
124
|
+
const [first = "", ...rest] = step(root);
|
|
125
|
+
const prefix = wiring.steps.length > 1 ? `${index + 1}. ` : "";
|
|
126
|
+
lines.push(` ${prefix}${first}`);
|
|
127
|
+
for (const line of rest) lines.push(` ${" ".repeat(prefix.length)}${line}`);
|
|
128
|
+
});
|
|
129
|
+
return lines;
|
|
130
|
+
}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import type { Database, SqlValue, WritableDatabase } from "#db/database.ts";
|
|
2
|
+
import { optionalNumber, optionalString, requireString } from "#db/rowDecode.ts";
|
|
3
|
+
import { logAccess } from "#memory/reinforcement.ts";
|
|
4
|
+
import { embeddingLedgerHook } from "#observability/ledgerHooks.ts";
|
|
5
|
+
import { ProviderConfigError } from "#providers/config.ts";
|
|
6
|
+
import {
|
|
7
|
+
type EmbeddingAttemptInfo,
|
|
8
|
+
EmbeddingError,
|
|
9
|
+
type EmbeddingProvider,
|
|
10
|
+
generateEmbeddingSafely,
|
|
11
|
+
} from "#providers/embedding.ts";
|
|
12
|
+
import { LlmTransportError } from "#providers/openrouter.ts";
|
|
13
|
+
import { nowIso } from "#util/pyGenerators.ts";
|
|
14
|
+
import {
|
|
15
|
+
type RankableMemory,
|
|
16
|
+
type RankedMemory,
|
|
17
|
+
type RankerConfig,
|
|
18
|
+
rankMemories,
|
|
19
|
+
} from "./ranker.ts";
|
|
20
|
+
|
|
21
|
+
export interface FreshSearchFilters {
|
|
22
|
+
memoryType?: string | null;
|
|
23
|
+
layer?: string | null;
|
|
24
|
+
journey?: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface FreshSearchOptions extends FreshSearchFilters {
|
|
28
|
+
query: string;
|
|
29
|
+
limit?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Reinforce retrieval for the returned memories. Default `true`, mirroring
|
|
32
|
+
* Python's `log_access: bool = True`.
|
|
33
|
+
*
|
|
34
|
+
* `access_count` feeds `reinforcement_score` and the hybrid ranker, so a
|
|
35
|
+
* caller that searches on the system's behalf rather than the user's must opt
|
|
36
|
+
* out -- otherwise the ranker learns from its own exhaust (AI-12). Python's
|
|
37
|
+
* non-genuine callers pass `log_access=False`: the curation pass, the MCP
|
|
38
|
+
* agent search, and `memories --search`. Builder context load keeps the
|
|
39
|
+
* default and still reinforces, because that one is a genuine load.
|
|
40
|
+
*/
|
|
41
|
+
logAccess?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Record each embedding attempt in the `llm_calls` ledger (AI-09/D-003).
|
|
44
|
+
* Default `true`.
|
|
45
|
+
*
|
|
46
|
+
* - `true`/unset -- write through `db`, which must be writable.
|
|
47
|
+
* - `false` -- record nothing.
|
|
48
|
+
* - a function -- record through that sink, leaving `db` untouched.
|
|
49
|
+
*
|
|
50
|
+
* The sink exists for the MCP server (CV22.DS9.TS2 D1), whose tools hold a
|
|
51
|
+
* driver-level READ-ONLY handle while its single sanctioned write goes to a
|
|
52
|
+
* separate `llm_calls`-only connection. Passing a function rather than a
|
|
53
|
+
* second handle keeps that connection out of this module and out of every
|
|
54
|
+
* tool: search never learns it exists.
|
|
55
|
+
*/
|
|
56
|
+
recordEmbeddingLedger?: boolean | ((info: EmbeddingAttemptInfo) => void);
|
|
57
|
+
frozenNowMs?: number;
|
|
58
|
+
now?: string;
|
|
59
|
+
provider: EmbeddingProvider;
|
|
60
|
+
/** Test-injectable override for generateEmbeddingSafely's retry backoff
|
|
61
|
+
* (real timers by default) -- avoids paying real backoff wait time in tests
|
|
62
|
+
* that exercise the transient-retry path. */
|
|
63
|
+
embeddingRetrySleep?: (ms: number) => Promise<void>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface FreshSearchResult extends RankedMemory {}
|
|
67
|
+
|
|
68
|
+
export interface FreshSearchOutcome {
|
|
69
|
+
results: FreshSearchResult[];
|
|
70
|
+
degraded: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Metadata-only cause of a degraded search: the transport taxonomy kind, or
|
|
73
|
+
* `config` when no key is set. Never a message, never the query -- this is
|
|
74
|
+
* written to the front-door log, which forbids payloads (RS005/CR026).
|
|
75
|
+
*/
|
|
76
|
+
degradedKind?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const DEFAULT_SEARCH_RANKER_CONFIG = {
|
|
80
|
+
weights: { semantic: 0.5, recency: 0.15, reinforcement: 0.1, relevance: 0.1, lexical: 0.15 },
|
|
81
|
+
mmrThreshold: 0.92,
|
|
82
|
+
recencyHalfLifeDays: 90,
|
|
83
|
+
reinforcementDecayDays: 180,
|
|
84
|
+
reinforcementUseWeight: 0.7,
|
|
85
|
+
reinforcementRetrievalWeight: 0.3,
|
|
86
|
+
} satisfies Omit<RankerConfig, "queryEmbedding" | "frozenNowMs" | "limit">;
|
|
87
|
+
|
|
88
|
+
interface MemoryRow {
|
|
89
|
+
id: string;
|
|
90
|
+
created_at: string;
|
|
91
|
+
last_accessed_at: string | null;
|
|
92
|
+
use_count: number;
|
|
93
|
+
relevance_score: number;
|
|
94
|
+
embedding_b64: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Hybrid search that also reports whether it ran degraded (lexical-only),
|
|
99
|
+
* mirroring Python's `search_with_status` (AI-04, CV9.E2.S10).
|
|
100
|
+
*
|
|
101
|
+
* When the query embedding cannot be generated (offline, missing key,
|
|
102
|
+
* timeout) the search falls back to the local FTS5 index: the semantic term
|
|
103
|
+
* is dropped (an empty `queryEmbedding` makes `cosineSimilarity`'s zero-norm
|
|
104
|
+
* path return 0 for every candidate, so `ranker.ts` needs no change) and only
|
|
105
|
+
* FTS-matched memories are ranked -- a hard filter, not just a lower score,
|
|
106
|
+
* matching Python's `mem.id not in fts_lookup: continue`. MMR dedup is
|
|
107
|
+
* unaffected -- it ranks on the stored memory embeddings, not the query.
|
|
108
|
+
*/
|
|
109
|
+
export async function searchMemoriesWithStatus(
|
|
110
|
+
db: Database,
|
|
111
|
+
options: FreshSearchOptions,
|
|
112
|
+
): Promise<FreshSearchOutcome> {
|
|
113
|
+
const limit = options.limit ?? 5;
|
|
114
|
+
let queryEmbedding: readonly number[] = [];
|
|
115
|
+
let degraded = false;
|
|
116
|
+
let degradedKind: string | undefined;
|
|
117
|
+
try {
|
|
118
|
+
// generateEmbeddingSafely (CR043) retries a transient empty response up to
|
|
119
|
+
// its default budget before giving up -- matching Python's search, which
|
|
120
|
+
// calls the same generate_embedding used everywhere else, not a bespoke
|
|
121
|
+
// single-shot attempt. Any exhausted/permanent/provider-exception failure
|
|
122
|
+
// still maps to degraded=true here, preserving CR037's contract exactly.
|
|
123
|
+
queryEmbedding = await generateEmbeddingSafely(options.provider, options.query, {
|
|
124
|
+
onAttempt: resolveLedgerSink(db, options.recordEmbeddingLedger),
|
|
125
|
+
sleep: options.embeddingRetrySleep,
|
|
126
|
+
});
|
|
127
|
+
} catch (error) {
|
|
128
|
+
degraded = true;
|
|
129
|
+
// Keep the CLASS of failure (AI-18 taxonomy) so an operator can tell an
|
|
130
|
+
// expired key from a timeout from a rate limit. Without this the front
|
|
131
|
+
// door only ever says "offline or no API key", which is a guess -- and
|
|
132
|
+
// the taxonomy the live transport builds would die here unseen.
|
|
133
|
+
degradedKind = classifyDegradedCause(error);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const memories = listSearchMemoryRows(db, options);
|
|
137
|
+
const accessCounts = accessCountsByMemoryId(
|
|
138
|
+
db,
|
|
139
|
+
memories.map((memory) => memory.id),
|
|
140
|
+
);
|
|
141
|
+
const lexicalScores = ftsLexicalScores(db, options.query, options);
|
|
142
|
+
|
|
143
|
+
const candidateMemories = degraded
|
|
144
|
+
? memories.filter((memory) => lexicalScores.has(memory.id))
|
|
145
|
+
: memories;
|
|
146
|
+
const rankable = candidateMemories.map(
|
|
147
|
+
(memory): RankableMemory => ({
|
|
148
|
+
...memory,
|
|
149
|
+
access_count: accessCounts.get(memory.id) ?? 0,
|
|
150
|
+
lexical_score: lexicalScores.get(memory.id) ?? 0,
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
const ranked = rankMemories(rankable, {
|
|
154
|
+
...DEFAULT_SEARCH_RANKER_CONFIG,
|
|
155
|
+
queryEmbedding,
|
|
156
|
+
frozenNowMs: options.frozenNowMs ?? Date.now(),
|
|
157
|
+
limit,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Reinforce only on genuine context loads. This mirrors Python's log_access
|
|
161
|
+
// param (AI-12) firing regardless of degraded status -- the two concerns are
|
|
162
|
+
// orthogonal and intentionally not coupled; do not make this conditional.
|
|
163
|
+
if (options.logAccess ?? true) {
|
|
164
|
+
const writable = asWritable(db, "logAccess");
|
|
165
|
+
const accessNow = options.now ?? nowIso();
|
|
166
|
+
for (const result of ranked) {
|
|
167
|
+
logAccess(writable, result.id, accessNow, options.query.slice(0, 200));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { results: ranked, degraded, degradedKind };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Thin, non-breaking wrapper over `searchMemoriesWithStatus`, mirroring
|
|
174
|
+
* Python's `search()` over `search_with_status()`: existing callers keep the
|
|
175
|
+
* plain-array shape, and an embedding failure no longer propagates uncaught. */
|
|
176
|
+
export async function searchMemories(
|
|
177
|
+
db: Database,
|
|
178
|
+
options: FreshSearchOptions,
|
|
179
|
+
): Promise<FreshSearchResult[]> {
|
|
180
|
+
return (await searchMemoriesWithStatus(db, options)).results;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function listSearchMemoryRows(db: Database, filters: FreshSearchFilters = {}): MemoryRow[] {
|
|
184
|
+
const conditions = ["embedding IS NOT NULL"];
|
|
185
|
+
const params: SqlValue[] = [];
|
|
186
|
+
if (filters.memoryType) {
|
|
187
|
+
conditions.push("memory_type = ?");
|
|
188
|
+
params.push(filters.memoryType);
|
|
189
|
+
}
|
|
190
|
+
if (filters.layer) {
|
|
191
|
+
conditions.push("layer = ?");
|
|
192
|
+
params.push(filters.layer);
|
|
193
|
+
}
|
|
194
|
+
if (filters.journey) {
|
|
195
|
+
conditions.push("journey = ?");
|
|
196
|
+
params.push(filters.journey);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return db
|
|
200
|
+
.prepare(
|
|
201
|
+
`SELECT id, created_at, last_accessed_at, use_count, relevance_score, embedding ` +
|
|
202
|
+
`FROM memories WHERE ${conditions.join(" AND ")} ORDER BY created_at DESC`,
|
|
203
|
+
)
|
|
204
|
+
.all(...params)
|
|
205
|
+
.map(toMemoryRow);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function accessCountsByMemoryId(
|
|
209
|
+
db: Database,
|
|
210
|
+
memoryIds: readonly string[],
|
|
211
|
+
): Map<string, number> {
|
|
212
|
+
if (memoryIds.length === 0) return new Map();
|
|
213
|
+
const placeholders = memoryIds.map(() => "?").join(", ");
|
|
214
|
+
const rows = db
|
|
215
|
+
.prepare(
|
|
216
|
+
`SELECT memory_id, COUNT(*) AS count FROM memory_access_log ` +
|
|
217
|
+
`WHERE memory_id IN (${placeholders}) GROUP BY memory_id`,
|
|
218
|
+
)
|
|
219
|
+
.all(...memoryIds);
|
|
220
|
+
const counts = new Map(memoryIds.map((id): [string, number] => [id, 0]));
|
|
221
|
+
for (const row of rows) {
|
|
222
|
+
counts.set(requireString(row, "memory_id"), Number(row.count));
|
|
223
|
+
}
|
|
224
|
+
return counts;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function ftsLexicalScores(
|
|
228
|
+
db: Database,
|
|
229
|
+
query: string,
|
|
230
|
+
filters: FreshSearchFilters = {},
|
|
231
|
+
limit = 100,
|
|
232
|
+
): Map<string, number> {
|
|
233
|
+
const safeQuery = ftsQuery(query);
|
|
234
|
+
if (!safeQuery) return new Map();
|
|
235
|
+
const conditions: string[] = [];
|
|
236
|
+
const params: SqlValue[] = [safeQuery];
|
|
237
|
+
if (filters.memoryType) {
|
|
238
|
+
conditions.push("m.memory_type = ?");
|
|
239
|
+
params.push(filters.memoryType);
|
|
240
|
+
}
|
|
241
|
+
if (filters.layer) {
|
|
242
|
+
conditions.push("m.layer = ?");
|
|
243
|
+
params.push(filters.layer);
|
|
244
|
+
}
|
|
245
|
+
if (filters.journey) {
|
|
246
|
+
conditions.push("m.journey = ?");
|
|
247
|
+
params.push(filters.journey);
|
|
248
|
+
}
|
|
249
|
+
params.push(limit);
|
|
250
|
+
const whereExtra = conditions.length > 0 ? ` AND ${conditions.join(" AND ")}` : "";
|
|
251
|
+
try {
|
|
252
|
+
const rows = db
|
|
253
|
+
.prepare(
|
|
254
|
+
`SELECT m.id FROM memories_fts f ` +
|
|
255
|
+
`JOIN memories m ON m.rowid = f.rowid ` +
|
|
256
|
+
`WHERE memories_fts MATCH ?${whereExtra} ` +
|
|
257
|
+
`ORDER BY bm25(memories_fts) LIMIT ?`,
|
|
258
|
+
)
|
|
259
|
+
.all(...params);
|
|
260
|
+
return new Map(rows.map((row, index) => [requireString(row, "id"), 1 / (1 + index)]));
|
|
261
|
+
} catch {
|
|
262
|
+
return new Map();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function ftsQuery(query: string): string {
|
|
267
|
+
const words = query
|
|
268
|
+
.split(/\s+/)
|
|
269
|
+
.map((word) => word.replaceAll('"', ""))
|
|
270
|
+
.filter((word) => word.length > 0);
|
|
271
|
+
return words.map((word) => `"${word}"`).join(" ");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Narrow a handle to a writable one, or fail with the option that asked for it.
|
|
276
|
+
*
|
|
277
|
+
* Search reads through a plain `Database` so a read-only caller (the MCP
|
|
278
|
+
* server) can use it. The two paths that WRITE -- reinforcement and the
|
|
279
|
+
* embedding ledger -- are opt-in, and asking for either with a read-only handle
|
|
280
|
+
* is a caller bug worth naming rather than a SQLite error to decode.
|
|
281
|
+
*/
|
|
282
|
+
function asWritable(db: Database, requestedBy: string): WritableDatabase {
|
|
283
|
+
if (typeof (db as WritableDatabase).exec !== "function") {
|
|
284
|
+
throw new Error(`${requestedBy} requires a writable database handle; this one is read-only`);
|
|
285
|
+
}
|
|
286
|
+
return db as WritableDatabase;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** The query embedding's ledger row (AI-09/D-003). The query text is not tied
|
|
290
|
+
* to a conversation, so no conversationId travels with it. */
|
|
291
|
+
function logQueryEmbeddingAttempt(db: WritableDatabase): (info: EmbeddingAttemptInfo) => void {
|
|
292
|
+
return embeddingLedgerHook(db);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Resolve where an embedding attempt is recorded: `db` by default, nowhere when
|
|
297
|
+
* disabled, or a caller-supplied sink. Only the default narrows `db` to a
|
|
298
|
+
* writable handle -- a caller passing a sink may hold a read-only one.
|
|
299
|
+
*/
|
|
300
|
+
function resolveLedgerSink(
|
|
301
|
+
db: Database,
|
|
302
|
+
record: boolean | ((info: EmbeddingAttemptInfo) => void) | undefined,
|
|
303
|
+
): ((info: EmbeddingAttemptInfo) => void) | undefined {
|
|
304
|
+
if (typeof record === "function") return record;
|
|
305
|
+
if (record === false) return undefined;
|
|
306
|
+
return logQueryEmbeddingAttempt(asWritable(db, "recordEmbeddingLedger"));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function toMemoryRow(row: Record<string, unknown>): MemoryRow {
|
|
310
|
+
const embedding = row.embedding;
|
|
311
|
+
if (!(embedding instanceof Uint8Array)) {
|
|
312
|
+
throw new Error("memory embedding must be a BLOB/Uint8Array");
|
|
313
|
+
}
|
|
314
|
+
return {
|
|
315
|
+
id: requireString(row, "id"),
|
|
316
|
+
created_at: requireString(row, "created_at"),
|
|
317
|
+
last_accessed_at: optionalString(row, "last_accessed_at"),
|
|
318
|
+
use_count: optionalNumber(row, "use_count") ?? 0,
|
|
319
|
+
relevance_score: optionalNumber(row, "relevance_score") ?? 0,
|
|
320
|
+
embedding_b64: Buffer.from(embedding).toString("base64"),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Reduce a degraded-search cause to a short, content-free category
|
|
326
|
+
* (CV22.DS8.US1). Only the CLASS travels: provider messages can echo request
|
|
327
|
+
* content, and the front-door log forbids payloads.
|
|
328
|
+
*/
|
|
329
|
+
function classifyDegradedCause(error: unknown): string {
|
|
330
|
+
if (error instanceof ProviderConfigError) return "config";
|
|
331
|
+
if (error instanceof LlmTransportError) return error.kind;
|
|
332
|
+
if (error instanceof EmbeddingError) {
|
|
333
|
+
// generateEmbeddingSafely wraps a provider exception; the inner kind is
|
|
334
|
+
// gone by then, so the honest answer is the layer that failed.
|
|
335
|
+
return error.permanent ? "embedding_permanent" : "embedding_exhausted";
|
|
336
|
+
}
|
|
337
|
+
return "unknown";
|
|
338
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { blobToFloat32, parseUtcMs } from "#db/decode.ts";
|
|
2
|
+
|
|
3
|
+
export interface SearchWeights {
|
|
4
|
+
semantic: number;
|
|
5
|
+
recency: number;
|
|
6
|
+
reinforcement: number;
|
|
7
|
+
relevance: number;
|
|
8
|
+
lexical?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RankerConfig {
|
|
12
|
+
queryEmbedding: readonly number[];
|
|
13
|
+
frozenNowMs: number;
|
|
14
|
+
limit: number;
|
|
15
|
+
weights: SearchWeights;
|
|
16
|
+
mmrThreshold: number;
|
|
17
|
+
recencyHalfLifeDays: number;
|
|
18
|
+
reinforcementDecayDays: number;
|
|
19
|
+
reinforcementUseWeight: number;
|
|
20
|
+
reinforcementRetrievalWeight: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface RankableMemory {
|
|
24
|
+
id: string;
|
|
25
|
+
created_at: string;
|
|
26
|
+
last_accessed_at?: string | null;
|
|
27
|
+
use_count: number;
|
|
28
|
+
relevance_score: number;
|
|
29
|
+
access_count: number;
|
|
30
|
+
lexical_score?: number;
|
|
31
|
+
embedding_b64: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RankedMemory {
|
|
35
|
+
id: string;
|
|
36
|
+
score: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface Candidate {
|
|
40
|
+
id: string;
|
|
41
|
+
score: number;
|
|
42
|
+
embedding: Float32Array;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function cosineSimilarity(a: ArrayLike<number>, b: ArrayLike<number>): number {
|
|
46
|
+
const length = Math.min(a.length, b.length);
|
|
47
|
+
let dot = 0;
|
|
48
|
+
let normA = 0;
|
|
49
|
+
let normB = 0;
|
|
50
|
+
for (let i = 0; i < length; i += 1) {
|
|
51
|
+
dot += a[i] * b[i];
|
|
52
|
+
normA += a[i] * a[i];
|
|
53
|
+
normB += b[i] * b[i];
|
|
54
|
+
}
|
|
55
|
+
const norm = Math.sqrt(normA) * Math.sqrt(normB);
|
|
56
|
+
return norm === 0 ? 0 : dot / norm;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function recencyScore(
|
|
60
|
+
createdAt: string,
|
|
61
|
+
config: Pick<RankerConfig, "frozenNowMs" | "recencyHalfLifeDays">,
|
|
62
|
+
): number {
|
|
63
|
+
const createdMs = parseUtcMs(createdAt);
|
|
64
|
+
if (createdMs === null) return 0.5;
|
|
65
|
+
const daysAgo = Math.max(0, (config.frozenNowMs - createdMs) / 86_400_000);
|
|
66
|
+
return Math.exp((-Math.LN2 * daysAgo) / config.recencyHalfLifeDays);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function reinforcementScore(
|
|
70
|
+
accessCount: number,
|
|
71
|
+
useCount: number,
|
|
72
|
+
lastAccessedAt: string | null | undefined,
|
|
73
|
+
config: Pick<
|
|
74
|
+
RankerConfig,
|
|
75
|
+
| "frozenNowMs"
|
|
76
|
+
| "reinforcementDecayDays"
|
|
77
|
+
| "reinforcementUseWeight"
|
|
78
|
+
| "reinforcementRetrievalWeight"
|
|
79
|
+
>,
|
|
80
|
+
): number {
|
|
81
|
+
const useSignal = Math.min(1, useCount / 5);
|
|
82
|
+
const retrievalRaw = Math.min(1, Math.log1p(accessCount) / 3);
|
|
83
|
+
const lastMs = accessCount > 0 && lastAccessedAt ? parseUtcMs(lastAccessedAt) : null;
|
|
84
|
+
let retrievalSignal = retrievalRaw;
|
|
85
|
+
if (lastMs !== null) {
|
|
86
|
+
const days = Math.max(0, (config.frozenNowMs - lastMs) / 86_400_000);
|
|
87
|
+
retrievalSignal = retrievalRaw * Math.exp((-Math.LN2 * days) / config.reinforcementDecayDays);
|
|
88
|
+
}
|
|
89
|
+
return (
|
|
90
|
+
config.reinforcementUseWeight * useSignal +
|
|
91
|
+
config.reinforcementRetrievalWeight * retrievalSignal
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function hybridScore(args: {
|
|
96
|
+
semantic: number;
|
|
97
|
+
recency: number;
|
|
98
|
+
reinforcement: number;
|
|
99
|
+
relevance: number;
|
|
100
|
+
lexical: number;
|
|
101
|
+
weights: SearchWeights;
|
|
102
|
+
}): number {
|
|
103
|
+
return (
|
|
104
|
+
args.weights.semantic * args.semantic +
|
|
105
|
+
args.weights.recency * args.recency +
|
|
106
|
+
args.weights.reinforcement * args.reinforcement +
|
|
107
|
+
args.weights.relevance * args.relevance +
|
|
108
|
+
(args.weights.lexical ?? 0) * args.lexical
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function rankMemories(
|
|
113
|
+
memories: readonly RankableMemory[],
|
|
114
|
+
config: RankerConfig,
|
|
115
|
+
): RankedMemory[] {
|
|
116
|
+
const candidates: Candidate[] = memories.map((memory) => {
|
|
117
|
+
const embedding = blobToFloat32(Buffer.from(memory.embedding_b64, "base64"));
|
|
118
|
+
const semantic = cosineSimilarity(config.queryEmbedding, embedding);
|
|
119
|
+
const recency = recencyScore(memory.created_at, config);
|
|
120
|
+
const reinforcement = reinforcementScore(
|
|
121
|
+
memory.access_count,
|
|
122
|
+
memory.use_count,
|
|
123
|
+
memory.last_accessed_at,
|
|
124
|
+
config,
|
|
125
|
+
);
|
|
126
|
+
const score = hybridScore({
|
|
127
|
+
semantic,
|
|
128
|
+
recency,
|
|
129
|
+
reinforcement,
|
|
130
|
+
relevance: memory.relevance_score,
|
|
131
|
+
lexical: memory.lexical_score ?? 0,
|
|
132
|
+
weights: config.weights,
|
|
133
|
+
});
|
|
134
|
+
return { id: memory.id, score, embedding };
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
138
|
+
|
|
139
|
+
const selected: Candidate[] = [];
|
|
140
|
+
for (const candidate of candidates) {
|
|
141
|
+
if (
|
|
142
|
+
selected.some(
|
|
143
|
+
(existing) =>
|
|
144
|
+
cosineSimilarity(candidate.embedding, existing.embedding) >= config.mmrThreshold,
|
|
145
|
+
)
|
|
146
|
+
) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
selected.push(candidate);
|
|
150
|
+
if (selected.length >= config.limit) break;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return selected.map((candidate) => ({ id: candidate.id, score: candidate.score }));
|
|
154
|
+
}
|