mirror-mind 0.0.1 → 0.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/log-session-end.sh +85 -0
- package/.claude/hooks/log-user-prompt.sh +85 -0
- package/.claude/hooks/mirror-inject.sh +85 -0
- package/.claude/hooks/session-start.sh +85 -0
- package/.gemini/hooks/log-assistant.sh +85 -0
- package/.gemini/hooks/log-user.sh +85 -0
- package/.gemini/hooks/session-end.sh +85 -0
- package/.gemini/hooks/session-start.sh +85 -0
- package/.pi/extensions/mirror-logger.ts +561 -0
- package/.pi/skills/mm-backup/SKILL.md +21 -0
- package/.pi/skills/mm-build/SKILL.md +1206 -0
- package/.pi/skills/mm-consolidate/SKILL.md +112 -0
- package/.pi/skills/mm-consult/SKILL.md +70 -0
- package/.pi/skills/mm-conversations/SKILL.md +16 -0
- package/.pi/skills/mm-discard/SKILL.md +39 -0
- package/.pi/skills/mm-explore/SKILL.md +255 -0
- package/.pi/skills/mm-help/SKILL.md +74 -0
- package/.pi/skills/mm-identity/SKILL.md +96 -0
- package/.pi/skills/mm-journal/SKILL.md +16 -0
- package/.pi/skills/mm-journey/SKILL.md +23 -0
- package/.pi/skills/mm-journeys/SKILL.md +15 -0
- package/.pi/skills/mm-memories/SKILL.md +17 -0
- package/.pi/skills/mm-mirror/SKILL.md +98 -0
- package/.pi/skills/mm-mute/SKILL.md +18 -0
- package/.pi/skills/mm-new/SKILL.md +18 -0
- package/.pi/skills/mm-recall/SKILL.md +17 -0
- package/.pi/skills/mm-release-notes/SKILL.md +52 -0
- package/.pi/skills/mm-seed/SKILL.md +19 -0
- package/.pi/skills/mm-shadow/SKILL.md +117 -0
- package/.pi/skills/mm-soul/SKILL.md +502 -0
- package/.pi/skills/mm-tasks/SKILL.md +24 -0
- package/.pi/skills/mm-update/SKILL.md +32 -0
- package/.pi/skills/mm-week/SKILL.md +28 -0
- package/.pi/skills/mm-welcome/SKILL.md +24 -0
- package/AGENTS.md +188 -0
- package/README.md +126 -9
- package/bin/loader.js +67 -0
- package/bin/mirror-hook.js +7 -0
- package/bin/mirror.js +7 -0
- package/docs/releases/index.md +99 -0
- package/docs/releases/pending-cutoffs.md +510 -0
- package/docs/releases/v0.10.0.md +123 -0
- package/docs/releases/v0.10.1.md +66 -0
- package/docs/releases/v0.10.10.md +51 -0
- package/docs/releases/v0.10.11.md +39 -0
- package/docs/releases/v0.10.12.md +57 -0
- package/docs/releases/v0.10.13.md +35 -0
- package/docs/releases/v0.10.14.md +42 -0
- package/docs/releases/v0.10.15.md +53 -0
- package/docs/releases/v0.10.16.md +35 -0
- package/docs/releases/v0.10.17.md +43 -0
- package/docs/releases/v0.10.2.md +64 -0
- package/docs/releases/v0.10.3.md +78 -0
- package/docs/releases/v0.10.4.md +42 -0
- package/docs/releases/v0.10.5.md +57 -0
- package/docs/releases/v0.10.6.md +47 -0
- package/docs/releases/v0.10.7.md +53 -0
- package/docs/releases/v0.10.8.md +49 -0
- package/docs/releases/v0.10.9.md +37 -0
- package/docs/releases/v0.11.0.md +86 -0
- package/docs/releases/v0.12.0.md +67 -0
- package/docs/releases/v0.13.0.md +64 -0
- package/docs/releases/v0.14.0.md +68 -0
- package/docs/releases/v0.15.0.md +48 -0
- package/docs/releases/v0.16.0.md +50 -0
- package/docs/releases/v0.16.1.md +55 -0
- package/docs/releases/v0.16.2.md +36 -0
- package/docs/releases/v0.17.0.md +50 -0
- package/docs/releases/v0.17.1.md +58 -0
- package/docs/releases/v0.17.2.md +42 -0
- package/docs/releases/v0.17.3.md +58 -0
- package/docs/releases/v0.18.0.md +84 -0
- package/docs/releases/v0.18.1.md +51 -0
- package/docs/releases/v0.18.2.md +69 -0
- package/docs/releases/v0.19.0.md +85 -0
- package/docs/releases/v0.19.1.md +46 -0
- package/docs/releases/v0.20.0.md +85 -0
- package/docs/releases/v0.21.0.md +61 -0
- package/docs/releases/v0.21.1.md +47 -0
- package/docs/releases/v0.22.0.md +77 -0
- package/docs/releases/v0.22.1.md +42 -0
- package/docs/releases/v0.23.0.md +105 -0
- package/docs/releases/v0.24.0.md +125 -0
- package/docs/releases/v0.25.0.md +107 -0
- package/docs/releases/v0.26.0.md +139 -0
- package/docs/releases/v0.26.1.md +65 -0
- package/docs/releases/v0.26.2.md +43 -0
- package/docs/releases/v0.27.0.md +63 -0
- package/docs/releases/v0.28.0.md +59 -0
- package/docs/releases/v0.29.0.md +61 -0
- package/docs/releases/v0.29.1.md +59 -0
- package/docs/releases/v0.29.2.md +59 -0
- package/docs/releases/v0.29.3.md +93 -0
- package/docs/releases/v0.30.0.md +75 -0
- package/docs/releases/v0.30.1.md +93 -0
- package/docs/releases/v0.30.2.md +66 -0
- package/docs/releases/v0.31.0.md +122 -0
- package/docs/releases/v0.31.1.md +142 -0
- package/docs/releases/v0.31.10.md +118 -0
- package/docs/releases/v0.31.11.md +88 -0
- package/docs/releases/v0.31.12.md +105 -0
- package/docs/releases/v0.31.13.md +99 -0
- package/docs/releases/v0.31.14.md +97 -0
- package/docs/releases/v0.31.2.md +115 -0
- package/docs/releases/v0.31.3.md +142 -0
- package/docs/releases/v0.31.4.md +117 -0
- package/docs/releases/v0.31.5.md +155 -0
- package/docs/releases/v0.31.6.md +132 -0
- package/docs/releases/v0.31.7.md +132 -0
- package/docs/releases/v0.31.8.md +120 -0
- package/docs/releases/v0.31.9.md +120 -0
- package/docs/releases/v0.32.0.md +261 -0
- package/docs/releases/v0.32.1.md +59 -0
- package/docs/releases/v0.8.0.md +118 -0
- package/docs/releases/v0.9.0.md +98 -0
- package/docs/releases/v0.9.1.md +83 -0
- package/package.json +102 -6
- package/plugins/mirror-mind/.claude-plugin/plugin.json +13 -0
- package/plugins/mirror-mind/hooks/hooks.json +50 -0
- package/plugins/mirror-mind/hooks/log-session-end.sh +98 -0
- package/plugins/mirror-mind/hooks/log-user-prompt.sh +98 -0
- package/plugins/mirror-mind/hooks/mirror-inject.sh +98 -0
- package/plugins/mirror-mind/hooks/session-start.sh +98 -0
- package/plugins/mirror-mind/mcp/launch.sh +52 -0
- package/plugins/mirror-mind/skills/mm-backup/SKILL.md +27 -0
- package/plugins/mirror-mind/skills/mm-build/SKILL.md +1206 -0
- package/plugins/mirror-mind/skills/mm-consolidate/SKILL.md +112 -0
- package/plugins/mirror-mind/skills/mm-consult/SKILL.md +83 -0
- package/plugins/mirror-mind/skills/mm-conversations/SKILL.md +17 -0
- package/plugins/mirror-mind/skills/mm-discard/SKILL.md +40 -0
- package/plugins/mirror-mind/skills/mm-explore/SKILL.md +241 -0
- package/plugins/mirror-mind/skills/mm-help/SKILL.md +104 -0
- package/plugins/mirror-mind/skills/mm-identity/SKILL.md +96 -0
- package/plugins/mirror-mind/skills/mm-journal/SKILL.md +21 -0
- package/plugins/mirror-mind/skills/mm-journey/SKILL.md +37 -0
- package/plugins/mirror-mind/skills/mm-journeys/SKILL.md +15 -0
- package/plugins/mirror-mind/skills/mm-memories/SKILL.md +20 -0
- package/plugins/mirror-mind/skills/mm-mirror/SKILL.md +65 -0
- package/plugins/mirror-mind/skills/mm-mute/SKILL.md +18 -0
- package/plugins/mirror-mind/skills/mm-new/SKILL.md +18 -0
- package/plugins/mirror-mind/skills/mm-recall/SKILL.md +17 -0
- package/plugins/mirror-mind/skills/mm-release-notes/SKILL.md +31 -0
- package/plugins/mirror-mind/skills/mm-seed/SKILL.md +15 -0
- package/plugins/mirror-mind/skills/mm-shadow/SKILL.md +117 -0
- package/plugins/mirror-mind/skills/mm-soul/SKILL.md +488 -0
- package/plugins/mirror-mind/skills/mm-tasks/SKILL.md +36 -0
- package/plugins/mirror-mind/skills/mm-update/SKILL.md +30 -0
- package/plugins/mirror-mind/skills/mm-week/SKILL.md +46 -0
- package/plugins/mirror-mind/skills/mm-welcome/SKILL.md +22 -0
- package/scripts/codex-hooks/session-end.sh +85 -0
- package/scripts/codex-hooks/session-start.sh +85 -0
- package/scripts/codex-mirror.sh +48 -0
- package/templates/identity/README.md +119 -0
- package/templates/identity/ego/behavior.yaml +52 -0
- package/templates/identity/ego/constraints.yaml +15 -0
- package/templates/identity/ego/identity.yaml +43 -0
- package/templates/identity/journeys/personal-growth.yaml +39 -0
- package/templates/identity/organization/identity.yaml +24 -0
- package/templates/identity/organization/principles.yaml +17 -0
- package/templates/identity/personas/coach.yaml +94 -0
- package/templates/identity/personas/designer.yaml +93 -0
- package/templates/identity/personas/doctor.yaml +96 -0
- package/templates/identity/personas/engineer.yaml +77 -0
- package/templates/identity/personas/financial.yaml +97 -0
- package/templates/identity/personas/prompt-engineer.yaml +122 -0
- package/templates/identity/personas/researcher.yaml +88 -0
- package/templates/identity/personas/strategist.yaml +91 -0
- package/templates/identity/personas/teacher.yaml +95 -0
- package/templates/identity/personas/therapist.yaml +91 -0
- package/templates/identity/personas/thinker.yaml +74 -0
- package/templates/identity/personas/writer.yaml +76 -0
- package/templates/identity/self/config.yaml +16 -0
- package/templates/identity/self/soul.yaml +105 -0
- package/templates/identity/user/identity.yaml +30 -0
- package/ts/src/backup/staging.ts +62 -0
- package/ts/src/backup/zipBackup.ts +267 -0
- package/ts/src/backup/zipReader.ts +109 -0
- package/ts/src/backup/zipWriter.ts +148 -0
- package/ts/src/builder/activeCheckpoint.ts +134 -0
- package/ts/src/builder/aggregateStatus.ts +34 -0
- package/ts/src/builder/approve.ts +149 -0
- package/ts/src/builder/argv.ts +292 -0
- package/ts/src/builder/ariadMethod.ts +536 -0
- package/ts/src/builder/artifacts/artifactSurfaces.ts +106 -0
- package/ts/src/builder/artifacts/artifactWriter.ts +113 -0
- package/ts/src/builder/artifacts/closureArtifacts.ts +199 -0
- package/ts/src/builder/artifacts/deliveryStoryArtifacts.ts +316 -0
- package/ts/src/builder/artifacts/planArtifacts.ts +242 -0
- package/ts/src/builder/artifacts/scaffoldSections.ts +273 -0
- package/ts/src/builder/artifacts/scaffoldState.ts +227 -0
- package/ts/src/builder/artifacts/storyFiles.ts +61 -0
- package/ts/src/builder/artifacts/storyIndex.ts +166 -0
- package/ts/src/builder/cadence.ts +59 -0
- package/ts/src/builder/card.ts +192 -0
- package/ts/src/builder/checkpointRefused.ts +66 -0
- package/ts/src/builder/closure.ts +964 -0
- package/ts/src/builder/commands.ts +2053 -0
- package/ts/src/builder/cursorPosition.ts +176 -0
- package/ts/src/builder/cursorTransitions.ts +77 -0
- package/ts/src/builder/deliveryCursor.ts +577 -0
- package/ts/src/builder/deliveryStoryClosure.ts +521 -0
- package/ts/src/builder/deliveryStoryPlan.ts +536 -0
- package/ts/src/builder/deliveryStoryReady.ts +160 -0
- package/ts/src/builder/deliveryStoryRoadmapClosure.ts +151 -0
- package/ts/src/builder/expand.ts +336 -0
- package/ts/src/builder/flowUnit.ts +339 -0
- package/ts/src/builder/homeSurface.ts +122 -0
- package/ts/src/builder/implementationGuard.ts +125 -0
- package/ts/src/builder/index.ts +11 -0
- package/ts/src/builder/lifecycleRefusal.ts +92 -0
- package/ts/src/builder/lifecycleRibbon.ts +159 -0
- package/ts/src/builder/load.ts +313 -0
- package/ts/src/builder/methodAdoption.ts +113 -0
- package/ts/src/builder/methodDefinition.ts +429 -0
- package/ts/src/builder/methodInspection.ts +371 -0
- package/ts/src/builder/plan.ts +408 -0
- package/ts/src/builder/planPreauthorization.ts +265 -0
- package/ts/src/builder/prepare.ts +225 -0
- package/ts/src/builder/projectPaths.ts +64 -0
- package/ts/src/builder/pull.ts +356 -0
- package/ts/src/builder/pullCandidates.ts +463 -0
- package/ts/src/builder/pullCandidatesRender.ts +238 -0
- package/ts/src/builder/refinementField.ts +93 -0
- package/ts/src/builder/releaseIntent.ts +173 -0
- package/ts/src/builder/resumeState.ts +84 -0
- package/ts/src/builder/resumeSurface.ts +149 -0
- package/ts/src/builder/roadmapGrammar.ts +300 -0
- package/ts/src/builder/roadmapScan.ts +97 -0
- package/ts/src/builder/roadmapScope.ts +210 -0
- package/ts/src/builder/scopePhrases.ts +146 -0
- package/ts/src/builder/storyPaths.ts +196 -0
- package/ts/src/builder/storyPlanPreauthorization.ts +287 -0
- package/ts/src/builder/surfaceProtocol.ts +42 -0
- package/ts/src/builder/templateGeneration.ts +148 -0
- package/ts/src/builder/transition.ts +178 -0
- package/ts/src/consult/args.ts +99 -0
- package/ts/src/consult/core.ts +122 -0
- package/ts/src/consult/modelCatalog.ts +55 -0
- package/ts/src/consult/render.ts +47 -0
- package/ts/src/conversation/append.ts +414 -0
- package/ts/src/conversation/backfill.ts +387 -0
- package/ts/src/conversation/closeTail.ts +467 -0
- package/ts/src/conversation/extraction.ts +333 -0
- package/ts/src/conversation/extractionDriver.ts +130 -0
- package/ts/src/conversation/extractionRun.ts +70 -0
- package/ts/src/conversation/journeyInference.ts +165 -0
- package/ts/src/conversation/journeyRepair.ts +114 -0
- package/ts/src/conversation/lifecycleFaces.ts +197 -0
- package/ts/src/conversation/lifecycleWrites.ts +313 -0
- package/ts/src/conversation/listing.ts +77 -0
- package/ts/src/conversation/logger.ts +576 -0
- package/ts/src/conversation/loggerCli.ts +282 -0
- package/ts/src/conversation/loggerRuntime.ts +259 -0
- package/ts/src/conversation/metadataLifecycle.ts +443 -0
- package/ts/src/conversation/recall.ts +66 -0
- package/ts/src/conversation/sessionComposites.ts +258 -0
- package/ts/src/conversation/sessionImport.ts +90 -0
- package/ts/src/conversation/transcriptBackfill.ts +141 -0
- package/ts/src/cultivation/applyActions.ts +241 -0
- package/ts/src/cultivation/cluster.ts +86 -0
- package/ts/src/cultivation/consolidationStore.ts +281 -0
- package/ts/src/cultivation/promptContext.ts +62 -0
- package/ts/src/cultivation/propose.ts +305 -0
- package/ts/src/cultivation/scan.ts +155 -0
- package/ts/src/db/backupGate.ts +47 -0
- package/ts/src/db/bootstrap.ts +108 -0
- package/ts/src/db/bootstrapLock.ts +223 -0
- package/ts/src/db/copyGuard.ts +60 -0
- package/ts/src/db/database.ts +397 -0
- package/ts/src/db/decode.ts +66 -0
- package/ts/src/db/ftsIntegrity.ts +41 -0
- package/ts/src/db/migrateOnOpen.ts +214 -0
- package/ts/src/db/migrations.ts +576 -0
- package/ts/src/db/readyOnOpen.ts +35 -0
- package/ts/src/db/rowDecode.ts +42 -0
- package/ts/src/db/schema.ts +432 -0
- package/ts/src/db/schemaInventory.ts +225 -0
- package/ts/src/db/schemaInventorySnapshot.ts +2107 -0
- package/ts/src/db/schemaState.ts +91 -0
- package/ts/src/db/schemaTsDivergence.ts +114 -0
- package/ts/src/descriptor/descriptorRead.ts +49 -0
- package/ts/src/docs/docsLint.ts +265 -0
- package/ts/src/explorer/handoff.ts +522 -0
- package/ts/src/explorer/render.ts +335 -0
- package/ts/src/explorer/story.ts +769 -0
- package/ts/src/explorer/transition.ts +72 -0
- package/ts/src/extensions/bindings.ts +170 -0
- package/ts/src/extensions/catalog.ts +249 -0
- package/ts/src/extensions/catalogCommands.ts +513 -0
- package/ts/src/extensions/catalogWrites.ts +652 -0
- package/ts/src/extensions/contextRuntime.ts +325 -0
- package/ts/src/extensions/dispatch.ts +374 -0
- package/ts/src/extensions/errors.ts +24 -0
- package/ts/src/extensions/manifest.ts +356 -0
- package/ts/src/extensions/migrations.ts +350 -0
- package/ts/src/extraction/conversation.ts +304 -0
- package/ts/src/extraction/conversationMetadata.ts +206 -0
- package/ts/src/extraction/fencing.ts +106 -0
- package/ts/src/extraction/json.ts +17 -0
- package/ts/src/extraction/prompts.ts +470 -0
- package/ts/src/frontDoor/args.ts +30 -0
- package/ts/src/frontDoor/argvShape.ts +106 -0
- package/ts/src/frontDoor/buildLoadRuntime.ts +139 -0
- package/ts/src/frontDoor/buildRoute.ts +398 -0
- package/ts/src/frontDoor/cli.ts +1865 -0
- package/ts/src/frontDoor/cloneRoleGuard.ts +98 -0
- package/ts/src/frontDoor/consultRoute.ts +50 -0
- package/ts/src/frontDoor/contentTailRoute.ts +239 -0
- package/ts/src/frontDoor/conversationLoggerRoute.ts +73 -0
- package/ts/src/frontDoor/cultivationRoute.ts +210 -0
- package/ts/src/frontDoor/dbPath.ts +131 -0
- package/ts/src/frontDoor/dbSafetyToolsRoute.ts +240 -0
- package/ts/src/frontDoor/exploreRoute.ts +572 -0
- package/ts/src/frontDoor/extensionCatalogRoute.ts +252 -0
- package/ts/src/frontDoor/frontDoorLog.ts +57 -0
- package/ts/src/frontDoor/identityWrite.ts +36 -0
- package/ts/src/frontDoor/journeyWriteRoute.ts +26 -0
- package/ts/src/frontDoor/lifecycleWriteRoute.ts +110 -0
- package/ts/src/frontDoor/liveBackup.ts +100 -0
- package/ts/src/frontDoor/mirrorModeRoute.ts +163 -0
- package/ts/src/frontDoor/namedRefusal.ts +35 -0
- package/ts/src/frontDoor/nodeSupport.ts +24 -0
- package/ts/src/frontDoor/render/consolidate.ts +199 -0
- package/ts/src/frontDoor/render/conversations.ts +33 -0
- package/ts/src/frontDoor/render/cultivationShared.ts +21 -0
- package/ts/src/frontDoor/render/descriptor.ts +20 -0
- package/ts/src/frontDoor/render/detectPersona.ts +50 -0
- package/ts/src/frontDoor/render/icons.ts +13 -0
- package/ts/src/frontDoor/render/identity.ts +59 -0
- package/ts/src/frontDoor/render/identityRows.ts +17 -0
- package/ts/src/frontDoor/render/inspectPersona.ts +53 -0
- package/ts/src/frontDoor/render/journeyStatus.ts +78 -0
- package/ts/src/frontDoor/render/journeys.ts +79 -0
- package/ts/src/frontDoor/render/list.ts +41 -0
- package/ts/src/frontDoor/render/memories.ts +67 -0
- package/ts/src/frontDoor/render/recall.ts +49 -0
- package/ts/src/frontDoor/render/shadow.ts +155 -0
- package/ts/src/frontDoor/render/tasks.ts +99 -0
- package/ts/src/frontDoor/render/tasksImportSync.ts +63 -0
- package/ts/src/frontDoor/render/week.ts +113 -0
- package/ts/src/frontDoor/routing.ts +999 -0
- package/ts/src/frontDoor/runtimeRoute.ts +623 -0
- package/ts/src/frontDoor/searchRoute.ts +166 -0
- package/ts/src/frontDoor/seedPaths.ts +98 -0
- package/ts/src/frontDoor/soulRoute.ts +433 -0
- package/ts/src/frontDoor/tasksImportSyncRoute.ts +110 -0
- package/ts/src/frontDoor/tasksWriteRoute.ts +101 -0
- package/ts/src/frontDoor/usage.ts +176 -0
- package/ts/src/guards/claudePlugin.ts +252 -0
- package/ts/src/guards/packContents.ts +306 -0
- package/ts/src/guards/piSourcedSkills.ts +70 -0
- package/ts/src/guards/retiredRouteShadows.ts +57 -0
- package/ts/src/guards/retiredSurfaces.ts +564 -0
- package/ts/src/hooks/claude.ts +176 -0
- package/ts/src/hooks/codex.ts +45 -0
- package/ts/src/hooks/gemini.ts +126 -0
- package/ts/src/hooks/main.ts +109 -0
- package/ts/src/hooks/mirrorState.ts +71 -0
- package/ts/src/hooks/payload.ts +68 -0
- package/ts/src/hooks/runtime.ts +159 -0
- package/ts/src/identity/applyConsolidationIdentityUpdate.ts +67 -0
- package/ts/src/identity/descriptorGenerate.ts +162 -0
- package/ts/src/identity/identityEdit.ts +143 -0
- package/ts/src/identity/identityKey.ts +77 -0
- package/ts/src/identity/identityRead.ts +69 -0
- package/ts/src/identity/identityStore.ts +80 -0
- package/ts/src/identity/journeyListing.ts +53 -0
- package/ts/src/identity/personaInspect.ts +44 -0
- package/ts/src/identity/personaListing.ts +37 -0
- package/ts/src/identity/setIdentity.ts +64 -0
- package/ts/src/index.ts +231 -0
- package/ts/src/init/init.ts +111 -0
- package/ts/src/journey/journeyOptions.ts +131 -0
- package/ts/src/journey/journeyRemoval.ts +158 -0
- package/ts/src/journey/journeyStatus.ts +149 -0
- package/ts/src/journey/journeySyncFile.ts +87 -0
- package/ts/src/journey/journeyWrite.ts +203 -0
- package/ts/src/journey/parentJourney.ts +41 -0
- package/ts/src/journey/validateParentJourney.ts +55 -0
- package/ts/src/mcp/boundary.ts +151 -0
- package/ts/src/mcp/guards.ts +244 -0
- package/ts/src/mcp/main.ts +121 -0
- package/ts/src/mcp/payload.ts +141 -0
- package/ts/src/mcp/protocol.ts +141 -0
- package/ts/src/mcp/readModels.ts +241 -0
- package/ts/src/mcp/registry.ts +201 -0
- package/ts/src/mcp/serve.ts +120 -0
- package/ts/src/mcp/tools/deterministic.ts +137 -0
- package/ts/src/mcp/tools/providerCrossing.ts +187 -0
- package/ts/src/mcp/wire.ts +8 -0
- package/ts/src/memory/journal.ts +180 -0
- package/ts/src/memory/listing.ts +107 -0
- package/ts/src/memory/memoryWrite.ts +88 -0
- package/ts/src/memory/reinforcement.ts +35 -0
- package/ts/src/mirror/context.ts +161 -0
- package/ts/src/mirror/defaultResolution.ts +199 -0
- package/ts/src/mirror/orchestration.ts +364 -0
- package/ts/src/mirror/reception.ts +194 -0
- package/ts/src/mirror/render.ts +75 -0
- package/ts/src/mirror/runtimeSession.ts +205 -0
- package/ts/src/mode/operatingMode.ts +148 -0
- package/ts/src/observability/callOutcome.ts +115 -0
- package/ts/src/observability/inspectLedger.ts +329 -0
- package/ts/src/observability/ledgerHooks.ts +126 -0
- package/ts/src/observability/llmCalls.ts +154 -0
- package/ts/src/persona/detectPersona.ts +81 -0
- package/ts/src/planning/promptAssembly.ts +111 -0
- package/ts/src/planning/weekPending.ts +81 -0
- package/ts/src/planning/weekPlan.ts +183 -0
- package/ts/src/planning/weekSave.ts +117 -0
- package/ts/src/providers/config.ts +189 -0
- package/ts/src/providers/cost.ts +64 -0
- package/ts/src/providers/credits.ts +204 -0
- package/ts/src/providers/embedding.ts +321 -0
- package/ts/src/providers/familyProviders.ts +147 -0
- package/ts/src/providers/llm.ts +334 -0
- package/ts/src/providers/openrouter.ts +244 -0
- package/ts/src/providers/redaction.ts +110 -0
- package/ts/src/providers/replay.ts +15 -0
- package/ts/src/providers/transport.ts +410 -0
- package/ts/src/repair/encodingRepair.ts +253 -0
- package/ts/src/runtime/backup.ts +151 -0
- package/ts/src/runtime/channel.ts +146 -0
- package/ts/src/runtime/config.ts +168 -0
- package/ts/src/runtime/diagnose.ts +686 -0
- package/ts/src/runtime/git.ts +510 -0
- package/ts/src/runtime/installKind.ts +220 -0
- package/ts/src/runtime/migrate.ts +154 -0
- package/ts/src/runtime/packageIdentity.ts +102 -0
- package/ts/src/runtime/releaseNotes.ts +244 -0
- package/ts/src/runtime/status.ts +557 -0
- package/ts/src/runtime/strategies/clone.ts +200 -0
- package/ts/src/runtime/strategies/package.ts +328 -0
- package/ts/src/runtime/treeRoot.ts +30 -0
- package/ts/src/runtime/update.ts +445 -0
- package/ts/src/runtime/updateGate.ts +40 -0
- package/ts/src/runtime/updatePipeline.ts +159 -0
- package/ts/src/runtime/version.ts +36 -0
- package/ts/src/runtime/wiring.ts +130 -0
- package/ts/src/search/memorySearch.ts +338 -0
- package/ts/src/search/ranker.ts +154 -0
- package/ts/src/seed/seed.ts +256 -0
- package/ts/src/seed/seedYaml.ts +133 -0
- package/ts/src/soul/apply.ts +218 -0
- package/ts/src/soul/harvest.ts +267 -0
- package/ts/src/soul/prompts/soul_beauty_voice.md +103 -0
- package/ts/src/soul/prompts/soul_self_voice.md +219 -0
- package/ts/src/soul/prompts/soul_wisdom_voice.md +121 -0
- package/ts/src/soul/prompts.ts +85 -0
- package/ts/src/soul/render.ts +358 -0
- package/ts/src/soul/state.ts +212 -0
- package/ts/src/soul/transition.ts +37 -0
- package/ts/src/tasks/journeyPathParse.ts +113 -0
- package/ts/src/tasks/taskImportSync.ts +142 -0
- package/ts/src/tasks/taskStore.ts +312 -0
- package/ts/src/tasks/weekView.ts +120 -0
- package/ts/src/util/clipCodePoints.ts +26 -0
- package/ts/src/util/paths.ts +63 -0
- package/ts/src/util/program.ts +9 -0
- package/ts/src/util/pyGenerators.ts +144 -0
- package/ts/src/util/pythonText.ts +317 -0
- package/ts/src/util/semver.ts +45 -0
- package/ts/src/util/shellWord.ts +25 -0
- package/ts/src/util/slug.ts +64 -0
- package/ts/src/welcome/card.ts +339 -0
- package/ts/src/welcome/statusLine.ts +101 -0
- package/ts/src/welcome/updateCache.ts +129 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// Port of `propose_consolidation` (`intelligence/consolidate.py`) and
|
|
2
|
+
// `propose_shadow_observations` (`intelligence/shadow.py`) -- the LLM
|
|
3
|
+
// orchestration half of `consolidate scan` / `shadow scan` (CV22.DS7.US3
|
|
4
|
+
// Slice B; live since CV22.DS8.TS2).
|
|
5
|
+
//
|
|
6
|
+
// The prompt sent to the provider is Python's real template
|
|
7
|
+
// (`CONSOLIDATION_PROMPT` / `SHADOW_SCAN_PROMPT`: task statement, identity or
|
|
8
|
+
// dedup context, the "## Untrusted input" guard, and the JSON output
|
|
9
|
+
// contract), assembled through `pyFormat` around the same fenced block Python
|
|
10
|
+
// fences. The assembled bytes are graded against a golden captured from the
|
|
11
|
+
// real Python builders and digest-pinned, because this file's history is the
|
|
12
|
+
// cautionary tale: from DS7.US3 to DS8.TS2 it sent a fenced memory dump with
|
|
13
|
+
// no instructions, and replay -- which resolves by role and never reads the
|
|
14
|
+
// prompt -- could not tell. Live, the model would have answered prose,
|
|
15
|
+
// `parseJsonResponse` would have failed, and every scan would have reported
|
|
16
|
+
// "no proposals" while the ledger showed paid calls.
|
|
17
|
+
|
|
18
|
+
import { fenceUntrusted } from "#extraction/fencing.ts";
|
|
19
|
+
import { parseJsonResponse } from "#extraction/json.ts";
|
|
20
|
+
import { CONSOLIDATION_PROMPT, SHADOW_SCAN_PROMPT } from "#extraction/prompts.ts";
|
|
21
|
+
import { classifyProviderError, type OnProviderCallOutcome } from "#observability/callOutcome.ts";
|
|
22
|
+
import type { ChatLedgerHook } from "#observability/ledgerHooks.ts";
|
|
23
|
+
import { resolveExtractionModel } from "#providers/config.ts";
|
|
24
|
+
import type { LlmProvider } from "#providers/llm.ts";
|
|
25
|
+
import { pyFormat } from "#util/pythonText.ts";
|
|
26
|
+
import type { ConsolidationRow, CultivationMemory } from "./consolidationStore.ts";
|
|
27
|
+
|
|
28
|
+
/** Mirrors Python's action allowlist check in `propose_consolidation`. */
|
|
29
|
+
const CONSOLIDATION_ACTIONS: ReadonlySet<string> = new Set([
|
|
30
|
+
"merge",
|
|
31
|
+
"identity_update",
|
|
32
|
+
"shadow_candidate",
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
/** Port of `_format_cluster`: one Markdown section per memory, in input order. */
|
|
36
|
+
export function formatCluster(cluster: readonly CultivationMemory[]): string {
|
|
37
|
+
const lines: string[] = [];
|
|
38
|
+
cluster.forEach((memory, index) => {
|
|
39
|
+
lines.push(`### Memory ${index + 1}`);
|
|
40
|
+
lines.push(`**Type:** ${memory.memory_type} | **Layer:** ${memory.layer}`);
|
|
41
|
+
if (memory.journey) lines.push(`**Journey:** ${memory.journey}`);
|
|
42
|
+
lines.push(`**Created:** ${memory.created_at.slice(0, 10)}`);
|
|
43
|
+
lines.push(`**Title:** ${memory.title}`);
|
|
44
|
+
lines.push(`**Content:** ${memory.content}`);
|
|
45
|
+
if (memory.context) lines.push(`**Context:** ${memory.context}`);
|
|
46
|
+
lines.push("");
|
|
47
|
+
});
|
|
48
|
+
return lines.join("\n");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Port of `_format_shadow_memories`. */
|
|
52
|
+
export function formatShadowMemories(memories: readonly CultivationMemory[]): string {
|
|
53
|
+
if (memories.length === 0) return "(no shadow-candidate memories found)";
|
|
54
|
+
const lines: string[] = [];
|
|
55
|
+
for (const memory of memories) {
|
|
56
|
+
lines.push(`### [${memory.id.slice(0, 8)}] ${memory.title}`);
|
|
57
|
+
lines.push(
|
|
58
|
+
`**Type:** ${memory.memory_type} | **Layer:** ${memory.layer} | ` +
|
|
59
|
+
`**State:** ${memory.readiness_state} | **Date:** ${memory.created_at.slice(0, 10)}`,
|
|
60
|
+
);
|
|
61
|
+
lines.push(`**Content:** ${memory.content}`);
|
|
62
|
+
if (memory.context) lines.push(`**Context:** ${memory.context}`);
|
|
63
|
+
lines.push("");
|
|
64
|
+
}
|
|
65
|
+
return lines.join("\n");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** A structural shadow identity entry, the minimal projection `_format_shadow_structure` needs. */
|
|
69
|
+
export interface ShadowStructureEntry {
|
|
70
|
+
key: string;
|
|
71
|
+
content: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Port of `_format_shadow_structure`. */
|
|
75
|
+
export function formatShadowStructure(entries: readonly ShadowStructureEntry[]): string {
|
|
76
|
+
if (entries.length === 0) return "(no structural shadow content yet)";
|
|
77
|
+
return entries.map((entry) => `### ${entry.key}\n${entry.content}`).join("\n\n");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Exactly what `propose_consolidation` sends: `CONSOLIDATION_PROMPT.format(...)`
|
|
82
|
+
* with only the user-derived cluster fenced -- `identity_context` is
|
|
83
|
+
* system-side. One pass, so a `{placeholder}` literal inside memory content
|
|
84
|
+
* is never expanded a second time.
|
|
85
|
+
*/
|
|
86
|
+
export function buildConsolidationPrompt(
|
|
87
|
+
cluster: readonly CultivationMemory[],
|
|
88
|
+
userName: string,
|
|
89
|
+
identityContext: string,
|
|
90
|
+
): string {
|
|
91
|
+
return pyFormat(CONSOLIDATION_PROMPT, {
|
|
92
|
+
user_name: userName,
|
|
93
|
+
identity_context: identityContext,
|
|
94
|
+
cluster_text: fenceUntrusted("cluster", formatCluster(cluster)),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Exactly what `propose_shadow_observations` sends: `SHADOW_SCAN_PROMPT.format(...)`
|
|
100
|
+
* with only `shadow_memories` fenced -- `shadow_structure` is structural
|
|
101
|
+
* identity content, not user-injected, and Python leaves it unfenced.
|
|
102
|
+
*/
|
|
103
|
+
export function buildShadowScanPrompt(
|
|
104
|
+
memories: readonly CultivationMemory[],
|
|
105
|
+
shadowEntries: readonly ShadowStructureEntry[],
|
|
106
|
+
userName: string,
|
|
107
|
+
): string {
|
|
108
|
+
return pyFormat(SHADOW_SCAN_PROMPT, {
|
|
109
|
+
user_name: userName,
|
|
110
|
+
shadow_structure: formatShadowStructure(shadowEntries),
|
|
111
|
+
shadow_memories: fenceUntrusted("shadow_memories", formatShadowMemories(memories)),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface ProposeConsolidationOptions {
|
|
116
|
+
id: string;
|
|
117
|
+
nowIso: string;
|
|
118
|
+
/** `cmd_scan`'s `_user_name(mem)`; see `promptContext.ts`. Required: the fallback lives in the resolver, as in Python. */
|
|
119
|
+
userName: string;
|
|
120
|
+
/** `cmd_scan`'s `_identity_context(mem)`; see `promptContext.ts`. */
|
|
121
|
+
identityContext: string;
|
|
122
|
+
/**
|
|
123
|
+
* Python passes `on_llm_call=build_llm_logger(store, role="consolidation")`
|
|
124
|
+
* from `consolidate_cmd`. TypeScript logged nothing, which was accurate
|
|
125
|
+
* while every call was replayed and free, and stops being accurate the
|
|
126
|
+
* moment this reaches a live provider (CV22.DS8.US3).
|
|
127
|
+
*/
|
|
128
|
+
onLlmCall?: ChatLedgerHook;
|
|
129
|
+
/**
|
|
130
|
+
* What happened on this call, category only. Every `null` below is Python's
|
|
131
|
+
* swallow, and without this they are indistinguishable from each other and
|
|
132
|
+
* from an honest "no proposal" (CV22.DS8.US3).
|
|
133
|
+
*/
|
|
134
|
+
onOutcome?: OnProviderCallOutcome;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Port of `propose_consolidation`: fence the cluster, call the LLM
|
|
139
|
+
* (role `'consolidation'`), and parse the response into a pending
|
|
140
|
+
* `ConsolidationRow`, or `null` on any failure (provider rejection, unparsable
|
|
141
|
+
* JSON, an action outside the allowlist, or empty proposed content) --
|
|
142
|
+
* mirroring Python's `except Exception: return None` / `return None` branches
|
|
143
|
+
* exactly. Deliberately does NOT gate `target_layer`/`target_key` here --
|
|
144
|
+
* this only stores the proposal as `pending`; the identity-write allowlist
|
|
145
|
+
* gate runs later, at `apply` time (`applyIdentityUpdate`), which is the
|
|
146
|
+
* adversarial-containment property this story proves.
|
|
147
|
+
*/
|
|
148
|
+
export async function proposeConsolidation(
|
|
149
|
+
provider: LlmProvider,
|
|
150
|
+
cluster: readonly CultivationMemory[],
|
|
151
|
+
options: ProposeConsolidationOptions,
|
|
152
|
+
): Promise<ConsolidationRow | null> {
|
|
153
|
+
const prompt = buildConsolidationPrompt(cluster, options.userName, options.identityContext);
|
|
154
|
+
let content: string;
|
|
155
|
+
try {
|
|
156
|
+
const response = await provider.complete({
|
|
157
|
+
role: "consolidation",
|
|
158
|
+
prompt,
|
|
159
|
+
model: resolveExtractionModel(),
|
|
160
|
+
temperature: 0.1,
|
|
161
|
+
});
|
|
162
|
+
// Python logs AFTER a successful call and BEFORE parsing, so a response
|
|
163
|
+
// the parser rejects still leaves a row: the call was made and paid for.
|
|
164
|
+
options.onLlmCall?.(response, prompt);
|
|
165
|
+
content = response.content;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
options.onOutcome?.({ outcome: "transport_failed", kind: classifyProviderError(error) });
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const data = parseJsonResponse(content);
|
|
172
|
+
// A response the parser cannot use is a PROMPT-layer signal, not a transport
|
|
173
|
+
// one: the call succeeded and was paid for.
|
|
174
|
+
if (!isRecord(data)) {
|
|
175
|
+
options.onOutcome?.({ outcome: "parse_failed" });
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// The model answered and was understood; we rejected what it proposed. That
|
|
180
|
+
// is "nothing to do", not a failure.
|
|
181
|
+
const action = typeof data.action === "string" ? data.action.toLowerCase() : "";
|
|
182
|
+
if (!CONSOLIDATION_ACTIONS.has(action)) {
|
|
183
|
+
options.onOutcome?.({ outcome: "empty" });
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const proposedContent =
|
|
188
|
+
typeof data.proposed_content === "string" ? data.proposed_content.trim() : "";
|
|
189
|
+
if (!proposedContent) {
|
|
190
|
+
options.onOutcome?.({ outcome: "empty" });
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
options.onOutcome?.({ outcome: "answered" });
|
|
195
|
+
|
|
196
|
+
const targetLayer =
|
|
197
|
+
typeof data.target_layer === "string" && data.target_layer ? data.target_layer : null;
|
|
198
|
+
const targetKey = typeof data.target_key === "string" && data.target_key ? data.target_key : null;
|
|
199
|
+
const rationaleRaw = typeof data.rationale === "string" ? data.rationale.trim() : "";
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
id: options.id,
|
|
203
|
+
action,
|
|
204
|
+
proposal: proposedContent,
|
|
205
|
+
result: null,
|
|
206
|
+
source_memory_ids: JSON.stringify(cluster.map((memory) => memory.id)),
|
|
207
|
+
target_layer: targetLayer,
|
|
208
|
+
target_key: targetKey,
|
|
209
|
+
rationale: rationaleRaw || null,
|
|
210
|
+
status: "pending",
|
|
211
|
+
created_at: options.nowIso,
|
|
212
|
+
reviewed_at: null,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export interface ProposeShadowObservationsOptions {
|
|
217
|
+
/** Called once per emitted observation, matching Python's per-item `_uuid()`/`_now()`. */
|
|
218
|
+
id: () => string;
|
|
219
|
+
nowIso: () => string;
|
|
220
|
+
/** `cmd_scan`'s `_user_name(mem)`; see `promptContext.ts`. */
|
|
221
|
+
userName: string;
|
|
222
|
+
/** Python's `build_llm_logger(store, role="shadow_scan")`; see above. */
|
|
223
|
+
onLlmCall?: ChatLedgerHook;
|
|
224
|
+
/** What happened on the one call, category only; see `ProposeConsolidationOptions`. */
|
|
225
|
+
onOutcome?: OnProviderCallOutcome;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Port of `propose_shadow_observations`: a single LLM call over the FULL
|
|
230
|
+
* candidate pool (not per-cluster) so cross-cluster patterns can surface,
|
|
231
|
+
* fenced under `'shadow_memories'`. Returns `[]` on any failure (no
|
|
232
|
+
* memories, provider rejection, unparsable JSON, or a non-array response) --
|
|
233
|
+
* mirroring Python's early-return branches exactly. Each valid item becomes
|
|
234
|
+
* its own pending `Consolidation` with `action: 'shadow_observation'`,
|
|
235
|
+
* `target_layer: 'shadow'`, `target_key: 'profile'` -- ALWAYS this pair,
|
|
236
|
+
* never read from the LLM response (Python's `Consolidation(...,
|
|
237
|
+
* target_layer="shadow", target_key="profile", ...)` is a hardcoded literal,
|
|
238
|
+
* not derived from `item`).
|
|
239
|
+
*/
|
|
240
|
+
export async function proposeShadowObservations(
|
|
241
|
+
provider: LlmProvider,
|
|
242
|
+
memories: readonly CultivationMemory[],
|
|
243
|
+
shadowEntries: readonly ShadowStructureEntry[],
|
|
244
|
+
options: ProposeShadowObservationsOptions,
|
|
245
|
+
): Promise<ConsolidationRow[]> {
|
|
246
|
+
if (memories.length === 0) return [];
|
|
247
|
+
|
|
248
|
+
const prompt = buildShadowScanPrompt(memories, shadowEntries, options.userName);
|
|
249
|
+
|
|
250
|
+
let content: string;
|
|
251
|
+
try {
|
|
252
|
+
const response = await provider.complete({
|
|
253
|
+
role: "shadow_scan",
|
|
254
|
+
prompt,
|
|
255
|
+
model: resolveExtractionModel(),
|
|
256
|
+
temperature: 0.1,
|
|
257
|
+
});
|
|
258
|
+
options.onLlmCall?.(response, prompt);
|
|
259
|
+
content = response.content;
|
|
260
|
+
} catch (error) {
|
|
261
|
+
options.onOutcome?.({ outcome: "transport_failed", kind: classifyProviderError(error) });
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const data = parseJsonResponse(content);
|
|
266
|
+
if (!Array.isArray(data)) {
|
|
267
|
+
options.onOutcome?.({ outcome: "parse_failed" });
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const results: ConsolidationRow[] = [];
|
|
272
|
+
for (const item of data) {
|
|
273
|
+
if (!isRecord(item)) continue;
|
|
274
|
+
const observation = typeof item.observation === "string" ? item.observation.trim() : "";
|
|
275
|
+
if (!observation) continue;
|
|
276
|
+
const title = typeof item.title === "string" ? item.title.trim() : "Shadow observation";
|
|
277
|
+
const memoryIds = Array.isArray(item.memory_ids)
|
|
278
|
+
? item.memory_ids.map((value) => String(value))
|
|
279
|
+
: [];
|
|
280
|
+
const evidenceNote = typeof item.evidence_note === "string" ? item.evidence_note.trim() : "";
|
|
281
|
+
|
|
282
|
+
const parts = [`**${title}**\n\n${observation}`];
|
|
283
|
+
if (evidenceNote) parts.push(`*Evidence: ${evidenceNote}*`);
|
|
284
|
+
|
|
285
|
+
results.push({
|
|
286
|
+
id: options.id(),
|
|
287
|
+
action: "shadow_observation",
|
|
288
|
+
proposal: parts.join("\n\n"),
|
|
289
|
+
result: null,
|
|
290
|
+
source_memory_ids: JSON.stringify(memoryIds),
|
|
291
|
+
target_layer: "shadow",
|
|
292
|
+
target_key: "profile",
|
|
293
|
+
rationale: title,
|
|
294
|
+
status: "pending",
|
|
295
|
+
created_at: options.nowIso(),
|
|
296
|
+
reviewed_at: null,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
options.onOutcome?.({ outcome: results.length > 0 ? "answered" : "empty" });
|
|
300
|
+
return results;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
304
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
305
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// `consolidate scan` / `shadow scan` orchestration (CV22.DS7.US3 Slice B):
|
|
2
|
+
// cluster (deterministic) -> propose (LLM call, replay-gated) -> store.
|
|
3
|
+
// Ports `cli/consolidate_cmd.py:cmd_scan` and `cli/shadow_cmd.py:cmd_scan`'s
|
|
4
|
+
// non-printing logic -- rendering is the front door's concern.
|
|
5
|
+
|
|
6
|
+
import type { WritableDatabase } from "#db/database.ts";
|
|
7
|
+
import { listIdentityByLayer } from "#identity/identityRead.ts";
|
|
8
|
+
import type { OnProviderCallOutcome } from "#observability/callOutcome.ts";
|
|
9
|
+
import type { ChatLedgerHook } from "#observability/ledgerHooks.ts";
|
|
10
|
+
import type { LlmProvider } from "#providers/llm.ts";
|
|
11
|
+
import { clusterMemories, DEFAULT_CLUSTER_THRESHOLD } from "./cluster.ts";
|
|
12
|
+
import {
|
|
13
|
+
type ConsolidationRow,
|
|
14
|
+
type CultivationMemoryWithEmbedding,
|
|
15
|
+
createConsolidation,
|
|
16
|
+
getMemoriesWithEmbeddingsForCultivation,
|
|
17
|
+
getShadowCandidateMemories,
|
|
18
|
+
} from "./consolidationStore.ts";
|
|
19
|
+
import { consolidationIdentityContext, cultivationUserName } from "./promptContext.ts";
|
|
20
|
+
import { proposeConsolidation, proposeShadowObservations } from "./propose.ts";
|
|
21
|
+
|
|
22
|
+
/** Mirrors `consolidate scan`'s `--limit` default (max proposals per run). */
|
|
23
|
+
export const DEFAULT_CONSOLIDATE_SCAN_LIMIT = 5;
|
|
24
|
+
|
|
25
|
+
/** Mirrors `shadow scan`'s `--limit` default (max shadow-candidate memories considered). */
|
|
26
|
+
export const DEFAULT_SHADOW_SCAN_LIMIT = 50;
|
|
27
|
+
|
|
28
|
+
export interface ConsolidateScanOptions {
|
|
29
|
+
journey?: string | null;
|
|
30
|
+
layer?: string | null;
|
|
31
|
+
/** Max proposals to generate (caps the cluster list before proposing, matching Python). */
|
|
32
|
+
limit?: number;
|
|
33
|
+
threshold?: number;
|
|
34
|
+
provider: LlmProvider;
|
|
35
|
+
/** Called once per cluster considered, matching Python's per-Consolidation `_uuid()`/`_now()`. */
|
|
36
|
+
id: () => string;
|
|
37
|
+
nowIso: () => string;
|
|
38
|
+
/** Ledger hook for the one `consolidation` call per cluster. */
|
|
39
|
+
onLlmCall?: ChatLedgerHook;
|
|
40
|
+
/** Per-cluster outcome, category only; see `observability/callOutcome.ts`. */
|
|
41
|
+
onOutcome?: OnProviderCallOutcome;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** One attempted cluster's outcome, in scan order -- `proposal: null` is
|
|
45
|
+
* Python's "LLM returned no valid proposal for cluster of N memories" branch. */
|
|
46
|
+
export interface ConsolidateScanClusterOutcome {
|
|
47
|
+
cluster: CultivationMemoryWithEmbedding[];
|
|
48
|
+
proposal: ConsolidationRow | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ConsolidateScanResult {
|
|
52
|
+
memoriesScanned: number;
|
|
53
|
+
/** Capped at `limit` (Python slices `clusters[:limit]` BEFORE proposing), in order. */
|
|
54
|
+
results: ConsolidateScanClusterOutcome[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Convenience projection: only the clusters that actually produced a stored proposal. */
|
|
58
|
+
export function createdProposals(result: ConsolidateScanResult): ConsolidationRow[] {
|
|
59
|
+
return result.results.flatMap((r) => (r.proposal ? [r.proposal] : []));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Port of `consolidate_cmd.cmd_scan`'s non-printing logic: read
|
|
64
|
+
* embedded memories (filtered), cluster them, cap at `limit`, propose one
|
|
65
|
+
* Consolidation per cluster, and persist every non-null proposal. A cluster
|
|
66
|
+
* whose proposal comes back `null` (LLM failure, unparsable JSON, disallowed
|
|
67
|
+
* action, or empty content) is reported as `proposal: null` -- exactly like
|
|
68
|
+
* Python's "LLM returned no valid proposal" branch, which does not store
|
|
69
|
+
* anything for that cluster and moves on, but still renders a line for it.
|
|
70
|
+
*/
|
|
71
|
+
export async function consolidateScan(
|
|
72
|
+
db: WritableDatabase,
|
|
73
|
+
options: ConsolidateScanOptions,
|
|
74
|
+
): Promise<ConsolidateScanResult> {
|
|
75
|
+
const memories = getMemoriesWithEmbeddingsForCultivation(db, {
|
|
76
|
+
journey: options.journey,
|
|
77
|
+
layer: options.layer,
|
|
78
|
+
});
|
|
79
|
+
if (memories.length === 0) {
|
|
80
|
+
return { memoriesScanned: 0, results: [] };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const allClusters = clusterMemories(memories, options.threshold ?? DEFAULT_CLUSTER_THRESHOLD);
|
|
84
|
+
const clusters = allClusters.slice(0, options.limit ?? DEFAULT_CONSOLIDATE_SCAN_LIMIT);
|
|
85
|
+
|
|
86
|
+
// Resolved once per scan, after the cluster cap and before the loop --
|
|
87
|
+
// where `cmd_scan` resolves them (CV22.DS8.TS2).
|
|
88
|
+
const userName = cultivationUserName(db);
|
|
89
|
+
const identityContext = consolidationIdentityContext(db);
|
|
90
|
+
|
|
91
|
+
const results: ConsolidateScanClusterOutcome[] = [];
|
|
92
|
+
for (const cluster of clusters) {
|
|
93
|
+
const proposal = await proposeConsolidation(options.provider, cluster, {
|
|
94
|
+
id: options.id(),
|
|
95
|
+
nowIso: options.nowIso(),
|
|
96
|
+
userName,
|
|
97
|
+
identityContext,
|
|
98
|
+
onLlmCall: options.onLlmCall,
|
|
99
|
+
onOutcome: options.onOutcome,
|
|
100
|
+
});
|
|
101
|
+
if (proposal === null) {
|
|
102
|
+
results.push({ cluster, proposal: null });
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
results.push({ cluster, proposal: createConsolidation(db, proposal) });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { memoriesScanned: memories.length, results };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface ShadowScanOptions {
|
|
112
|
+
limit?: number;
|
|
113
|
+
provider: LlmProvider;
|
|
114
|
+
/** Ledger hook for the one `shadow_scan` call; Python always passes one. */
|
|
115
|
+
onLlmCall?: ChatLedgerHook;
|
|
116
|
+
/** Outcome of the one call, category only. */
|
|
117
|
+
onOutcome?: OnProviderCallOutcome;
|
|
118
|
+
/** Called once per emitted observation, matching Python's per-item `_uuid()`/`_now()`. */
|
|
119
|
+
id: () => string;
|
|
120
|
+
nowIso: () => string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface ShadowScanResult {
|
|
124
|
+
candidatesConsidered: number;
|
|
125
|
+
proposalsCreated: ConsolidationRow[];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Port of `shadow_cmd.cmd_scan`'s non-printing logic: read shadow-candidate
|
|
130
|
+
* memories and the current structural shadow layer, propose observations over
|
|
131
|
+
* the FULL pool in one call, and persist every emitted proposal.
|
|
132
|
+
*/
|
|
133
|
+
export async function shadowScan(
|
|
134
|
+
db: WritableDatabase,
|
|
135
|
+
options: ShadowScanOptions,
|
|
136
|
+
): Promise<ShadowScanResult> {
|
|
137
|
+
const memories = getShadowCandidateMemories(db, {
|
|
138
|
+
limit: options.limit ?? DEFAULT_SHADOW_SCAN_LIMIT,
|
|
139
|
+
});
|
|
140
|
+
const shadowEntries = listIdentityByLayer(db, "shadow").map((row) => ({
|
|
141
|
+
key: row.key,
|
|
142
|
+
content: row.content,
|
|
143
|
+
}));
|
|
144
|
+
|
|
145
|
+
const proposals = await proposeShadowObservations(options.provider, memories, shadowEntries, {
|
|
146
|
+
id: options.id,
|
|
147
|
+
nowIso: options.nowIso,
|
|
148
|
+
userName: cultivationUserName(db),
|
|
149
|
+
onLlmCall: options.onLlmCall,
|
|
150
|
+
onOutcome: options.onOutcome,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const proposalsCreated = proposals.map((proposal) => createConsolidation(db, proposal));
|
|
154
|
+
return { candidatesConsidered: memories.length, proposalsCreated };
|
|
155
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Backup gate for destructive write-parity runs.
|
|
2
|
+
//
|
|
3
|
+
// The copy-only guard (`copyGuard.ts`) keeps writes off the live database; this
|
|
4
|
+
// gate adds the second half of the discipline: a hash-verified backup of the
|
|
5
|
+
// source must exist before any destructive apply. The harness refuses to run if
|
|
6
|
+
// the recorded backup is missing or its bytes have changed, so a parity run is
|
|
7
|
+
// always recoverable.
|
|
8
|
+
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { lstatSync, readFileSync } from "node:fs";
|
|
11
|
+
|
|
12
|
+
/** Raised when the required pre-write backup is missing or altered. */
|
|
13
|
+
export class BackupGateError extends Error {}
|
|
14
|
+
|
|
15
|
+
/** A recorded backup: a file path and the sha256 of its bytes when recorded. */
|
|
16
|
+
export interface BackupRecord {
|
|
17
|
+
path: string;
|
|
18
|
+
sha256: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Hash a file's bytes as lowercase-hex sha256. */
|
|
22
|
+
export function sha256File(path: string): string {
|
|
23
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Require a present, hash-verified backup before any destructive apply. The
|
|
28
|
+
* recorded path must be a regular file — a symlinked backup could be
|
|
29
|
+
* redirected after verification, so indirection is refused (CR030).
|
|
30
|
+
*/
|
|
31
|
+
export function requireBackup(backup: BackupRecord | undefined): void {
|
|
32
|
+
if (!backup?.path) {
|
|
33
|
+
throw new BackupGateError("no backup recorded before a destructive write");
|
|
34
|
+
}
|
|
35
|
+
let isRegularFile: boolean;
|
|
36
|
+
try {
|
|
37
|
+
isRegularFile = lstatSync(backup.path).isFile();
|
|
38
|
+
} catch {
|
|
39
|
+
throw new BackupGateError(`recorded backup is missing: ${backup.path}`);
|
|
40
|
+
}
|
|
41
|
+
if (!isRegularFile) {
|
|
42
|
+
throw new BackupGateError(`recorded backup is not a regular file: ${backup.path}`);
|
|
43
|
+
}
|
|
44
|
+
if (sha256File(backup.path) !== backup.sha256) {
|
|
45
|
+
throw new BackupGateError("recorded backup hash does not match the backup file");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Bootstrap authority — CV22.DS6.TS3.
|
|
2
|
+
//
|
|
3
|
+
// TS equivalent of Python's `src/memory/db/connection.py::get_connection`.
|
|
4
|
+
// Composes TS1's `createSchema` and TS2's `runMigrations` behind the
|
|
5
|
+
// cross-process bootstrap lock (`bootstrapLock.ts`) and Python's exact pragma
|
|
6
|
+
// ordering, so a TS-only process can create and migrate `memory.db` safely —
|
|
7
|
+
// including under concurrent bootstrap attempts from multiple processes.
|
|
8
|
+
//
|
|
9
|
+
// Pragma ordering matches Python precisely and is load-bearing:
|
|
10
|
+
// connect -> busy_timeout -> foreign_keys -> WAL -> run_migrations -> SCHEMA
|
|
11
|
+
// `journal_mode=WAL` must be set outside any transaction and before
|
|
12
|
+
// migrations run (each migration commits via `withTransaction`; switching
|
|
13
|
+
// journal mode inside a transaction is refused by SQLite).
|
|
14
|
+
|
|
15
|
+
import { chmodSync, existsSync, mkdirSync } from "node:fs";
|
|
16
|
+
import { dirname } from "node:path";
|
|
17
|
+
|
|
18
|
+
import { acquireBootstrapLock, type BootstrapLockOptions } from "./bootstrapLock.ts";
|
|
19
|
+
import { type OpenOptions, openDatabaseForBootstrap, type WritableDatabase } from "./database.ts";
|
|
20
|
+
import { runMigrations } from "./migrations.ts";
|
|
21
|
+
import { createSchema } from "./schema.ts";
|
|
22
|
+
|
|
23
|
+
export interface BootstrapOptions extends OpenOptions, BootstrapLockOptions {}
|
|
24
|
+
|
|
25
|
+
function ensureWalMode(db: WritableDatabase): void {
|
|
26
|
+
const row = db.prepare("PRAGMA journal_mode").get() as { journal_mode?: string } | undefined;
|
|
27
|
+
const current = (row?.journal_mode ?? "").toLowerCase();
|
|
28
|
+
if (current !== "wal") {
|
|
29
|
+
db.exec("PRAGMA journal_mode=WAL");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Owner-only posture (0600) for the database file and its WAL sidecars. A
|
|
34
|
+
* missing sidecar (e.g. no writes have happened yet) is not an error. */
|
|
35
|
+
function chmodOwnerOnly(dbPath: string): void {
|
|
36
|
+
for (const path of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
|
|
37
|
+
try {
|
|
38
|
+
chmodSync(path, 0o600);
|
|
39
|
+
} catch {
|
|
40
|
+
// Sidecar absent, or filesystem does not support POSIX modes — Python's
|
|
41
|
+
// own posture is best-effort here too (`contextlib.suppress(OSError)`).
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Bootstrap `dbPath`: create the parent directory (owner-only if we create
|
|
48
|
+
* it — an existing directory is never mutated, matching Python), acquire the
|
|
49
|
+
* cross-process lock, apply pragma discipline, run migrations, and create any
|
|
50
|
+
* missing schema. Idempotent and safe to call repeatedly — every step it
|
|
51
|
+
* composes (pragmas, `runMigrations`, `createSchema`) is a no-op against an
|
|
52
|
+
* already-current database.
|
|
53
|
+
*
|
|
54
|
+
* Returns the open, bootstrapped connection — callers should use it directly
|
|
55
|
+
* rather than reopening, exactly as Python's `get_connection` returns the
|
|
56
|
+
* connection it just bootstrapped.
|
|
57
|
+
*/
|
|
58
|
+
export function bootstrapDatabase(
|
|
59
|
+
dbPath: string,
|
|
60
|
+
options: BootstrapOptions = {},
|
|
61
|
+
): WritableDatabase {
|
|
62
|
+
const parentDir = dirname(dbPath);
|
|
63
|
+
const parentPreexisting = existsSync(parentDir);
|
|
64
|
+
mkdirSync(parentDir, { recursive: true });
|
|
65
|
+
if (!parentPreexisting) {
|
|
66
|
+
try {
|
|
67
|
+
chmodSync(parentDir, 0o700);
|
|
68
|
+
} catch {
|
|
69
|
+
// Best-effort, matching Python's `contextlib.suppress(OSError)`.
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const lock = acquireBootstrapLock(dbPath, options);
|
|
74
|
+
let db: WritableDatabase;
|
|
75
|
+
try {
|
|
76
|
+
db = openDatabaseForBootstrap(dbPath, options);
|
|
77
|
+
// busy_timeout + foreign_keys are already applied by openDatabaseForBootstrap
|
|
78
|
+
// (shared `applyConnectionPragmas`); WAL is bootstrap-specific because it
|
|
79
|
+
// must run outside a transaction and only needs setting once per file.
|
|
80
|
+
ensureWalMode(db);
|
|
81
|
+
runMigrations(db);
|
|
82
|
+
createSchema(db);
|
|
83
|
+
} finally {
|
|
84
|
+
lock.release();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
chmodOwnerOnly(dbPath);
|
|
88
|
+
return db;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Ensure a bootstrapped database exists at `dbPath` (CV22.DS6.TS4). When the
|
|
93
|
+
* file is absent, create and migrate it through {@link bootstrapDatabase} under
|
|
94
|
+
* the cross-process lock, then close the connection so the caller can reopen in
|
|
95
|
+
* the exact mode it needs — read-only serving, or the backup-gated live-write
|
|
96
|
+
* seam. When the file already exists this is a cheap `existsSync` no-op, so the
|
|
97
|
+
* per-command hot path never pays for a lock acquire.
|
|
98
|
+
*
|
|
99
|
+
* This is the front door's first-run seam: it replaces the DS2/DS3 stopgap that
|
|
100
|
+
* delegated a missing database to Python to bootstrap. Concurrency-safe by
|
|
101
|
+
* construction — the underlying bootstrap is lock-guarded and idempotent, so
|
|
102
|
+
* even if the file appears between the check here and the call, the work is a
|
|
103
|
+
* safe no-op rather than a double-apply.
|
|
104
|
+
*/
|
|
105
|
+
export function bootstrapDatabaseIfMissing(dbPath: string, options: BootstrapOptions = {}): void {
|
|
106
|
+
if (existsSync(dbPath)) return;
|
|
107
|
+
bootstrapDatabase(dbPath, options).close();
|
|
108
|
+
}
|