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,137 @@
|
|
|
1
|
+
// The five MCP tools that never cross a provider (CV22.DS9.US2 plateau 3).
|
|
2
|
+
//
|
|
3
|
+
// `list_journeys`, `journey_status`, `list_conversations`, `recall_conversation`,
|
|
4
|
+
// and `detect_persona` are plain database reads. Each returns the exact text its
|
|
5
|
+
// Python handler returns -- `json.dumps(value, ensure_ascii=False, indent=2,
|
|
6
|
+
// default=str)` -- which is what the model on the other end reads.
|
|
7
|
+
//
|
|
8
|
+
// The two provider-crossing tools (`search_memories` with a query, and
|
|
9
|
+
// `mirror_context`) live in their own module.
|
|
10
|
+
|
|
11
|
+
import { listRecentConversationSummaries } from "#conversation/listing.ts";
|
|
12
|
+
import { findConversationByIdPrefix, pythonTailSliceStart } from "#conversation/recall.ts";
|
|
13
|
+
import type { Database, Row } from "#db/database.ts";
|
|
14
|
+
import { optionalString, requireString } from "#db/rowDecode.ts";
|
|
15
|
+
import { detectPersona, type PersonaRoutingRow } from "#persona/detectPersona.ts";
|
|
16
|
+
import { pyFloat, pythonJson } from "../payload.ts";
|
|
17
|
+
import { journeyStatus, listActiveJourneyDtos, recallMessages } from "../readModels.ts";
|
|
18
|
+
|
|
19
|
+
/** Python's `_json`: pretty, unescaped, two-space indent. */
|
|
20
|
+
export function toolJson(value: unknown): string {
|
|
21
|
+
return pythonJson(value, { indent: 2 });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Read an argument that Python would have taken straight off the dict. */
|
|
25
|
+
function argument(args: unknown, key: string): unknown {
|
|
26
|
+
if (args === null || typeof args !== "object" || Array.isArray(args)) return undefined;
|
|
27
|
+
return (args as Record<string, unknown>)[key];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Python's `int(args.get("limit", fallback))`.
|
|
32
|
+
*
|
|
33
|
+
* `int("5")` and `int(5.9)` both succeed in Python, so a string or float limit
|
|
34
|
+
* is coerced rather than rejected. Argument VALIDATION is CV22.DS9.TS1's; this
|
|
35
|
+
* reproduces the oracle, including the limits US1's threat model recorded as
|
|
36
|
+
* unbounded.
|
|
37
|
+
*/
|
|
38
|
+
function pythonInt(value: unknown, fallback: number): number {
|
|
39
|
+
if (value === undefined || value === null) return fallback;
|
|
40
|
+
if (typeof value === "number") return Math.trunc(value);
|
|
41
|
+
if (typeof value === "string") {
|
|
42
|
+
const parsed = Number(value.trim());
|
|
43
|
+
if (Number.isFinite(parsed)) return Math.trunc(parsed);
|
|
44
|
+
throw new Error(`invalid literal for int() with base 10: '${value}'`);
|
|
45
|
+
}
|
|
46
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
47
|
+
throw new Error(`int() argument must be a string or a number, not '${typeof value}'`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function listJourneysTool(db: Database, _args: unknown): string {
|
|
51
|
+
return toolJson(listActiveJourneyDtos(db));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function journeyStatusTool(db: Database, args: unknown): string {
|
|
55
|
+
const slug = argument(args, "slug");
|
|
56
|
+
return toolJson(journeyStatus(db, typeof slug === "string" ? slug : null));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function listConversationsTool(db: Database, args: unknown): string {
|
|
60
|
+
const summaries = listRecentConversationSummaries(db, {
|
|
61
|
+
limit: pythonInt(argument(args, "limit"), 20),
|
|
62
|
+
journey: (argument(args, "journey") as string | undefined) ?? undefined,
|
|
63
|
+
persona: (argument(args, "persona") as string | undefined) ?? undefined,
|
|
64
|
+
});
|
|
65
|
+
return toolJson(
|
|
66
|
+
summaries.map((summary) => ({
|
|
67
|
+
id: summary.id,
|
|
68
|
+
title: summary.title,
|
|
69
|
+
started_at: summary.started_at,
|
|
70
|
+
persona: summary.persona,
|
|
71
|
+
journey: summary.journey,
|
|
72
|
+
message_count: summary.message_count,
|
|
73
|
+
})),
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function recallConversationTool(db: Database, args: unknown): string {
|
|
78
|
+
const conversationId = argument(args, "conversation_id");
|
|
79
|
+
if (!conversationId) throw new Error("'conversation_id' is required");
|
|
80
|
+
const conversation = findConversationByIdPrefix(db, String(conversationId));
|
|
81
|
+
if (conversation === null) throw new Error(`no conversation matching '${conversationId}'`);
|
|
82
|
+
|
|
83
|
+
const limit = pythonInt(argument(args, "limit"), 50);
|
|
84
|
+
const messages = recallMessages(db, conversation.id);
|
|
85
|
+
// `messages[-limit:]` -- and `-0 == 0` in Python, so limit=0 returns the WHOLE
|
|
86
|
+
// transcript rather than none. Recorded in the golden and owned by the DS9
|
|
87
|
+
// threat model; TS1 caps it, and this stays faithful until it does.
|
|
88
|
+
const tail = messages.slice(pythonTailSliceStart(messages.length, limit));
|
|
89
|
+
|
|
90
|
+
return toolJson({
|
|
91
|
+
conversation_id: conversation.id,
|
|
92
|
+
messages: tail.map((message) => ({
|
|
93
|
+
role: message.role,
|
|
94
|
+
content: message.content,
|
|
95
|
+
created_at: message.created_at,
|
|
96
|
+
})),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Python's `_persona_metadata(identity).get("routing_keywords") or []`. */
|
|
101
|
+
function routingKeywords(metadata: unknown): string[] {
|
|
102
|
+
if (typeof metadata !== "string" || metadata === "") return [];
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(metadata) as Record<string, unknown>;
|
|
105
|
+
if (!Array.isArray(parsed.routing_keywords)) return [];
|
|
106
|
+
return parsed.routing_keywords.filter((item): item is string => typeof item === "string");
|
|
107
|
+
} catch {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function personaRoutingRows(db: Database): PersonaRoutingRow[] {
|
|
113
|
+
return db
|
|
114
|
+
.prepare("SELECT key, metadata FROM identity WHERE layer = 'persona' ORDER BY key")
|
|
115
|
+
.all()
|
|
116
|
+
.map((row: Row) => ({
|
|
117
|
+
key: requireString(row, "key"),
|
|
118
|
+
routing_keywords: routingKeywords(optionalString(row, "metadata")),
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function detectPersonaTool(db: Database, args: unknown): string {
|
|
123
|
+
const query = argument(args, "query");
|
|
124
|
+
if (!query) throw new Error("'query' is required");
|
|
125
|
+
const matches = detectPersona(String(query), personaRoutingRows(db));
|
|
126
|
+
return toolJson(
|
|
127
|
+
matches.map((match) => ({
|
|
128
|
+
persona: match.key,
|
|
129
|
+
// Python types this as float, and hit counts are routinely whole -- 2.0,
|
|
130
|
+
// not 2. JavaScript cannot tell the two apart, so the mapper says so.
|
|
131
|
+
score: pyFloat(match.score),
|
|
132
|
+
// Python appends the literal "keyword" as the third tuple element; the TS
|
|
133
|
+
// port carries it as `matchType`, and the tool renders it as `descriptor`.
|
|
134
|
+
descriptor: match.matchType,
|
|
135
|
+
})),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// The two MCP tools that can reach a provider (CV22.DS9.US2 plateau 4).
|
|
2
|
+
//
|
|
3
|
+
// `search_memories` with a `query` embeds it; `mirror_context` with a `query`
|
|
4
|
+
// reaches attachment search and every installed extension context provider.
|
|
5
|
+
// Both are the paid, fan-out paths the DS9 threat model names -- and, per the
|
|
6
|
+
// AI-19 rider, the ones DS9.TS1 will put a wallet guard in front of.
|
|
7
|
+
//
|
|
8
|
+
// Everything else here is a deterministic read: `search_memories` without a
|
|
9
|
+
// query is a filter over `memories`, and `mirror_context` without one assembles
|
|
10
|
+
// identity layers from the database.
|
|
11
|
+
|
|
12
|
+
import type { Database } from "#db/database.ts";
|
|
13
|
+
import { collectExtensionContext } from "#extensions/contextRuntime.ts";
|
|
14
|
+
import { loadMirrorContext } from "#mirror/context.ts";
|
|
15
|
+
import type { EmbeddingAttemptInfo, EmbeddingProvider } from "#providers/embedding.ts";
|
|
16
|
+
import { searchMemories } from "#search/memorySearch.ts";
|
|
17
|
+
import { pyFloat, pythonJson } from "../payload.ts";
|
|
18
|
+
import { type MemoryDto, memoriesByFilter, memoriesByIds } from "../readModels.ts";
|
|
19
|
+
|
|
20
|
+
/** What the tools need from the process to reach providers and extensions. */
|
|
21
|
+
export interface ToolRuntime {
|
|
22
|
+
embeddingProvider: EmbeddingProvider;
|
|
23
|
+
/**
|
|
24
|
+
* Where an agent-initiated embedding is recorded as spend (CV22.DS9.TS2 D1).
|
|
25
|
+
*
|
|
26
|
+
* A FUNCTION, never a database handle, and the distinction is the control:
|
|
27
|
+
* the `llm_calls`-only connection lives in `main.ts` and is never reachable
|
|
28
|
+
* from a tool, so no tool -- present or future -- can write an
|
|
29
|
+
* agent-influenced value into the ledger. Omitted, nothing is recorded and
|
|
30
|
+
* US2's read-only posture stands unchanged.
|
|
31
|
+
*/
|
|
32
|
+
embeddingLedger?: (info: EmbeddingAttemptInfo) => void;
|
|
33
|
+
/** Absolute mirror home, for extension resolution. Omit to disable fan-out. */
|
|
34
|
+
mirrorHome?: string;
|
|
35
|
+
/** Absolute database path, handed to extension providers. */
|
|
36
|
+
databasePath?: string;
|
|
37
|
+
user?: string;
|
|
38
|
+
/** Frozen clock for the ranker, used by the parity harness. */
|
|
39
|
+
frozenNowMs?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function toolJson(value: unknown): string {
|
|
43
|
+
return pythonJson(value, { indent: 2 });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function argument(args: unknown, key: string): unknown {
|
|
47
|
+
if (args === null || typeof args !== "object" || Array.isArray(args)) return undefined;
|
|
48
|
+
return (args as Record<string, unknown>)[key];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function pythonInt(value: unknown, fallback: number): number {
|
|
52
|
+
if (value === undefined || value === null) return fallback;
|
|
53
|
+
if (typeof value === "number") return Math.trunc(value);
|
|
54
|
+
if (typeof value === "string") {
|
|
55
|
+
const parsed = Number(value.trim());
|
|
56
|
+
if (Number.isFinite(parsed)) return Math.trunc(parsed);
|
|
57
|
+
throw new Error(`invalid literal for int() with base 10: '${value}'`);
|
|
58
|
+
}
|
|
59
|
+
if (typeof value === "boolean") return value ? 1 : 0;
|
|
60
|
+
throw new Error(`int() argument must be a string or a number, not '${typeof value}'`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function optionalText(value: unknown): string | undefined {
|
|
64
|
+
return typeof value === "string" && value !== "" ? value : undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Port of `_search_memories`.
|
|
69
|
+
*
|
|
70
|
+
* With a `query`: hybrid search, then the ranked ids mapped back to full rows.
|
|
71
|
+
* Without: the first of `journey`, `layer`, `type` that is present, sliced to
|
|
72
|
+
* `limit`; none of them is an error. Python's order of preference is exactly
|
|
73
|
+
* this, and the error text is its text.
|
|
74
|
+
*/
|
|
75
|
+
export async function searchMemoriesTool(
|
|
76
|
+
db: Database,
|
|
77
|
+
args: unknown,
|
|
78
|
+
runtime: ToolRuntime,
|
|
79
|
+
): Promise<string> {
|
|
80
|
+
const query = argument(args, "query");
|
|
81
|
+
const limit = pythonInt(argument(args, "limit"), 5);
|
|
82
|
+
const memoryType = optionalText(argument(args, "type"));
|
|
83
|
+
const layer = optionalText(argument(args, "layer"));
|
|
84
|
+
const journey = optionalText(argument(args, "journey"));
|
|
85
|
+
|
|
86
|
+
if (query) {
|
|
87
|
+
const ranked = await searchMemories(db, {
|
|
88
|
+
query: String(query),
|
|
89
|
+
limit,
|
|
90
|
+
memoryType: memoryType ?? null,
|
|
91
|
+
layer: layer ?? null,
|
|
92
|
+
journey: journey ?? null,
|
|
93
|
+
provider: runtime.embeddingProvider,
|
|
94
|
+
// AI-12: an agent searching on its own behalf is not a genuine context
|
|
95
|
+
// load, so it must not teach the ranker. Python's MCP handler passes
|
|
96
|
+
// log_access=False for exactly this reason.
|
|
97
|
+
logAccess: false,
|
|
98
|
+
// Spend IS recorded, through the sink rather than through this handle
|
|
99
|
+
// (TS2 D1): the tools' database is read-only at the driver level, and the
|
|
100
|
+
// ledger connection it never sees can form no statement but the append.
|
|
101
|
+
// Without a sink, nothing is written -- US2's posture, unchanged.
|
|
102
|
+
recordEmbeddingLedger: runtime.embeddingLedger ?? false,
|
|
103
|
+
...(runtime.frozenNowMs === undefined ? {} : { frozenNowMs: runtime.frozenNowMs }),
|
|
104
|
+
});
|
|
105
|
+
const rows = memoriesByIds(
|
|
106
|
+
db,
|
|
107
|
+
ranked.map((result) => result.id),
|
|
108
|
+
);
|
|
109
|
+
const byId = new Map(rows.map((row) => [row.id, row]));
|
|
110
|
+
return toolJson(
|
|
111
|
+
ranked.map((result) => {
|
|
112
|
+
const row = byId.get(result.id);
|
|
113
|
+
return {
|
|
114
|
+
...(row ?? emptyMemoryDto(result.id)),
|
|
115
|
+
// Python types the score as float; a whole score renders 1.0, not 1.
|
|
116
|
+
score: pyFloat(result.score),
|
|
117
|
+
};
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let rows: MemoryDto[];
|
|
123
|
+
if (journey) rows = memoriesByFilter(db, "journey", journey);
|
|
124
|
+
else if (layer) rows = memoriesByFilter(db, "layer", layer);
|
|
125
|
+
else if (memoryType) rows = memoriesByFilter(db, "memory_type", memoryType);
|
|
126
|
+
else throw new Error("provide 'query' or one of 'type' / 'layer' / 'journey'");
|
|
127
|
+
|
|
128
|
+
return toolJson(rows.slice(0, Math.max(0, limit)));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function emptyMemoryDto(id: string): MemoryDto {
|
|
132
|
+
return {
|
|
133
|
+
id,
|
|
134
|
+
title: null,
|
|
135
|
+
memory_type: null,
|
|
136
|
+
layer: null,
|
|
137
|
+
journey: null,
|
|
138
|
+
tags: null,
|
|
139
|
+
content: null,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Port of `_mirror_context`.
|
|
145
|
+
*
|
|
146
|
+
* Deliberately not the Mirror Mode load: that one activates the mode and
|
|
147
|
+
* mutates sticky defaults. This is the side-effect-free context builder, and it
|
|
148
|
+
* keeps `load_mirror_context`'s defaults -- including reinforcement, because
|
|
149
|
+
* unlike an agent search this IS a genuine context load (AI-12).
|
|
150
|
+
*
|
|
151
|
+
* Extension context is resolved through the same `collectExtensionContext` the
|
|
152
|
+
* front door uses (plan D4), not a second reading of the `mirror-context-v1`
|
|
153
|
+
* contract. The agent-supplied `query` reaches every installed provider, which
|
|
154
|
+
* the DS9 threat model names as the fan-out path.
|
|
155
|
+
*/
|
|
156
|
+
export async function mirrorContextTool(
|
|
157
|
+
db: Database,
|
|
158
|
+
args: unknown,
|
|
159
|
+
runtime: ToolRuntime,
|
|
160
|
+
): Promise<string> {
|
|
161
|
+
const persona = optionalText(argument(args, "persona"));
|
|
162
|
+
const journey = optionalText(argument(args, "journey"));
|
|
163
|
+
const query = optionalText(argument(args, "query"));
|
|
164
|
+
|
|
165
|
+
const extensionContext =
|
|
166
|
+
runtime.mirrorHome && runtime.databasePath
|
|
167
|
+
? collectExtensionContext(db, {
|
|
168
|
+
mirrorHome: runtime.mirrorHome,
|
|
169
|
+
databasePath: runtime.databasePath,
|
|
170
|
+
personaId: persona ?? null,
|
|
171
|
+
journeyId: journey ?? null,
|
|
172
|
+
user: runtime.user ?? "",
|
|
173
|
+
query: query ?? null,
|
|
174
|
+
}).rendered
|
|
175
|
+
: "";
|
|
176
|
+
|
|
177
|
+
return loadMirrorContext(db, {
|
|
178
|
+
persona: persona ?? null,
|
|
179
|
+
journey: journey ?? null,
|
|
180
|
+
query: query ?? null,
|
|
181
|
+
embeddingProvider: runtime.embeddingProvider,
|
|
182
|
+
// TS1: attachment search is the tool's paid path and neither engine recorded it.
|
|
183
|
+
// Same sink as `search_memories`, so one window counts the whole surface.
|
|
184
|
+
...(runtime.embeddingLedger ? { embeddingLedger: runtime.embeddingLedger } : {}),
|
|
185
|
+
extensionContext,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Kept as the US1 call site's name; the rules live in payload.ts (US2 D8).
|
|
2
|
+
//
|
|
3
|
+
// US1 shipped this module with its own copy of the separator logic. US2 needed
|
|
4
|
+
// the same semantics with `indent=2` and a float rule, so the two were folded
|
|
5
|
+
// into one encoder rather than maintained twice. This re-export keeps US1's
|
|
6
|
+
// imports and tests unmoved.
|
|
7
|
+
|
|
8
|
+
export { encodeJsonLine } from "./payload.ts";
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `journal` — CV22.DS7.US11 plateau 3.
|
|
3
|
+
*
|
|
4
|
+
* Records a journal entry: classify it through the model, then write the memory
|
|
5
|
+
* with its embedding. CR068 found this leaf unowned — US2 sent it to US5, US5
|
|
6
|
+
* was re-scoped without it, and the ledger reported the family `3/3 done` while
|
|
7
|
+
* Python answered every call.
|
|
8
|
+
*
|
|
9
|
+
* Graded by `ts/test/goldens/journal.golden.json`. The corpus pins several
|
|
10
|
+
* Python behaviours that a reasonable port gets wrong:
|
|
11
|
+
*
|
|
12
|
+
* - **AI-24 layer coercion.** A model layer outside `self|ego|shadow` becomes
|
|
13
|
+
* `ego` rather than reaching the write unchecked (CV9.E2.S23/S25).
|
|
14
|
+
* - **The non-JSON fallback** is `{title: content[:60], layer: "ego",
|
|
15
|
+
* tags: []}`, and the 60 is CODE POINTS.
|
|
16
|
+
* - **`", ".join(tags)` over a STRING iterates characters.** When the model
|
|
17
|
+
* returns `"not-a-list"`, Python's receipt reads `n, o, t, -, a, ...`. It
|
|
18
|
+
* looks like a bug and is reproduced deliberately: the receipt is what a
|
|
19
|
+
* user sees, and the row stores the same odd value.
|
|
20
|
+
* - **The embedding is generated BEFORE the row is inserted**, so a failing
|
|
21
|
+
* embedding persists NOTHING — while the classification and the (unpriced)
|
|
22
|
+
* embedding ledger rows already exist. Insert-then-embed passes every other
|
|
23
|
+
* case and fails this one.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { WritableDatabase } from "#db/database.ts";
|
|
27
|
+
import { createMemoryRow } from "#memory/memoryWrite.ts";
|
|
28
|
+
import { addEmbeddingProvenance } from "#providers/embedding.ts";
|
|
29
|
+
import { memoryEmbedText } from "#soul/harvest.ts";
|
|
30
|
+
import { pythonJsonDumpsEnsureAscii } from "#util/pyGenerators.ts";
|
|
31
|
+
import { sliceCodePoints } from "#util/pythonText.ts";
|
|
32
|
+
|
|
33
|
+
/** Python `VALID_MEMORY_LAYERS`. */
|
|
34
|
+
const VALID_MEMORY_LAYERS = new Set(["self", "ego", "shadow"]);
|
|
35
|
+
|
|
36
|
+
/** Python's `layer_labels`, with the raw layer as its own fallback. */
|
|
37
|
+
const LAYER_LABELS: Record<string, string> = {
|
|
38
|
+
self: "Self (identity)",
|
|
39
|
+
ego: "Ego (operational)",
|
|
40
|
+
shadow: "Shadow (tension)",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export interface JournalClassification {
|
|
44
|
+
title: string;
|
|
45
|
+
layer: string;
|
|
46
|
+
tags: unknown;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Python `classify_journal_entry`'s post-processing, given a raw model
|
|
51
|
+
* response body. Kept separate from transport so it can be graded directly.
|
|
52
|
+
*/
|
|
53
|
+
export function interpretJournalClassification(
|
|
54
|
+
responseContent: string,
|
|
55
|
+
content: string,
|
|
56
|
+
parseJson: (raw: string) => unknown,
|
|
57
|
+
): JournalClassification {
|
|
58
|
+
const data = parseJson(responseContent);
|
|
59
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) {
|
|
60
|
+
// Python: `{"title": content[:60], "layer": "ego", "tags": []}`.
|
|
61
|
+
return { title: sliceCodePoints(content, 60), layer: "ego", tags: [] };
|
|
62
|
+
}
|
|
63
|
+
const record = data as Record<string, unknown>;
|
|
64
|
+
const rawLayer = "layer" in record ? record.layer : "ego";
|
|
65
|
+
const layer =
|
|
66
|
+
typeof rawLayer === "string" && VALID_MEMORY_LAYERS.has(rawLayer) ? rawLayer : "ego";
|
|
67
|
+
const title = "title" in record ? record.title : sliceCodePoints(content, 60);
|
|
68
|
+
return {
|
|
69
|
+
title: typeof title === "string" ? title : String(title),
|
|
70
|
+
layer,
|
|
71
|
+
tags: "tags" in record ? record.tags : [],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Python `", ".join(tags)`.
|
|
77
|
+
*
|
|
78
|
+
* `str.join` iterates its argument. Over a list that is the elements; over a
|
|
79
|
+
* STRING it is the characters, which is why a model returning `"not-a-list"`
|
|
80
|
+
* yields `n, o, t, -, a, -, l, i, s, t`. Reproduced, not corrected.
|
|
81
|
+
*
|
|
82
|
+
* The CLI reaches this only after `json.loads(memory.tags)`, so the value seen
|
|
83
|
+
* here is whatever round-tripped through the stored JSON.
|
|
84
|
+
*/
|
|
85
|
+
export function renderJournalTags(tags: unknown): string {
|
|
86
|
+
if (typeof tags === "string") return Array.from(tags).join(", ");
|
|
87
|
+
if (Array.isArray(tags))
|
|
88
|
+
return tags.map((tag) => (typeof tag === "string" ? tag : String(tag))).join(", ");
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Python: `json.dumps(tags) if tags else None` — an empty list stores NULL. */
|
|
93
|
+
function storedTags(tags: unknown): string | null {
|
|
94
|
+
if (tags === null || tags === undefined) return null;
|
|
95
|
+
if (Array.isArray(tags) && tags.length === 0) return null;
|
|
96
|
+
if (typeof tags === "string" && tags.length === 0) return null;
|
|
97
|
+
return pythonJsonDumpsEnsureAscii(tags);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface AddJournalInput {
|
|
101
|
+
content: string;
|
|
102
|
+
journey?: string | null;
|
|
103
|
+
classification: JournalClassification;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface AddJournalDeps {
|
|
107
|
+
newId: () => string;
|
|
108
|
+
nowIso: string;
|
|
109
|
+
/** Throws when the provider fails; called BEFORE the row is inserted. */
|
|
110
|
+
embed: (text: string) => Uint8Array;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface AddJournalResult {
|
|
114
|
+
memoryId: string;
|
|
115
|
+
title: string;
|
|
116
|
+
layer: string;
|
|
117
|
+
tags: unknown;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Python `MemoryService.add_journal` → `add_memory` for the journal path.
|
|
122
|
+
*
|
|
123
|
+
* The embedding is computed first, deliberately: `add_memory` embeds before it
|
|
124
|
+
* builds the row, so a provider failure leaves no memory behind.
|
|
125
|
+
*/
|
|
126
|
+
export function addJournal(
|
|
127
|
+
db: WritableDatabase,
|
|
128
|
+
input: AddJournalInput,
|
|
129
|
+
deps: AddJournalDeps,
|
|
130
|
+
): AddJournalResult {
|
|
131
|
+
const { title, layer, tags } = input.classification;
|
|
132
|
+
|
|
133
|
+
const embedding = deps.embed(memoryEmbedText(title, input.content, null));
|
|
134
|
+
|
|
135
|
+
const memoryId = deps.newId();
|
|
136
|
+
createMemoryRow(db, {
|
|
137
|
+
id: memoryId,
|
|
138
|
+
conversationId: null,
|
|
139
|
+
memoryType: "journal",
|
|
140
|
+
layer,
|
|
141
|
+
title,
|
|
142
|
+
content: input.content,
|
|
143
|
+
context: null,
|
|
144
|
+
journey: input.journey ?? null,
|
|
145
|
+
persona: null,
|
|
146
|
+
tags: storedTags(tags),
|
|
147
|
+
createdAt: deps.nowIso,
|
|
148
|
+
embedding,
|
|
149
|
+
metadata: addEmbeddingProvenance(null),
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return { memoryId, title, layer, tags };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Python's printed receipt, line for line. */
|
|
156
|
+
export function renderJournalReceipt(
|
|
157
|
+
result: AddJournalResult,
|
|
158
|
+
journey: string | null | undefined,
|
|
159
|
+
storedTagsJson: string | null,
|
|
160
|
+
): string {
|
|
161
|
+
const parsedTags: unknown = storedTagsJson === null ? [] : safeJsonParse(storedTagsJson);
|
|
162
|
+
const lines = [
|
|
163
|
+
"📓 Journal entry recorded",
|
|
164
|
+
` Title: ${result.title}`,
|
|
165
|
+
` Layer: ${LAYER_LABELS[result.layer] ?? result.layer}`,
|
|
166
|
+
` Tags: ${renderJournalTags(parsedTags)}`,
|
|
167
|
+
];
|
|
168
|
+
if (journey) lines.push(` Journey: ${journey}`);
|
|
169
|
+
lines.push(` ID: ${result.memoryId.slice(0, 8)}`);
|
|
170
|
+
return `${lines.join("\n")}\n`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Python's `try: json.loads(...) except (JSONDecodeError, TypeError): []`. */
|
|
174
|
+
function safeJsonParse(raw: string): unknown {
|
|
175
|
+
try {
|
|
176
|
+
return JSON.parse(raw);
|
|
177
|
+
} catch {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Memory listing parity port (CV22.DS2.US3).
|
|
2
|
+
//
|
|
3
|
+
// A faithful TypeScript port of Python `Store.list_recent_memory_summaries` and
|
|
4
|
+
// `Store.count_memories_by_type` (`src/memory/storage/memories.py`). Following
|
|
5
|
+
// the database-seam philosophy, the ordering is pushed down to SQLite
|
|
6
|
+
// (`ORDER BY created_at DESC`) — the TS unit is a query builder + row→summary
|
|
7
|
+
// mapper, not a re-implementation of SQLite's sort. The `--search` listing path
|
|
8
|
+
// is out of scope here: it reuses the US1 hybrid ranker.
|
|
9
|
+
|
|
10
|
+
import type { Database, SqlValue } from "#db/database.ts";
|
|
11
|
+
import { optionalString, requireString } from "#db/rowDecode.ts";
|
|
12
|
+
|
|
13
|
+
/** The listing projection, mirroring the Python `MemorySummary` DTO. */
|
|
14
|
+
export interface MemorySummary {
|
|
15
|
+
id: string;
|
|
16
|
+
memory_type: string;
|
|
17
|
+
layer: string;
|
|
18
|
+
title: string;
|
|
19
|
+
content: string;
|
|
20
|
+
context: string | null;
|
|
21
|
+
journey: string | null;
|
|
22
|
+
persona: string | null;
|
|
23
|
+
tags: string | null;
|
|
24
|
+
created_at: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Optional equality filters and limit for the recent-memories read. */
|
|
28
|
+
export interface ListRecentFilters {
|
|
29
|
+
limit?: number;
|
|
30
|
+
memoryType?: string | null;
|
|
31
|
+
layer?: string | null;
|
|
32
|
+
journey?: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const SUMMARY_COLUMNS =
|
|
36
|
+
"id, memory_type, layer, title, content, context, journey, persona, tags, created_at";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Build the parameterized recent-memories query, mirroring the Python builder:
|
|
40
|
+
* a `1=1` seed, one `col = ?` clause per provided filter (in
|
|
41
|
+
* type → layer → journey order), `ORDER BY created_at DESC`, and a trailing
|
|
42
|
+
* `LIMIT ?`. Params are ordered to match the placeholders.
|
|
43
|
+
*/
|
|
44
|
+
export function buildListRecentQuery(filters: ListRecentFilters = {}): {
|
|
45
|
+
sql: string;
|
|
46
|
+
params: SqlValue[];
|
|
47
|
+
} {
|
|
48
|
+
const { limit = 20, memoryType = null, layer = null, journey = null } = filters;
|
|
49
|
+
const conditions = ["1=1"];
|
|
50
|
+
const params: SqlValue[] = [];
|
|
51
|
+
if (memoryType) {
|
|
52
|
+
conditions.push("memory_type = ?");
|
|
53
|
+
params.push(memoryType);
|
|
54
|
+
}
|
|
55
|
+
if (layer) {
|
|
56
|
+
conditions.push("layer = ?");
|
|
57
|
+
params.push(layer);
|
|
58
|
+
}
|
|
59
|
+
if (journey) {
|
|
60
|
+
conditions.push("journey = ?");
|
|
61
|
+
params.push(journey);
|
|
62
|
+
}
|
|
63
|
+
params.push(limit);
|
|
64
|
+
const sql =
|
|
65
|
+
`SELECT ${SUMMARY_COLUMNS} FROM memories ` +
|
|
66
|
+
`WHERE ${conditions.join(" AND ")} ORDER BY created_at DESC LIMIT ?`;
|
|
67
|
+
return { sql, params };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function toSummary(row: Record<string, unknown>): MemorySummary {
|
|
71
|
+
return {
|
|
72
|
+
id: requireString(row, "id"),
|
|
73
|
+
memory_type: requireString(row, "memory_type"),
|
|
74
|
+
layer: requireString(row, "layer"),
|
|
75
|
+
title: requireString(row, "title"),
|
|
76
|
+
content: requireString(row, "content"),
|
|
77
|
+
context: optionalString(row, "context"),
|
|
78
|
+
journey: optionalString(row, "journey"),
|
|
79
|
+
persona: optionalString(row, "persona"),
|
|
80
|
+
tags: optionalString(row, "tags"),
|
|
81
|
+
created_at: requireString(row, "created_at"),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Return recent memory summaries with optional equality filters, newest first. */
|
|
86
|
+
export function listRecentMemorySummaries(
|
|
87
|
+
db: Database,
|
|
88
|
+
filters: ListRecentFilters = {},
|
|
89
|
+
): MemorySummary[] {
|
|
90
|
+
const { sql, params } = buildListRecentQuery(filters);
|
|
91
|
+
return db
|
|
92
|
+
.prepare(sql)
|
|
93
|
+
.all(...params)
|
|
94
|
+
.map(toSummary);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Return memory counts grouped by type. SQLite leaves `GROUP BY` order
|
|
99
|
+
* unspecified, so — like the Python CLI, which sorts before display — callers
|
|
100
|
+
* compare these as a map/sorted set, not as an ordered list.
|
|
101
|
+
*/
|
|
102
|
+
export function countMemoriesByType(db: Database): [string, number][] {
|
|
103
|
+
return db
|
|
104
|
+
.prepare("SELECT memory_type, COUNT(*) as count FROM memories GROUP BY memory_type")
|
|
105
|
+
.all()
|
|
106
|
+
.map((row) => [requireString(row, "memory_type"), Number(row.count)] as [string, number]);
|
|
107
|
+
}
|