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,329 @@
|
|
|
1
|
+
// Ledger introspection READS (CV22.DS7.TS4 plateau 2).
|
|
2
|
+
//
|
|
3
|
+
// Port of `inspect llm-calls` and `inspect embedding-provenance` from
|
|
4
|
+
// `src/memory/cli/inspect.py`, over the summary query DS8 already ported
|
|
5
|
+
// (`getLlmCallSummary`) plus the two reads this plateau adds.
|
|
6
|
+
//
|
|
7
|
+
// MEASURED, and none of it is visible from reading the modules:
|
|
8
|
+
//
|
|
9
|
+
// * these two leaves ARE argparse -- a bad `--limit` exits **2** with usage
|
|
10
|
+
// on stderr -- while every leaf in the catalog family (plateau 1) exits 1
|
|
11
|
+
// with usage on stdout. Both classes live under `inspect`;
|
|
12
|
+
// * `--session` and `--since` are applied by the CLI *after* the store's
|
|
13
|
+
// `LIMIT`, so `--session X --limit 3` can report "no rows" while
|
|
14
|
+
// `--session X` alone finds one. Reproduced, not repaired;
|
|
15
|
+
// * `role` and `model` are NOT NULL in the schema, so the renderer's
|
|
16
|
+
// `or "?"` fallback is reachable only through an EMPTY STRING;
|
|
17
|
+
// * the role column is padded to 18 with `{:<18}` and NOT truncated, so
|
|
18
|
+
// `journal_classification` overflows and misaligns its own row;
|
|
19
|
+
// * prompts and responses are cut at 200 CODE POINTS after `strip()`, and
|
|
20
|
+
// printed with `!r`. A UTF-16 cut halves an astral character on the
|
|
21
|
+
// boundary; the corpus stages exactly that character at index 199.
|
|
22
|
+
|
|
23
|
+
import type { Database } from "#db/database.ts";
|
|
24
|
+
import { pyRepr, sortByCodePoint } from "#util/pythonText.ts";
|
|
25
|
+
import { getLlmCallSummary, type LlmCallSummary } from "./llmCalls.ts";
|
|
26
|
+
|
|
27
|
+
export interface LlmCallRow {
|
|
28
|
+
id: string;
|
|
29
|
+
role: string;
|
|
30
|
+
model: string;
|
|
31
|
+
prompt: string;
|
|
32
|
+
response: string;
|
|
33
|
+
promptTokens: number | null;
|
|
34
|
+
completionTokens: number | null;
|
|
35
|
+
latencyMs: number | null;
|
|
36
|
+
costUsd: number | null;
|
|
37
|
+
conversationId: string | null;
|
|
38
|
+
sessionId: string | null;
|
|
39
|
+
calledAt: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Port of `Store.get_llm_calls`: newest first, filtered in SQL, then LIMIT. */
|
|
43
|
+
export function getLlmCalls(
|
|
44
|
+
db: Database,
|
|
45
|
+
options: { conversationId?: string | null; role?: string | null; limit?: number } = {},
|
|
46
|
+
): LlmCallRow[] {
|
|
47
|
+
const clauses: string[] = [];
|
|
48
|
+
const params: Array<string | number> = [];
|
|
49
|
+
if (options.conversationId) {
|
|
50
|
+
clauses.push("conversation_id = ?");
|
|
51
|
+
params.push(options.conversationId);
|
|
52
|
+
}
|
|
53
|
+
if (options.role) {
|
|
54
|
+
clauses.push("role = ?");
|
|
55
|
+
params.push(options.role);
|
|
56
|
+
}
|
|
57
|
+
const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
58
|
+
params.push(options.limit ?? 50);
|
|
59
|
+
const rows = db
|
|
60
|
+
.prepare(
|
|
61
|
+
`SELECT id, role, model, prompt, response,
|
|
62
|
+
prompt_tokens, completion_tokens, latency_ms, cost_usd,
|
|
63
|
+
conversation_id, session_id, called_at
|
|
64
|
+
FROM llm_calls
|
|
65
|
+
${where}
|
|
66
|
+
ORDER BY called_at DESC
|
|
67
|
+
LIMIT ?`,
|
|
68
|
+
)
|
|
69
|
+
.all(...params);
|
|
70
|
+
return rows.map((row) => ({
|
|
71
|
+
id: String(row.id ?? ""),
|
|
72
|
+
role: String(row.role ?? ""),
|
|
73
|
+
model: String(row.model ?? ""),
|
|
74
|
+
prompt: String(row.prompt ?? ""),
|
|
75
|
+
response: String(row.response ?? ""),
|
|
76
|
+
promptTokens: row.prompt_tokens === null ? null : Number(row.prompt_tokens),
|
|
77
|
+
completionTokens: row.completion_tokens === null ? null : Number(row.completion_tokens),
|
|
78
|
+
latencyMs: row.latency_ms === null ? null : Number(row.latency_ms),
|
|
79
|
+
costUsd: row.cost_usd === null ? null : Number(row.cost_usd),
|
|
80
|
+
conversationId: row.conversation_id === null ? null : String(row.conversation_id),
|
|
81
|
+
sessionId: row.session_id === null ? null : String(row.session_id),
|
|
82
|
+
calledAt: String(row.called_at ?? ""),
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Port of `Store.count_memories_by_embedding_model`.
|
|
88
|
+
*
|
|
89
|
+
* Aggregated in TypeScript rather than in SQL, as Python does and for the same
|
|
90
|
+
* reason: a legacy or malformed `metadata` degrades into the unknown bucket
|
|
91
|
+
* instead of raising. Sorted by descending count, then by Python's
|
|
92
|
+
* `str(model)` -- which renders the unknown bucket as the string `None`, so it
|
|
93
|
+
* sorts before any lowercase model name on a tie.
|
|
94
|
+
*/
|
|
95
|
+
export function countMemoriesByEmbeddingModel(db: Database): Array<[string | null, number]> {
|
|
96
|
+
const rows = db.prepare("SELECT metadata FROM memories WHERE embedding IS NOT NULL").all();
|
|
97
|
+
const counts = new Map<string | null, number>();
|
|
98
|
+
for (const row of rows) {
|
|
99
|
+
let model: string | null = null;
|
|
100
|
+
const metadata = row.metadata;
|
|
101
|
+
if (metadata) {
|
|
102
|
+
try {
|
|
103
|
+
const parsed = JSON.parse(String(metadata));
|
|
104
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
105
|
+
const value = (parsed as Record<string, unknown>).embedding_model;
|
|
106
|
+
model = typeof value === "string" ? value : null;
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
model = null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
counts.set(model, (counts.get(model) ?? 0) + 1);
|
|
113
|
+
}
|
|
114
|
+
const entries = [...counts.entries()];
|
|
115
|
+
const keyOf = (model: string | null): string => (model === null ? "None" : model);
|
|
116
|
+
const order = sortByCodePoint(entries.map(([model]) => keyOf(model)));
|
|
117
|
+
return entries.sort((left, right) => {
|
|
118
|
+
if (left[1] !== right[1]) return right[1] - left[1];
|
|
119
|
+
return order.indexOf(keyOf(left[0])) - order.indexOf(keyOf(right[0]));
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Python `f"{cost:.6f}"`. */
|
|
124
|
+
function formatCost(cost: number): string {
|
|
125
|
+
return cost.toFixed(6);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Port of `_fmt_summary_cost`: an em dash for unpriced. */
|
|
129
|
+
export function formatSummaryCost(cost: number | null): string {
|
|
130
|
+
return cost === null ? "—" : `~$${formatCost(cost)}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Python `str.ljust`, in code points. */
|
|
134
|
+
function ljust(text: string, width: number): string {
|
|
135
|
+
const length = Array.from(text).length;
|
|
136
|
+
return length >= width ? text : text + " ".repeat(width - length);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Python `text[:limit]` -- CODE POINTS, not UTF-16 units. */
|
|
140
|
+
function sliceCodePoints(text: string, limit: number): string {
|
|
141
|
+
return Array.from(text).slice(0, limit).join("");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const NO_ROWS = "(no llm_calls rows match the given filters)\n";
|
|
145
|
+
|
|
146
|
+
/** Port of the row-listing half of `cmd_inspect_llm_calls`. */
|
|
147
|
+
export function renderLlmCallRows(rows: readonly LlmCallRow[]): string {
|
|
148
|
+
if (rows.length === 0) return NO_ROWS;
|
|
149
|
+
const lines = [`=== llm_calls (${rows.length} row${rows.length !== 1 ? "s" : ""}) ===`];
|
|
150
|
+
for (const row of rows) {
|
|
151
|
+
const calledAt = sliceCodePoints(row.calledAt, 19).replace("T", " ");
|
|
152
|
+
const latency = row.latencyMs === null ? "?ms" : `${row.latencyMs}ms`;
|
|
153
|
+
const conversation = sliceCodePoints(row.conversationId ?? "", 8) || "—";
|
|
154
|
+
const cost = row.costUsd === null ? "$—" : `~$${formatCost(row.costUsd)}`;
|
|
155
|
+
lines.push("");
|
|
156
|
+
lines.push(`[${calledAt}] ${row.role || "?"} | ${row.model || "?"}`);
|
|
157
|
+
lines.push(
|
|
158
|
+
` id:${sliceCodePoints(row.id, 8)} conv:${conversation} ` +
|
|
159
|
+
`${row.promptTokens ?? 0}→${row.completionTokens ?? 0} tokens ${latency} ${cost}`,
|
|
160
|
+
);
|
|
161
|
+
lines.push(` prompt: ${pyRepr(sliceCodePoints(row.prompt.trim(), 200))}`);
|
|
162
|
+
lines.push(` response: ${pyRepr(sliceCodePoints(row.response.trim(), 200))}`);
|
|
163
|
+
}
|
|
164
|
+
return `${lines.join("\n")}\n`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Port of `_print_llm_call_summary`. */
|
|
168
|
+
export function renderLlmCallSummary(summary: LlmCallSummary, since: string | null): string {
|
|
169
|
+
if (summary.total.calls === 0) return NO_ROWS;
|
|
170
|
+
const scope = since ? ` since ${since}` : "";
|
|
171
|
+
const lines = [`=== llm_calls summary${scope} ===`];
|
|
172
|
+
const block = (title: string, buckets: readonly LlmCallSummary["byRole"][number][]): void => {
|
|
173
|
+
lines.push("");
|
|
174
|
+
lines.push(title);
|
|
175
|
+
for (const bucket of buckets) {
|
|
176
|
+
const unpriced = bucket.unpriced ? ` (${bucket.unpriced} unpriced)` : "";
|
|
177
|
+
// `{:<18}` pads but never truncates: a longer role overflows the column.
|
|
178
|
+
lines.push(
|
|
179
|
+
` ${ljust(bucket.bucket, 18)} ${String(bucket.calls).padStart(5)} calls ` +
|
|
180
|
+
`${bucket.promptTokens}→${bucket.completionTokens} tokens ` +
|
|
181
|
+
`${formatSummaryCost(bucket.costUsd)}${unpriced}`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
block("By role:", summary.byRole);
|
|
186
|
+
block("By week:", summary.byWeek);
|
|
187
|
+
const unpriced = summary.total.unpriced ? ` (${summary.total.unpriced} unpriced)` : "";
|
|
188
|
+
lines.push("");
|
|
189
|
+
lines.push(
|
|
190
|
+
`TOTAL ${summary.total.calls} calls ` +
|
|
191
|
+
`${summary.total.promptTokens}→${summary.total.completionTokens} tokens ` +
|
|
192
|
+
`${formatSummaryCost(summary.total.costUsd)}${unpriced}`,
|
|
193
|
+
);
|
|
194
|
+
return `${lines.join("\n")}\n`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Port of `render_embedding_provenance`. */
|
|
198
|
+
export function renderEmbeddingProvenance(
|
|
199
|
+
distribution: ReadonlyArray<readonly [string | null, number]>,
|
|
200
|
+
): string {
|
|
201
|
+
const total = distribution.reduce((sum, [, count]) => sum + count, 0);
|
|
202
|
+
if (total === 0) return "(no stored memory vectors)";
|
|
203
|
+
const lines = [`=== embedding provenance (${total} memory vector${total !== 1 ? "s" : ""}) ===`];
|
|
204
|
+
for (const [model, count] of distribution) {
|
|
205
|
+
lines.push(` ${String(count).padStart(6)} ${model ? model : "unknown (pre-provenance)"}`);
|
|
206
|
+
}
|
|
207
|
+
return lines.join("\n");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export interface LedgerCommandResult {
|
|
211
|
+
stdout: string;
|
|
212
|
+
stderr: string;
|
|
213
|
+
exitCode: number;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
interface LlmCallOptions {
|
|
217
|
+
conversationId: string | null;
|
|
218
|
+
sessionId: string | null;
|
|
219
|
+
role: string | null;
|
|
220
|
+
since: string | null;
|
|
221
|
+
limit: number;
|
|
222
|
+
summary: boolean;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* The argparse-class refusal for these two leaves: exit 2, stderr, and a
|
|
227
|
+
* one-line TypeScript message rather than argparse's usage block.
|
|
228
|
+
*
|
|
229
|
+
* Deliberate, and the same recorded divergence US8 pinned: argparse wraps its
|
|
230
|
+
* usage to the terminal's `COLUMNS`, so the "byte-identical" text would only
|
|
231
|
+
* be byte-identical at the width the golden was generated with. The graded
|
|
232
|
+
* contract is the input set, the stream, and the exit code.
|
|
233
|
+
*/
|
|
234
|
+
function usageError(message: string): LedgerCommandResult {
|
|
235
|
+
return { stdout: "", stderr: `Mirror TS inspect: ${message}\n`, exitCode: 2 };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const LLM_CALL_VALUE_OPTIONS = new Map<string, keyof LlmCallOptions>([
|
|
239
|
+
["--conversation", "conversationId"],
|
|
240
|
+
["--session", "sessionId"],
|
|
241
|
+
["--role", "role"],
|
|
242
|
+
["--since", "since"],
|
|
243
|
+
]);
|
|
244
|
+
|
|
245
|
+
function parseLlmCallArgv(argv: readonly string[]): LlmCallOptions | LedgerCommandResult {
|
|
246
|
+
const options: LlmCallOptions = {
|
|
247
|
+
conversationId: null,
|
|
248
|
+
sessionId: null,
|
|
249
|
+
role: null,
|
|
250
|
+
since: null,
|
|
251
|
+
limit: 20,
|
|
252
|
+
summary: false,
|
|
253
|
+
};
|
|
254
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
255
|
+
const token = argv[index] as string;
|
|
256
|
+
if (token === "--summary") continue;
|
|
257
|
+
if (token === "--mirror-home") {
|
|
258
|
+
if (argv[index + 1] === undefined)
|
|
259
|
+
return usageError("argument --mirror-home: expected one argument");
|
|
260
|
+
index += 1;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (token === "--limit") {
|
|
264
|
+
const value = argv[index + 1];
|
|
265
|
+
if (value === undefined) return usageError("argument --limit: expected one argument");
|
|
266
|
+
if (!/^[+-]?\d+$/.test(value.trim())) {
|
|
267
|
+
return usageError(`argument --limit: invalid int value: ${pyRepr(value)}`);
|
|
268
|
+
}
|
|
269
|
+
options.limit = Number.parseInt(value, 10);
|
|
270
|
+
index += 1;
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
const field = LLM_CALL_VALUE_OPTIONS.get(token);
|
|
274
|
+
if (field === undefined) return usageError(`unrecognized arguments: ${token}`);
|
|
275
|
+
const value = argv[index + 1];
|
|
276
|
+
if (value === undefined) return usageError(`argument ${token}: expected one argument`);
|
|
277
|
+
(options[field] as string | null) = value;
|
|
278
|
+
index += 1;
|
|
279
|
+
}
|
|
280
|
+
options.summary = argv.includes("--summary");
|
|
281
|
+
return options;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Port of `cmd_inspect_llm_calls`, both faces. */
|
|
285
|
+
export function runInspectLlmCalls(db: Database, argv: readonly string[]): LedgerCommandResult {
|
|
286
|
+
const parsed = parseLlmCallArgv(argv);
|
|
287
|
+
if ("stdout" in parsed) return parsed;
|
|
288
|
+
|
|
289
|
+
if (parsed.summary) {
|
|
290
|
+
const summary = getLlmCallSummary(db, parsed.since ? { since: parsed.since } : {});
|
|
291
|
+
return { stdout: renderLlmCallSummary(summary, parsed.since), stderr: "", exitCode: 0 };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let rows = getLlmCalls(db, {
|
|
295
|
+
conversationId: parsed.conversationId,
|
|
296
|
+
role: parsed.role,
|
|
297
|
+
limit: parsed.limit,
|
|
298
|
+
});
|
|
299
|
+
// AFTER the store's LIMIT, exactly as Python does it.
|
|
300
|
+
if (parsed.sessionId) rows = rows.filter((row) => row.sessionId === parsed.sessionId);
|
|
301
|
+
if (parsed.since) {
|
|
302
|
+
const since = parsed.since;
|
|
303
|
+
rows = rows.filter((row) => (row.calledAt || "") >= since);
|
|
304
|
+
}
|
|
305
|
+
return { stdout: renderLlmCallRows(rows), stderr: "", exitCode: 0 };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Port of `cmd_inspect_embedding_provenance`. */
|
|
309
|
+
export function runInspectEmbeddingProvenance(
|
|
310
|
+
db: Database,
|
|
311
|
+
argv: readonly string[],
|
|
312
|
+
): LedgerCommandResult {
|
|
313
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
314
|
+
const token = argv[index] as string;
|
|
315
|
+
if (token === "--mirror-home") {
|
|
316
|
+
if (argv[index + 1] === undefined) {
|
|
317
|
+
return usageError("argument --mirror-home: expected one argument");
|
|
318
|
+
}
|
|
319
|
+
index += 1;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
return usageError(`unrecognized arguments: ${token}`);
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
stdout: `${renderEmbeddingProvenance(countMemoriesByEmbeddingModel(db))}\n`,
|
|
326
|
+
stderr: "",
|
|
327
|
+
exitCode: 0,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two `llm_calls` ledger hooks every provider-crossing leaf wires
|
|
3
|
+
* (CV22.DS8.US3).
|
|
4
|
+
*
|
|
5
|
+
* Python has one authority for this — `build_llm_logger(store, role=...)` —
|
|
6
|
+
* and every call site passes through it. TypeScript grew three hand-written
|
|
7
|
+
* copies instead (search, extraction, the close tail), and they did not stay
|
|
8
|
+
* equal: US1 priced the search embedding row and left extraction's unpriced,
|
|
9
|
+
* which only surfaced when a live smoke run reported 7 of 10 rows priced. US3
|
|
10
|
+
* would have added six more copies.
|
|
11
|
+
*
|
|
12
|
+
* So: one builder per shape, options for what genuinely differs between call
|
|
13
|
+
* sites (role, conversation/session attribution, the pipeline's clock, an
|
|
14
|
+
* explicit env for the model pins), and pricing that cannot be forgotten
|
|
15
|
+
* because it is not a per-call-site decision any more.
|
|
16
|
+
*
|
|
17
|
+
* What every row here shares with Python's logger:
|
|
18
|
+
*
|
|
19
|
+
* - **bodies are withheld** unless `MEMORY_LOG_LLM_CALLS=full`, which
|
|
20
|
+
* `logLlmCall` already enforces;
|
|
21
|
+
* - **cost is the static estimate** from the one cost authority. A chat or
|
|
22
|
+
* embedding call has no generation id to fetch a real cost for, so the
|
|
23
|
+
* estimate IS the cost of record. Consult is the exception and logs
|
|
24
|
+
* directly: it fetches a real figure and falls back to the estimate;
|
|
25
|
+
* - **an unknown model prices to `null`**, never `0`, so unpriced spend stays
|
|
26
|
+
* visibly unpriced;
|
|
27
|
+
* - **a failed round trip still lands a row.** No usage comes back, so it is
|
|
28
|
+
* unpriced — but money may already have been spent, and a vanished row
|
|
29
|
+
* would hide it.
|
|
30
|
+
*
|
|
31
|
+
* Under replay no usage comes back either, so every row this writes is
|
|
32
|
+
* unpriced and every existing golden is unchanged.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import type { StatementRunner } from "#db/database.ts";
|
|
36
|
+
import { logLlmCall } from "#observability/llmCalls.ts";
|
|
37
|
+
import {
|
|
38
|
+
type ModelPinOptions,
|
|
39
|
+
resolveEmbeddingModel,
|
|
40
|
+
resolveExtractionModel,
|
|
41
|
+
} from "#providers/config.ts";
|
|
42
|
+
import { computeCost } from "#providers/cost.ts";
|
|
43
|
+
import type { EmbeddingAttemptInfo } from "#providers/embedding.ts";
|
|
44
|
+
import type { LlmResponse } from "#providers/llm.ts";
|
|
45
|
+
|
|
46
|
+
export interface LedgerContext {
|
|
47
|
+
/**
|
|
48
|
+
* The orchestration's clock, not the wall clock: an injected `now` must
|
|
49
|
+
* stamp every row the pipeline writes, the ledger included.
|
|
50
|
+
*/
|
|
51
|
+
now?: () => string;
|
|
52
|
+
conversationId?: string | null;
|
|
53
|
+
sessionId?: string | null;
|
|
54
|
+
/** Explicit env for the model pins, when the caller holds one. */
|
|
55
|
+
env?: Record<string, string | undefined>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** What a chat call reports back to the ledger. */
|
|
59
|
+
export type ChatLedgerHook = (response: LlmResponse, prompt: string) => void;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* One priced row per successful chat call, as Python's `build_llm_logger`
|
|
63
|
+
* writes it. `model` echoes the response's model, falling back to the
|
|
64
|
+
* extraction pin — which is what Python logs.
|
|
65
|
+
*/
|
|
66
|
+
export function chatLedgerHook(
|
|
67
|
+
db: StatementRunner,
|
|
68
|
+
role: string,
|
|
69
|
+
context: LedgerContext = {},
|
|
70
|
+
): ChatLedgerHook {
|
|
71
|
+
const pins: ModelPinOptions = context.env ? { env: context.env } : {};
|
|
72
|
+
return (response, prompt) => {
|
|
73
|
+
const model = response.model ?? resolveExtractionModel(pins);
|
|
74
|
+
logLlmCall(
|
|
75
|
+
db,
|
|
76
|
+
{
|
|
77
|
+
role,
|
|
78
|
+
model,
|
|
79
|
+
prompt,
|
|
80
|
+
response: response.content,
|
|
81
|
+
promptTokens: response.promptTokens ?? null,
|
|
82
|
+
completionTokens: response.completionTokens ?? null,
|
|
83
|
+
latencyMs: response.latencyMs ?? null,
|
|
84
|
+
costUsd: computeCost(
|
|
85
|
+
model,
|
|
86
|
+
response.promptTokens ?? null,
|
|
87
|
+
response.completionTokens ?? null,
|
|
88
|
+
),
|
|
89
|
+
conversationId: context.conversationId ?? null,
|
|
90
|
+
sessionId: context.sessionId ?? null,
|
|
91
|
+
},
|
|
92
|
+
context.now ? { now: context.now } : {},
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* One row per embedding ROUND TRIP, wired to `generateEmbeddingSafely`'s
|
|
99
|
+
* `onAttempt`. "A vector is not text", so `response` is always empty; the
|
|
100
|
+
* input text rides the same body-withholding rule as every other row.
|
|
101
|
+
*/
|
|
102
|
+
export function embeddingLedgerHook(
|
|
103
|
+
db: StatementRunner,
|
|
104
|
+
context: LedgerContext = {},
|
|
105
|
+
): (info: EmbeddingAttemptInfo) => void {
|
|
106
|
+
const pins: ModelPinOptions = context.env ? { env: context.env } : {};
|
|
107
|
+
return (info) => {
|
|
108
|
+
const model = resolveEmbeddingModel(pins);
|
|
109
|
+
logLlmCall(
|
|
110
|
+
db,
|
|
111
|
+
{
|
|
112
|
+
role: "embedding",
|
|
113
|
+
model,
|
|
114
|
+
prompt: info.text,
|
|
115
|
+
response: "",
|
|
116
|
+
latencyMs: info.latencyMs,
|
|
117
|
+
promptTokens: info.promptTokens,
|
|
118
|
+
// An embedding has no completion side; Python prices it the same way.
|
|
119
|
+
costUsd: computeCost(model, info.promptTokens, null),
|
|
120
|
+
conversationId: context.conversationId ?? null,
|
|
121
|
+
sessionId: context.sessionId ?? null,
|
|
122
|
+
},
|
|
123
|
+
context.now ? { now: context.now } : {},
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM call observability logging (AI-09, CV9.E2.S13/S14), scoped to the
|
|
3
|
+
* consult path only -- see CR040 plan for why the broader Python
|
|
4
|
+
* `build_llm_logger` six-call-site retrofit (extraction, task-extraction,
|
|
5
|
+
* consolidation, shadow, journal) is explicitly out of scope here.
|
|
6
|
+
*
|
|
7
|
+
* Deliberately does NOT port `compute_cost`/`MODEL_PRICES`: consult always
|
|
8
|
+
* logs the real fetched generation cost, never a static per-token estimate --
|
|
9
|
+
* none of consult's models are in Python's 2-entry price table either.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Database, StatementRunner } from "#db/database.ts";
|
|
13
|
+
import { type LogLlmCallsMode, resolveLogLlmCallsMode } from "#providers/config.ts";
|
|
14
|
+
import { newId, nowIso } from "#util/pyGenerators.ts";
|
|
15
|
+
|
|
16
|
+
export interface LogLlmCallInput {
|
|
17
|
+
role: string;
|
|
18
|
+
model: string;
|
|
19
|
+
prompt: string;
|
|
20
|
+
response: string;
|
|
21
|
+
promptTokens?: number | null;
|
|
22
|
+
completionTokens?: number | null;
|
|
23
|
+
latencyMs?: number | null;
|
|
24
|
+
costUsd?: number | null;
|
|
25
|
+
conversationId?: string | null;
|
|
26
|
+
sessionId?: string | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface LogLlmCallOptions {
|
|
30
|
+
mode?: LogLlmCallsMode;
|
|
31
|
+
now?: () => string;
|
|
32
|
+
id?: () => string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Insert one row into `llm_calls`, mirroring Python's `log_llm_call` column
|
|
37
|
+
* order exactly. Fail-soft by design: a logging failure is swallowed, never
|
|
38
|
+
* propagated -- "observability must never break the pipeline it observes"
|
|
39
|
+
* (the same principle behind Python's build_llm_logger try/except).
|
|
40
|
+
*
|
|
41
|
+
* In `metadata` mode (the default) prompt/response are written as empty
|
|
42
|
+
* strings, never the real bodies -- consult's prompt carries identity
|
|
43
|
+
* context, so withholding it by default is a security property, not a
|
|
44
|
+
* formatting choice. Only explicit `full` mode persists bodies.
|
|
45
|
+
*/
|
|
46
|
+
export function logLlmCall(
|
|
47
|
+
db: StatementRunner,
|
|
48
|
+
input: LogLlmCallInput,
|
|
49
|
+
options: LogLlmCallOptions = {},
|
|
50
|
+
): void {
|
|
51
|
+
const mode = options.mode ?? resolveLogLlmCallsMode();
|
|
52
|
+
if (mode === "off") return;
|
|
53
|
+
const withBodies = mode === "full";
|
|
54
|
+
try {
|
|
55
|
+
db.prepare(
|
|
56
|
+
`INSERT INTO llm_calls (
|
|
57
|
+
id, role, model, prompt, response,
|
|
58
|
+
prompt_tokens, completion_tokens, latency_ms, cost_usd,
|
|
59
|
+
conversation_id, session_id, called_at
|
|
60
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
61
|
+
).run(
|
|
62
|
+
(options.id ?? newId)(),
|
|
63
|
+
input.role,
|
|
64
|
+
input.model,
|
|
65
|
+
withBodies ? input.prompt : "",
|
|
66
|
+
withBodies ? input.response : "",
|
|
67
|
+
input.promptTokens ?? null,
|
|
68
|
+
input.completionTokens ?? null,
|
|
69
|
+
input.latencyMs ?? null,
|
|
70
|
+
input.costUsd ?? null,
|
|
71
|
+
input.conversationId ?? null,
|
|
72
|
+
input.sessionId ?? null,
|
|
73
|
+
(options.now ?? nowIso)(),
|
|
74
|
+
);
|
|
75
|
+
} catch {
|
|
76
|
+
// Fail-soft: never let observability break the caller.
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface LlmCallSummaryBucket {
|
|
81
|
+
bucket: string;
|
|
82
|
+
calls: number;
|
|
83
|
+
promptTokens: number;
|
|
84
|
+
completionTokens: number;
|
|
85
|
+
costUsd: number | null;
|
|
86
|
+
unpriced: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface LlmCallSummary {
|
|
90
|
+
byRole: LlmCallSummaryBucket[];
|
|
91
|
+
byWeek: LlmCallSummaryBucket[];
|
|
92
|
+
total: LlmCallSummaryBucket;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Aggregate spend by role and by week, plus an overall total. Mirrors
|
|
97
|
+
* Python's `get_llm_call_summary`: `SUM(cost_usd)` skips NULLs, so an
|
|
98
|
+
* all-unpriced bucket reports `costUsd: null` with a non-zero `unpriced`
|
|
99
|
+
* count -- unpriced spend stays visibly unpriced, never summed as 0.
|
|
100
|
+
*
|
|
101
|
+
* Shipped without a CLI renderer (no TS `inspect` command exists -- see
|
|
102
|
+
* CR040 plan) because it operates on real data once logLlmCall ships, and is
|
|
103
|
+
* independently tested; an unconsumed but tested query is not dead code.
|
|
104
|
+
*/
|
|
105
|
+
export function getLlmCallSummary(
|
|
106
|
+
// A READ: widened from `WritableDatabase` at CV22.DS7.TS4 plateau 2, when
|
|
107
|
+
// `inspect llm-calls --summary` gave it its first caller and that caller
|
|
108
|
+
// opens the database read-only.
|
|
109
|
+
db: Database,
|
|
110
|
+
options: { since?: string } = {},
|
|
111
|
+
): LlmCallSummary {
|
|
112
|
+
const where = options.since ? "WHERE called_at >= ?" : "";
|
|
113
|
+
const params = options.since ? [options.since] : [];
|
|
114
|
+
|
|
115
|
+
const aggregate = (bucketExpr: string, order: string): LlmCallSummaryBucket[] => {
|
|
116
|
+
const rows = db
|
|
117
|
+
.prepare(
|
|
118
|
+
`SELECT ${bucketExpr} AS bucket,
|
|
119
|
+
COUNT(*) AS calls,
|
|
120
|
+
COALESCE(SUM(prompt_tokens), 0) AS prompt_tokens,
|
|
121
|
+
COALESCE(SUM(completion_tokens), 0) AS completion_tokens,
|
|
122
|
+
SUM(cost_usd) AS cost_usd,
|
|
123
|
+
SUM(CASE WHEN cost_usd IS NULL THEN 1 ELSE 0 END) AS unpriced
|
|
124
|
+
FROM llm_calls
|
|
125
|
+
${where}
|
|
126
|
+
GROUP BY bucket
|
|
127
|
+
ORDER BY ${order}`,
|
|
128
|
+
)
|
|
129
|
+
.all(...params);
|
|
130
|
+
return rows.map((row) => ({
|
|
131
|
+
bucket: String(row.bucket),
|
|
132
|
+
calls: Number(row.calls),
|
|
133
|
+
promptTokens: Number(row.prompt_tokens),
|
|
134
|
+
completionTokens: Number(row.completion_tokens),
|
|
135
|
+
costUsd: row.cost_usd === null ? null : Number(row.cost_usd),
|
|
136
|
+
unpriced: Number(row.unpriced),
|
|
137
|
+
}));
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const byRole = aggregate("role", "bucket");
|
|
141
|
+
const byWeek = aggregate("strftime('%Y-W%W', called_at)", "bucket DESC");
|
|
142
|
+
|
|
143
|
+
const priced = byRole.map((b) => b.costUsd).filter((cost): cost is number => cost !== null);
|
|
144
|
+
const total: LlmCallSummaryBucket = {
|
|
145
|
+
bucket: "TOTAL",
|
|
146
|
+
calls: byRole.reduce((sum, b) => sum + b.calls, 0),
|
|
147
|
+
promptTokens: byRole.reduce((sum, b) => sum + b.promptTokens, 0),
|
|
148
|
+
completionTokens: byRole.reduce((sum, b) => sum + b.completionTokens, 0),
|
|
149
|
+
costUsd: priced.length > 0 ? priced.reduce((sum, cost) => sum + cost, 0) : null,
|
|
150
|
+
unpriced: byRole.reduce((sum, b) => sum + b.unpriced, 0),
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return { byRole, byWeek, total };
|
|
154
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// `detect-persona` parity port (CV22.DS2.US2).
|
|
2
|
+
//
|
|
3
|
+
// A faithful TypeScript port of Python `IdentityService.detect_persona`
|
|
4
|
+
// (`src/memory/services/identity.py`). The router is pure and deterministic — it
|
|
5
|
+
// reads DB-backed routing metadata only, no clock and no embeddings — so parity
|
|
6
|
+
// is exact behavioral equality, not the ordered-id-with-float-drift metric the
|
|
7
|
+
// hybrid ranker uses.
|
|
8
|
+
//
|
|
9
|
+
// This module scores an already-decoded persona routing table; reading the
|
|
10
|
+
// `persona` identity rows out of the SQLite seam belongs to the caller (the DS3
|
|
11
|
+
// front door and the real-DB-copy harness), keeping this the pure decision core.
|
|
12
|
+
|
|
13
|
+
/** One persona's routing table, as parsed from `identity.metadata`. */
|
|
14
|
+
export interface PersonaRoutingRow {
|
|
15
|
+
key: string;
|
|
16
|
+
routing_keywords: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A routing decision: the persona, its hit count, and the match kind. */
|
|
20
|
+
export interface PersonaMatch {
|
|
21
|
+
key: string;
|
|
22
|
+
score: number;
|
|
23
|
+
matchType: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Normalize routing text, mirroring Python `_normalize_routing_text`:
|
|
28
|
+
* lowercase, turn `-`/`_` into spaces, replace any run of non-alphanumeric,
|
|
29
|
+
* non-whitespace characters with a space, then collapse whitespace and trim.
|
|
30
|
+
*
|
|
31
|
+
* JS `\s` and Python `re`'s `\s` both match Unicode whitespace, and ASCII
|
|
32
|
+
* `[a-z0-9]` is identical across engines, so the synthetic (ASCII) corpus is
|
|
33
|
+
* reproduced exactly.
|
|
34
|
+
*/
|
|
35
|
+
export function normalizeRoutingText(text: string): string {
|
|
36
|
+
const lowered = text.toLowerCase().replaceAll("-", " ").replaceAll("_", " ");
|
|
37
|
+
const normalized = lowered.replace(/[^a-z0-9\s]+/g, " ");
|
|
38
|
+
return normalized.replace(/\s+/g, " ").trim();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Detect likely personas from routing metadata, reproducing the Python oracle.
|
|
43
|
+
*
|
|
44
|
+
* A single-word keyword must match a whole query token (set membership); a
|
|
45
|
+
* multi-word keyword (including one that becomes multi-word after normalization,
|
|
46
|
+
* e.g. `savings-plan` -> `savings plan`) matches as a raw substring of the
|
|
47
|
+
* normalized query — no word boundary, exactly like Python's `in`. Personas with
|
|
48
|
+
* `hitCount >= threshold` are returned, sorted by score descending then key
|
|
49
|
+
* ascending, matching `matches.sort(key=lambda i: (-i[1], i[0]))`.
|
|
50
|
+
*/
|
|
51
|
+
export function detectPersona(
|
|
52
|
+
query: string,
|
|
53
|
+
personas: readonly PersonaRoutingRow[],
|
|
54
|
+
threshold = 1.0,
|
|
55
|
+
): PersonaMatch[] {
|
|
56
|
+
const normalizedQuery = normalizeRoutingText(query);
|
|
57
|
+
if (!normalizedQuery) return [];
|
|
58
|
+
|
|
59
|
+
const queryTokens = new Set(normalizedQuery.split(" ").filter(Boolean));
|
|
60
|
+
const matches: PersonaMatch[] = [];
|
|
61
|
+
|
|
62
|
+
for (const persona of personas) {
|
|
63
|
+
let hitCount = 0;
|
|
64
|
+
for (const keyword of persona.routing_keywords) {
|
|
65
|
+
if (typeof keyword !== "string") continue;
|
|
66
|
+
const normalizedKeyword = normalizeRoutingText(keyword);
|
|
67
|
+
if (!normalizedKeyword) continue;
|
|
68
|
+
if (normalizedKeyword.includes(" ")) {
|
|
69
|
+
if (normalizedQuery.includes(normalizedKeyword)) hitCount += 1;
|
|
70
|
+
} else if (queryTokens.has(normalizedKeyword)) {
|
|
71
|
+
hitCount += 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (hitCount >= threshold) {
|
|
75
|
+
matches.push({ key: persona.key, score: hitCount, matchType: "keyword" });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
matches.sort((a, b) => b.score - a.score || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
80
|
+
return matches;
|
|
81
|
+
}
|