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,256 @@
|
|
|
1
|
+
// `seed` orchestration — the port of memory.cli.seed.seed(). Scans the
|
|
2
|
+
// identity-template YAML tree and upserts core identity, personas, and
|
|
3
|
+
// journeys, in that order, reusing the already-ported `setIdentity` write
|
|
4
|
+
// primitive. Operates on an already-open `WritableDatabase` and an
|
|
5
|
+
// already-resolved `identityRoot`; CLI-level resolution (mirror home, env,
|
|
6
|
+
// the header print lines) lives in ../frontDoor/cli.ts, matching how every
|
|
7
|
+
// other write in this port keeps CLI argument/env handling out of the pure
|
|
8
|
+
// orchestration.
|
|
9
|
+
|
|
10
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import type { WritableDatabase } from "#db/database.ts";
|
|
13
|
+
import { identityRowExists } from "#identity/identityRead.ts";
|
|
14
|
+
import { setIdentity } from "#identity/setIdentity.ts";
|
|
15
|
+
import { PROGRAM } from "#util/program.ts";
|
|
16
|
+
import { newId, nowIso } from "#util/pyGenerators.ts";
|
|
17
|
+
import { shellWord } from "#util/shellWord.ts";
|
|
18
|
+
import {
|
|
19
|
+
loadJourneyContent,
|
|
20
|
+
loadPersonaContent,
|
|
21
|
+
loadYamlContent,
|
|
22
|
+
YamlFileNotFoundError,
|
|
23
|
+
} from "./seedYaml.ts";
|
|
24
|
+
|
|
25
|
+
interface CoreIdentityMapping {
|
|
26
|
+
layer: string;
|
|
27
|
+
key: string;
|
|
28
|
+
yamlPath: string;
|
|
29
|
+
field: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Port of `IDENTITY_MAP`. Order matters: it is the scan/print order. */
|
|
33
|
+
const IDENTITY_MAP: readonly CoreIdentityMapping[] = [
|
|
34
|
+
{ layer: "self", key: "soul", yamlPath: "self/soul.yaml", field: "soul" },
|
|
35
|
+
{ layer: "ego", key: "identity", yamlPath: "ego/identity.yaml", field: "identity" },
|
|
36
|
+
{ layer: "ego", key: "behavior", yamlPath: "ego/behavior.yaml", field: "behavior" },
|
|
37
|
+
{ layer: "ego", key: "constraints", yamlPath: "ego/constraints.yaml", field: "constraints" },
|
|
38
|
+
{ layer: "ego", key: "expression", yamlPath: "ego/expression.yaml", field: "expression" },
|
|
39
|
+
{ layer: "user", key: "identity", yamlPath: "user/identity.yaml", field: "user" },
|
|
40
|
+
{
|
|
41
|
+
layer: "organization",
|
|
42
|
+
key: "identity",
|
|
43
|
+
yamlPath: "organization/identity.yaml",
|
|
44
|
+
field: "identity",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
layer: "organization",
|
|
48
|
+
key: "principles",
|
|
49
|
+
yamlPath: "organization/principles.yaml",
|
|
50
|
+
field: "principles",
|
|
51
|
+
},
|
|
52
|
+
{ layer: "shadow", key: "profile", yamlPath: "shadow/profile.yaml", field: "profile" },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/** Port of `_REQUIRED_IDENTITY_KEYS`: a missing file here is a recorded error;
|
|
56
|
+
* a missing file for any other mapping is a fully silent skip. */
|
|
57
|
+
const REQUIRED_IDENTITY_KEYS = new Set([
|
|
58
|
+
"self/soul",
|
|
59
|
+
"ego/identity",
|
|
60
|
+
"ego/behavior",
|
|
61
|
+
"user/identity",
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
export interface SeedResult {
|
|
65
|
+
created: number;
|
|
66
|
+
updated: number;
|
|
67
|
+
skipped: number;
|
|
68
|
+
errors: string[];
|
|
69
|
+
/** One entry per scan-time print line, in Python's exact emission order. */
|
|
70
|
+
lines: string[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The command that edits an entry seed skipped. It is printed to be run as is, and
|
|
75
|
+
* a key from before CR104's grammar can hold shell syntax, so every value in it
|
|
76
|
+
* is one shell word. It named `memory`, the Python program, until CR104 (D1).
|
|
77
|
+
*/
|
|
78
|
+
function editCommand(layer: string, key: string): string {
|
|
79
|
+
return `${PROGRAM} identity edit ${shellWord(layer)} ${shellWord(key)}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function record(
|
|
83
|
+
result: SeedResult,
|
|
84
|
+
existing: boolean,
|
|
85
|
+
force: boolean,
|
|
86
|
+
label: string,
|
|
87
|
+
editHint: string,
|
|
88
|
+
): boolean {
|
|
89
|
+
if (existing && !force) {
|
|
90
|
+
result.skipped += 1;
|
|
91
|
+
result.lines.push(` \u2192 ${label} (skipped \u2014 to update, run: ${editHint})`);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function seedCoreIdentity(
|
|
98
|
+
db: WritableDatabase,
|
|
99
|
+
identityRoot: string,
|
|
100
|
+
force: boolean,
|
|
101
|
+
result: SeedResult,
|
|
102
|
+
readFile: (path: string) => string,
|
|
103
|
+
): void {
|
|
104
|
+
for (const mapping of IDENTITY_MAP) {
|
|
105
|
+
const label = `${mapping.layer}/${mapping.key}`;
|
|
106
|
+
try {
|
|
107
|
+
const { content, version } = loadYamlContent(
|
|
108
|
+
join(identityRoot, mapping.yamlPath),
|
|
109
|
+
mapping.field,
|
|
110
|
+
readFile,
|
|
111
|
+
);
|
|
112
|
+
if (!content) {
|
|
113
|
+
result.errors.push(`${label}: empty content`);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const existing = identityRowExists(db, mapping.layer, mapping.key);
|
|
117
|
+
if (!record(result, existing, force, label, editCommand(mapping.layer, mapping.key))) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
setIdentity(
|
|
121
|
+
db,
|
|
122
|
+
{ id: newId(), layer: mapping.layer, key: mapping.key, content, version },
|
|
123
|
+
nowIso(),
|
|
124
|
+
);
|
|
125
|
+
if (existing) {
|
|
126
|
+
result.updated += 1;
|
|
127
|
+
result.lines.push(` \u21bb ${label} (updated)`);
|
|
128
|
+
} else {
|
|
129
|
+
result.created += 1;
|
|
130
|
+
result.lines.push(` \u2713 ${label}`);
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (error instanceof YamlFileNotFoundError) {
|
|
134
|
+
if (REQUIRED_IDENTITY_KEYS.has(label)) {
|
|
135
|
+
result.errors.push(`${label}: ${error.message}`);
|
|
136
|
+
result.lines.push(` \u2717 ${label}: ${error.message}`);
|
|
137
|
+
}
|
|
138
|
+
// Else: fully silent skip for a missing, non-required file -- matches
|
|
139
|
+
// Python exactly (no error recorded, no line printed at all).
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
143
|
+
result.errors.push(`${label}: ${message}`);
|
|
144
|
+
result.lines.push(` \u2717 ${label}: ${message}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** `.yaml` filenames directly under `dir`, sorted (mirrors `sorted(dir.glob("*.yaml"))`). */
|
|
150
|
+
function yamlFileNames(dir: string): string[] {
|
|
151
|
+
if (!existsSync(dir)) return [];
|
|
152
|
+
return readdirSync(dir)
|
|
153
|
+
.filter((name) => name.endsWith(".yaml"))
|
|
154
|
+
.sort();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function seedPersonas(
|
|
158
|
+
db: WritableDatabase,
|
|
159
|
+
identityRoot: string,
|
|
160
|
+
force: boolean,
|
|
161
|
+
result: SeedResult,
|
|
162
|
+
readFile: (path: string) => string,
|
|
163
|
+
): void {
|
|
164
|
+
const personasDir = join(identityRoot, "personas");
|
|
165
|
+
for (const fileName of yamlFileNames(personasDir)) {
|
|
166
|
+
const stem = fileName.slice(0, -".yaml".length);
|
|
167
|
+
try {
|
|
168
|
+
const { personaId, content, version, metadataJson } = loadPersonaContent(
|
|
169
|
+
join(personasDir, fileName),
|
|
170
|
+
readFile,
|
|
171
|
+
);
|
|
172
|
+
if (!content) continue;
|
|
173
|
+
const label = `persona/${personaId}`;
|
|
174
|
+
const existing = identityRowExists(db, "persona", personaId);
|
|
175
|
+
if (!record(result, existing, force, label, editCommand("persona", personaId))) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
setIdentity(
|
|
179
|
+
db,
|
|
180
|
+
{ id: newId(), layer: "persona", key: personaId, content, version, metadata: metadataJson },
|
|
181
|
+
nowIso(),
|
|
182
|
+
);
|
|
183
|
+
if (existing) {
|
|
184
|
+
result.updated += 1;
|
|
185
|
+
result.lines.push(` \u21bb ${label} (updated)`);
|
|
186
|
+
} else {
|
|
187
|
+
result.created += 1;
|
|
188
|
+
result.lines.push(` \u2713 ${label}`);
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
192
|
+
result.errors.push(`persona/${stem}: ${message}`);
|
|
193
|
+
result.lines.push(` \u2717 persona/${stem}: ${message}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function seedJourneys(
|
|
199
|
+
db: WritableDatabase,
|
|
200
|
+
identityRoot: string,
|
|
201
|
+
force: boolean,
|
|
202
|
+
result: SeedResult,
|
|
203
|
+
readFile: (path: string) => string,
|
|
204
|
+
): void {
|
|
205
|
+
const journeysDir = join(identityRoot, "journeys");
|
|
206
|
+
for (const fileName of yamlFileNames(journeysDir)) {
|
|
207
|
+
const stem = fileName.slice(0, -".yaml".length);
|
|
208
|
+
try {
|
|
209
|
+
const { journeyId, content, version } = loadJourneyContent(
|
|
210
|
+
join(journeysDir, fileName),
|
|
211
|
+
readFile,
|
|
212
|
+
);
|
|
213
|
+
if (!content) continue;
|
|
214
|
+
const label = `journey/${journeyId}`;
|
|
215
|
+
const existing = identityRowExists(db, "journey", journeyId);
|
|
216
|
+
if (!record(result, existing, force, label, editCommand("journey", journeyId))) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
setIdentity(
|
|
220
|
+
db,
|
|
221
|
+
{ id: newId(), layer: "journey", key: journeyId, content, version },
|
|
222
|
+
nowIso(),
|
|
223
|
+
);
|
|
224
|
+
if (existing) {
|
|
225
|
+
result.updated += 1;
|
|
226
|
+
result.lines.push(` \u21bb ${label} (updated)`);
|
|
227
|
+
} else {
|
|
228
|
+
result.created += 1;
|
|
229
|
+
result.lines.push(` \u2713 ${label}`);
|
|
230
|
+
}
|
|
231
|
+
} catch (error) {
|
|
232
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
233
|
+
result.errors.push(`journey/${stem}: ${message}`);
|
|
234
|
+
result.lines.push(` \u2717 journey/${stem}: ${message}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Port of `seed()`'s three scan phases (core identity, personas, journeys),
|
|
241
|
+
* in order. Does NOT print the "Mirror home:"/"Identity root:" header lines —
|
|
242
|
+
* those depend on CLI-level resolution and are composed by the caller.
|
|
243
|
+
*/
|
|
244
|
+
export function runSeed(
|
|
245
|
+
db: WritableDatabase,
|
|
246
|
+
identityRoot: string,
|
|
247
|
+
options: { force?: boolean; readFile?: (path: string) => string } = {},
|
|
248
|
+
): SeedResult {
|
|
249
|
+
const force = options.force ?? false;
|
|
250
|
+
const readFile = options.readFile ?? ((path: string) => readFileSync(path, "utf8"));
|
|
251
|
+
const result: SeedResult = { created: 0, updated: 0, skipped: 0, errors: [], lines: [] };
|
|
252
|
+
seedCoreIdentity(db, identityRoot, force, result, readFile);
|
|
253
|
+
seedPersonas(db, identityRoot, force, result, readFile);
|
|
254
|
+
seedJourneys(db, identityRoot, force, result, readFile);
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// YAML template parsing for `seed` — the port of the load_* helpers in
|
|
2
|
+
// memory.cli.seed. Uses the `yaml` package (safe `parse`, no arbitrary object
|
|
3
|
+
// instantiation — the same trust boundary as Python's `yaml.safe_load`).
|
|
4
|
+
|
|
5
|
+
import { basename } from "node:path";
|
|
6
|
+
import { parse as parseYaml } from "yaml";
|
|
7
|
+
|
|
8
|
+
/** Port of Python's uncaught `FileNotFoundError(f"File not found: {full_path}")`. */
|
|
9
|
+
export class YamlFileNotFoundError extends Error {
|
|
10
|
+
readonly path: string;
|
|
11
|
+
constructor(path: string) {
|
|
12
|
+
super(`File not found: ${path}`);
|
|
13
|
+
this.path = path;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function asRecord(data: unknown): Record<string, unknown> {
|
|
18
|
+
return data !== null && typeof data === "object" && !Array.isArray(data)
|
|
19
|
+
? (data as Record<string, unknown>)
|
|
20
|
+
: {};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function stringField(data: Record<string, unknown>, field: string): string | null {
|
|
24
|
+
const value = data[field];
|
|
25
|
+
return typeof value === "string" ? value : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CoreYamlContent {
|
|
29
|
+
content: string;
|
|
30
|
+
version: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Port of `load_yaml_content`: read one field (and `version`) from a YAML
|
|
35
|
+
* file. Throws YamlFileNotFoundError when the file is absent, matching
|
|
36
|
+
* Python's explicit check (not a generic ENOENT from the read itself).
|
|
37
|
+
*/
|
|
38
|
+
export function loadYamlContent(
|
|
39
|
+
fullPath: string,
|
|
40
|
+
field: string,
|
|
41
|
+
readFile: (path: string) => string,
|
|
42
|
+
): CoreYamlContent {
|
|
43
|
+
let raw: string;
|
|
44
|
+
try {
|
|
45
|
+
raw = readFile(fullPath);
|
|
46
|
+
} catch {
|
|
47
|
+
throw new YamlFileNotFoundError(fullPath);
|
|
48
|
+
}
|
|
49
|
+
const data = asRecord(parseYaml(raw));
|
|
50
|
+
return {
|
|
51
|
+
content: stringField(data, field) ?? "",
|
|
52
|
+
version: stringField(data, "version") ?? "1.0.0",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface PersonaYamlContent {
|
|
57
|
+
personaId: string;
|
|
58
|
+
content: string;
|
|
59
|
+
version: string;
|
|
60
|
+
/** Canonical (plain JSON.stringify) form, per the DS6 identity.metadata
|
|
61
|
+
* decision: byte-mimicking Python's json.dumps spacing was the transition
|
|
62
|
+
* contract only; every reader JSON.parses, so key order (preserved by
|
|
63
|
+
* object-literal insertion order here) is what matters, not whitespace. */
|
|
64
|
+
metadataJson: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Port of `load_persona_content`. */
|
|
68
|
+
export function loadPersonaContent(
|
|
69
|
+
fullPath: string,
|
|
70
|
+
readFile: (path: string) => string,
|
|
71
|
+
): PersonaYamlContent {
|
|
72
|
+
const data = asRecord(parseYaml(readFile(fullPath)));
|
|
73
|
+
const stem = basename(fullPath, ".yaml");
|
|
74
|
+
const personaId = stringField(data, "persona_id") ?? stem;
|
|
75
|
+
const version = stringField(data, "version") ?? "1.0.0";
|
|
76
|
+
|
|
77
|
+
const parts: string[] = [];
|
|
78
|
+
const systemPrompt = stringField(data, "system_prompt");
|
|
79
|
+
if (systemPrompt) parts.push(systemPrompt);
|
|
80
|
+
const briefing = stringField(data, "briefing");
|
|
81
|
+
if (briefing) parts.push(`\n\n# Briefing\n\n${briefing}`);
|
|
82
|
+
|
|
83
|
+
const rawKeywords = data.routing_keywords;
|
|
84
|
+
const routingKeywords = Array.isArray(rawKeywords)
|
|
85
|
+
? rawKeywords.filter((item): item is string => typeof item === "string")
|
|
86
|
+
: [];
|
|
87
|
+
const metadata = {
|
|
88
|
+
persona_id: personaId,
|
|
89
|
+
name: stringField(data, "name"),
|
|
90
|
+
inherits_from: stringField(data, "inherits_from"),
|
|
91
|
+
description: stringField(data, "description"),
|
|
92
|
+
routing_keywords: routingKeywords,
|
|
93
|
+
default_model: stringField(data, "default_model"),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
personaId,
|
|
98
|
+
content: parts.join(""),
|
|
99
|
+
version,
|
|
100
|
+
metadataJson: JSON.stringify(metadata),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface JourneyYamlContent {
|
|
105
|
+
journeyId: string;
|
|
106
|
+
content: string;
|
|
107
|
+
version: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Port of `load_journey_content` (`_resolve_journey_id` inlined). */
|
|
111
|
+
export function loadJourneyContent(
|
|
112
|
+
fullPath: string,
|
|
113
|
+
readFile: (path: string) => string,
|
|
114
|
+
): JourneyYamlContent {
|
|
115
|
+
const data = asRecord(parseYaml(readFile(fullPath)));
|
|
116
|
+
const stem = basename(fullPath, ".yaml");
|
|
117
|
+
const journeyId = stringField(data, "journey_id") || stem;
|
|
118
|
+
const version = stringField(data, "version") ?? "1.0.0";
|
|
119
|
+
|
|
120
|
+
const parts: string[] = [];
|
|
121
|
+
const name = stringField(data, "name");
|
|
122
|
+
if (name) parts.push(`# ${name}`);
|
|
123
|
+
const status = stringField(data, "status");
|
|
124
|
+
if (status) parts.push(`**Status:** ${status}`);
|
|
125
|
+
const description = stringField(data, "description");
|
|
126
|
+
if (description) parts.push(`\n## Description\n\n${description}`);
|
|
127
|
+
const briefing = stringField(data, "briefing");
|
|
128
|
+
if (briefing) parts.push(`\n## Briefing\n\n${briefing}`);
|
|
129
|
+
const context = stringField(data, "context");
|
|
130
|
+
if (context) parts.push(`\n## Context\n\n${context}`);
|
|
131
|
+
|
|
132
|
+
return { journeyId, content: parts.join("\n"), version };
|
|
133
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// CV22.DS7.US6 plateau 4 — the Soul Mode identity integration, ported from
|
|
2
|
+
// `apply_identity_integration` and `append_identity_integration_to_content`
|
|
3
|
+
// (`src/memory/services/soul.py`).
|
|
4
|
+
//
|
|
5
|
+
// This is the only write in Soul Mode that touches identity, and it does two
|
|
6
|
+
// things: an audit row in `identity_integrations`, and a dated bullet appended
|
|
7
|
+
// into the identity DOCUMENT under a layer-specific section title.
|
|
8
|
+
//
|
|
9
|
+
// On the allowlist question: this write deliberately does NOT go through
|
|
10
|
+
// `applyConsolidationIdentityUpdate`. Python is explicit that the consolidation
|
|
11
|
+
// allowlist is narrow to the `propose_consolidation` -> accept flow, where the
|
|
12
|
+
// target layer is MODEL-CHOSEN and untrusted, and it names Soul Mode
|
|
13
|
+
// integration as a caller of the general-purpose write instead. The two also
|
|
14
|
+
// append differently -- consolidation joins raw content after a blank line,
|
|
15
|
+
// Soul inserts a dated bullet inside a titled section -- so routing Soul
|
|
16
|
+
// through that gate would change behavior, not just tighten it. Soul's own
|
|
17
|
+
// guards are reproduced exactly: the layer must be one of the four section
|
|
18
|
+
// titles, the content must be non-empty, and the CLI additionally requires
|
|
19
|
+
// `--confirm APPLY`. Whether that is the right guard for ritual-authored text
|
|
20
|
+
// is a product question recorded for Debt Review, not one a port may answer.
|
|
21
|
+
//
|
|
22
|
+
// The document surgery is index arithmetic over Markdown with three different
|
|
23
|
+
// strip rules in five lines. Two are graded by the golden. The third,
|
|
24
|
+
// `lstrip("\n")`, is reproduced faithfully but is provably UNREACHABLE as a
|
|
25
|
+
// divergence: the slice it operates on begins at a `"\n## "` match, so after the
|
|
26
|
+
// newlines the next character is always `#` and `trimStart()` would produce the
|
|
27
|
+
// same string. Substituting `trimStart()` fails no scenario, and that is a fact
|
|
28
|
+
// about the call site rather than a gap in the corpus -- recorded so nobody
|
|
29
|
+
// later "strengthens" the test with a case that cannot exist.
|
|
30
|
+
|
|
31
|
+
import type { WritableDatabase } from "#db/database.ts";
|
|
32
|
+
import { getIdentityContent } from "#identity/identityRead.ts";
|
|
33
|
+
import { type IdentityRow, upsertIdentity } from "#identity/identityStore.ts";
|
|
34
|
+
import { pythonJsonDumps } from "#util/pyGenerators.ts";
|
|
35
|
+
import { pyRStrip, pyStrip, sliceCodePoints } from "#util/pythonText.ts";
|
|
36
|
+
|
|
37
|
+
/** Port of `IDENTITY_INTEGRATION_SECTION_TITLES`. */
|
|
38
|
+
export const IDENTITY_INTEGRATION_SECTION_TITLES: Readonly<Record<string, string>> = {
|
|
39
|
+
self: "New Incorporated Principles",
|
|
40
|
+
shadow: "New Hidden Needs Recognized",
|
|
41
|
+
ego: "New Operational Patterns Identified",
|
|
42
|
+
persona: "New Participation Patterns Revealed",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** Python raises `ValueError`; the CLI renders it as `Error: <message>`. */
|
|
46
|
+
export class SoulApplyError extends Error {}
|
|
47
|
+
|
|
48
|
+
export interface IdentityIntegrationRow {
|
|
49
|
+
id: string;
|
|
50
|
+
layer: string;
|
|
51
|
+
key: string;
|
|
52
|
+
content: string;
|
|
53
|
+
source: string;
|
|
54
|
+
origin: string | null;
|
|
55
|
+
conversationId: string | null;
|
|
56
|
+
journalId: string | null;
|
|
57
|
+
createdAt: string;
|
|
58
|
+
status: string;
|
|
59
|
+
metadata: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ApplyIdentityIntegrationInput {
|
|
63
|
+
layer: string;
|
|
64
|
+
key: string;
|
|
65
|
+
content: string;
|
|
66
|
+
origin?: string | null;
|
|
67
|
+
conversationId?: string | null;
|
|
68
|
+
journalId?: string | null;
|
|
69
|
+
metadata?: Record<string, unknown> | null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ApplyIdentityIntegrationResult {
|
|
73
|
+
integration: IdentityIntegrationRow;
|
|
74
|
+
updatedContent: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Python's `x.strip() if isinstance(x, str) and x.strip() else None`. */
|
|
78
|
+
function strippedOrNull(value: string | null | undefined): string | null {
|
|
79
|
+
return typeof value === "string" && pyStrip(value) ? pyStrip(value) : null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function applyIdentityIntegration(
|
|
83
|
+
db: WritableDatabase,
|
|
84
|
+
input: ApplyIdentityIntegrationInput,
|
|
85
|
+
deps: { newId: () => string; nowIso: string },
|
|
86
|
+
): ApplyIdentityIntegrationResult {
|
|
87
|
+
const normalized = pyStrip(input.content);
|
|
88
|
+
if (!normalized) throw new SoulApplyError("identity content must not be empty");
|
|
89
|
+
if (!(input.layer in IDENTITY_INTEGRATION_SECTION_TITLES)) {
|
|
90
|
+
throw new SoulApplyError(`unsupported psyche layer: ${input.layer}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const integration: IdentityIntegrationRow = {
|
|
94
|
+
id: deps.newId(),
|
|
95
|
+
layer: input.layer,
|
|
96
|
+
key: input.key,
|
|
97
|
+
content: normalized,
|
|
98
|
+
source: "soul_mode",
|
|
99
|
+
origin: strippedOrNull(input.origin),
|
|
100
|
+
conversationId: strippedOrNull(input.conversationId),
|
|
101
|
+
journalId: strippedOrNull(input.journalId),
|
|
102
|
+
createdAt: deps.nowIso,
|
|
103
|
+
status: "active",
|
|
104
|
+
// Python: json.dumps(metadata or {}, ensure_ascii=False, sort_keys=True).
|
|
105
|
+
// Note the sort: the harvest journal's metadata is NOT sorted, so the two
|
|
106
|
+
// Soul writes use different rules and each is pinned by its own golden.
|
|
107
|
+
metadata: pythonJsonDumpsSorted(input.metadata ?? {}),
|
|
108
|
+
};
|
|
109
|
+
addIdentityIntegration(db, integration);
|
|
110
|
+
|
|
111
|
+
const existingContent = getIdentityContent(db, input.layer, input.key);
|
|
112
|
+
const updatedContent = appendIdentityIntegrationToContent(existingContent ?? "", {
|
|
113
|
+
layer: input.layer,
|
|
114
|
+
content: integration.content,
|
|
115
|
+
createdAt: integration.createdAt,
|
|
116
|
+
});
|
|
117
|
+
upsertIdentity(
|
|
118
|
+
db,
|
|
119
|
+
identityRowFor(db, input.layer, input.key, updatedContent, deps.newId),
|
|
120
|
+
deps.nowIso,
|
|
121
|
+
);
|
|
122
|
+
return { integration, updatedContent };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Port of `Store.add_identity_integration`. */
|
|
126
|
+
export function addIdentityIntegration(
|
|
127
|
+
db: WritableDatabase,
|
|
128
|
+
integration: IdentityIntegrationRow,
|
|
129
|
+
): void {
|
|
130
|
+
db.prepare(
|
|
131
|
+
`INSERT INTO identity_integrations (
|
|
132
|
+
id, layer, key, content, source, origin, conversation_id,
|
|
133
|
+
journal_id, created_at, status, metadata
|
|
134
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
135
|
+
).run(
|
|
136
|
+
integration.id,
|
|
137
|
+
integration.layer,
|
|
138
|
+
integration.key,
|
|
139
|
+
integration.content,
|
|
140
|
+
integration.source,
|
|
141
|
+
integration.origin,
|
|
142
|
+
integration.conversationId,
|
|
143
|
+
integration.journalId,
|
|
144
|
+
integration.createdAt,
|
|
145
|
+
integration.status,
|
|
146
|
+
integration.metadata,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Port of `append_identity_integration_to_content`.
|
|
152
|
+
*
|
|
153
|
+
* Four branches: empty document, heading absent, heading last, heading followed
|
|
154
|
+
* by another `## ` section. Python matches the heading as a SUBSTRING, so a
|
|
155
|
+
* longer heading that contains the title is treated as the section -- preserved
|
|
156
|
+
* here rather than corrected.
|
|
157
|
+
*/
|
|
158
|
+
export function appendIdentityIntegrationToContent(
|
|
159
|
+
currentContent: string,
|
|
160
|
+
integration: { layer: string; content: string; createdAt: string },
|
|
161
|
+
): string {
|
|
162
|
+
const title = IDENTITY_INTEGRATION_SECTION_TITLES[integration.layer];
|
|
163
|
+
if (title === undefined) {
|
|
164
|
+
throw new SoulApplyError(`unsupported psyche layer: ${integration.layer}`);
|
|
165
|
+
}
|
|
166
|
+
const date = sliceCodePoints(integration.createdAt, 10);
|
|
167
|
+
const bullet = `- [${date}] ${pyStrip(integration.content)}`;
|
|
168
|
+
const base = pyStrip(currentContent);
|
|
169
|
+
const heading = `## ${title}`;
|
|
170
|
+
if (!base) return `${heading}\n\n${bullet}`;
|
|
171
|
+
if (!base.includes(heading)) return `${base}\n\n${heading}\n\n${bullet}`;
|
|
172
|
+
|
|
173
|
+
const headingIndex = base.indexOf(heading);
|
|
174
|
+
const afterHeadingIndex = headingIndex + heading.length;
|
|
175
|
+
const nextSectionIndex = base.indexOf("\n## ", afterHeadingIndex);
|
|
176
|
+
const insertAt = nextSectionIndex === -1 ? base.length : nextSectionIndex;
|
|
177
|
+
const before = pyRStrip(base.slice(0, insertAt));
|
|
178
|
+
// Python `lstrip("\n")`: newline characters only, NOT whitespace.
|
|
179
|
+
// Python `lstrip("\n")`: newline characters only, NOT whitespace. Equivalent
|
|
180
|
+
// to `trimStart()` here because `insertAt` always points at a `"\n## "` match;
|
|
181
|
+
// kept in Python's form because that is what the oracle does.
|
|
182
|
+
const after = base.slice(insertAt).replace(/^\n+/, "");
|
|
183
|
+
const updated = `${before}\n${bullet}`;
|
|
184
|
+
return after ? `${updated}\n\n${after}` : updated;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Python passes `existing.model_copy(update={"content": ...})`, so an existing
|
|
189
|
+
* row keeps its id, version, and metadata and only its content moves.
|
|
190
|
+
*/
|
|
191
|
+
function identityRowFor(
|
|
192
|
+
db: WritableDatabase,
|
|
193
|
+
layer: string,
|
|
194
|
+
key: string,
|
|
195
|
+
content: string,
|
|
196
|
+
newId: () => string,
|
|
197
|
+
): IdentityRow {
|
|
198
|
+
const existing = db
|
|
199
|
+
.prepare("SELECT id, version, metadata FROM identity WHERE layer = ? AND key = ?")
|
|
200
|
+
.get(layer, key) as { id: string; version: string; metadata: string | null } | undefined;
|
|
201
|
+
// Absent row: Python builds a fresh `Identity`, whose defaults are a new uuid,
|
|
202
|
+
// version "1.0.0", and NULL metadata.
|
|
203
|
+
return {
|
|
204
|
+
id: existing?.id ?? newId(),
|
|
205
|
+
layer,
|
|
206
|
+
key,
|
|
207
|
+
content,
|
|
208
|
+
version: existing?.version ?? "1.0.0",
|
|
209
|
+
metadata: existing?.metadata ?? null,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Python `json.dumps(value, ensure_ascii=False, sort_keys=True)`. */
|
|
214
|
+
function pythonJsonDumpsSorted(value: Record<string, unknown>): string {
|
|
215
|
+
const sorted: Record<string, unknown> = {};
|
|
216
|
+
for (const key of Object.keys(value).sort()) sorted[key] = value[key];
|
|
217
|
+
return pythonJsonDumps(sorted);
|
|
218
|
+
}
|