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,769 @@
|
|
|
1
|
+
// CV22.DS7.US7 plateau 2 — Exploratory Story state, ported from
|
|
2
|
+
// `src/memory/services/explorer_story.py` and
|
|
3
|
+
// `src/memory/storage/explorer_stories.py`.
|
|
4
|
+
//
|
|
5
|
+
// A story lives in TWO places at once and both are graded by
|
|
6
|
+
// `explorer-story.golden.json`:
|
|
7
|
+
//
|
|
8
|
+
// * the durable `exploratory_stories` row, whose four `*_json` columns are
|
|
9
|
+
// written `ensure_ascii=False` in INSERTION order (never sorted), and whose
|
|
10
|
+
// absent experiment/handoff is SQL NULL rather than the string `"null"`;
|
|
11
|
+
// * the `__explorer_story__:<journey>` runtime session, which every mutation
|
|
12
|
+
// still rewrites. The service docstring calls that payload "pre-DS8"; it
|
|
13
|
+
// understates it. `_store_story` writes both stores on every mutation, so
|
|
14
|
+
// it is a live dual write, and the read falls back to it whenever no
|
|
15
|
+
// durable row exists.
|
|
16
|
+
//
|
|
17
|
+
// Dropping that fallback is the failure this module exists to avoid: an install
|
|
18
|
+
// that has not written a story since DS8 keeps its work only there, and a port
|
|
19
|
+
// that reads the table alone returns "no story" and exits 0 — the
|
|
20
|
+
// `conversations append` class (CR055) arriving through a read.
|
|
21
|
+
//
|
|
22
|
+
// Absent from this module since CV22.DS10.TS1: the Journey projection. Mirror no
|
|
23
|
+
// longer publishes `.mirror/projections`, so a story mutation no longer carries a
|
|
24
|
+
// decision about refreshing one.
|
|
25
|
+
|
|
26
|
+
import type { Database, WritableDatabase } from "#db/database.ts";
|
|
27
|
+
import { getRuntimeSession, upsertRuntimeSession } from "#mirror/runtimeSession.ts";
|
|
28
|
+
import { pythonJsonDumps } from "#util/pyGenerators.ts";
|
|
29
|
+
import { pySplitLines, pyStrip, sliceCodePoints } from "#util/pythonText.ts";
|
|
30
|
+
|
|
31
|
+
export const EXPLORER_STORY_SESSION_PREFIX = "__explorer_story__:";
|
|
32
|
+
|
|
33
|
+
const ATTRACTOR_STATUSES = new Set(["proposed", "accepted"]);
|
|
34
|
+
const HANDOFF_READINESS = new Set(["proposed", "confirmed"]);
|
|
35
|
+
const STORY_STATUSES = new Set(["active", "archived", "promoted"]);
|
|
36
|
+
|
|
37
|
+
/** Python raises `ValueError`; the CLI catches it and exits 1. */
|
|
38
|
+
export class ExplorerStoryError extends Error {}
|
|
39
|
+
|
|
40
|
+
export interface ExplorerAttractor {
|
|
41
|
+
readonly label: string;
|
|
42
|
+
readonly description: string | null;
|
|
43
|
+
readonly status: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ExplorerExperimentProposal {
|
|
47
|
+
readonly title: string;
|
|
48
|
+
readonly description: string | null;
|
|
49
|
+
readonly status: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ExplorerBuilderHandoff {
|
|
53
|
+
readonly title: string;
|
|
54
|
+
readonly summary: string | null;
|
|
55
|
+
readonly readiness: string;
|
|
56
|
+
readonly artifactDir: string | null;
|
|
57
|
+
readonly indexPath: string | null;
|
|
58
|
+
readonly exploratoryStoryPath: string | null;
|
|
59
|
+
readonly handoffInfoPath: string | null;
|
|
60
|
+
readonly productDesignProposalPath: string | null;
|
|
61
|
+
readonly fullConversationPath: string | null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ExplorerSourceConversation {
|
|
65
|
+
readonly conversationId: string;
|
|
66
|
+
readonly title: string | null;
|
|
67
|
+
readonly role: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ExplorerStory {
|
|
71
|
+
readonly journey: string;
|
|
72
|
+
readonly currentExploratoryStory: string | null;
|
|
73
|
+
readonly narrativeFieldSummary: string | null;
|
|
74
|
+
readonly lastStoryCard: string | null;
|
|
75
|
+
readonly attractors: readonly ExplorerAttractor[];
|
|
76
|
+
readonly experimentProposal: ExplorerExperimentProposal | null;
|
|
77
|
+
readonly builderHandoff: ExplorerBuilderHandoff | null;
|
|
78
|
+
readonly sourceConversations: readonly ExplorerSourceConversation[];
|
|
79
|
+
readonly id: string | null;
|
|
80
|
+
readonly title: string | null;
|
|
81
|
+
readonly status: string;
|
|
82
|
+
readonly createdAt: string | null;
|
|
83
|
+
readonly updatedAt: string | null;
|
|
84
|
+
readonly promotedAt: string | null;
|
|
85
|
+
readonly archivedAt: string | null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Python's `_UNSET` sentinel. The three optional scalars distinguish "not
|
|
90
|
+
* passed" (keep the existing value) from "passed None or blank" (clear it), and
|
|
91
|
+
* a port that maps both to `undefined` turns every clear into a keep.
|
|
92
|
+
*/
|
|
93
|
+
export const UNSET = Symbol("explorer.unset");
|
|
94
|
+
export type Settable = string | null | typeof UNSET;
|
|
95
|
+
|
|
96
|
+
/** Injected so the golden can advance them deterministically, as Python's are. */
|
|
97
|
+
export interface StoryClock {
|
|
98
|
+
now(): string;
|
|
99
|
+
uuid(): string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// --- normalization ---------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
/** Python `_string_or_none`: non-strings become null; blanks become null. */
|
|
105
|
+
function stringOrNone(value: unknown): string | null {
|
|
106
|
+
if (typeof value !== "string") return null;
|
|
107
|
+
const cleaned = pyStrip(value);
|
|
108
|
+
return cleaned === "" ? null : cleaned;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function validStatus(value: unknown): string {
|
|
112
|
+
return typeof value === "string" && ATTRACTOR_STATUSES.has(value) ? value : "proposed";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function validHandoffReadiness(value: unknown): string {
|
|
116
|
+
return typeof value === "string" && HANDOFF_READINESS.has(value) ? value : "proposed";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function validStoryStatus(value: unknown): string {
|
|
120
|
+
return typeof value === "string" && STORY_STATUSES.has(value) ? value : "active";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function normalizeJourney(journey: string): string {
|
|
124
|
+
const normalized = pyStrip(journey);
|
|
125
|
+
if (normalized === "") throw new ExplorerStoryError("journey must not be empty");
|
|
126
|
+
return normalized;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function sessionId(journey: string): string {
|
|
130
|
+
const normalized = pyStrip(journey);
|
|
131
|
+
if (normalized === "") throw new ExplorerStoryError("journey must not be empty");
|
|
132
|
+
return `${EXPLORER_STORY_SESSION_PREFIX}${normalized}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Python `_clean` then `_resolve_scalar`, composed. */
|
|
136
|
+
function resolveScalar(
|
|
137
|
+
value: Settable,
|
|
138
|
+
existing: string | null,
|
|
139
|
+
hasExisting: boolean,
|
|
140
|
+
): string | null {
|
|
141
|
+
if (value === UNSET) return hasExisting ? existing : null;
|
|
142
|
+
return stringOrNone(value);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// --- parsing ---------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
148
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function parseAttractors(value: unknown): ExplorerAttractor[] {
|
|
152
|
+
if (!Array.isArray(value)) return [];
|
|
153
|
+
const attractors: ExplorerAttractor[] = [];
|
|
154
|
+
for (const item of value) {
|
|
155
|
+
if (!isRecord(item)) continue;
|
|
156
|
+
const label = stringOrNone(item.label);
|
|
157
|
+
if (!label) continue;
|
|
158
|
+
attractors.push({
|
|
159
|
+
label,
|
|
160
|
+
description: stringOrNone(item.description),
|
|
161
|
+
status: validStatus(item.status),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return attractors;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function parseExperiment(value: unknown): ExplorerExperimentProposal | null {
|
|
168
|
+
if (!isRecord(value)) return null;
|
|
169
|
+
const title = stringOrNone(value.title);
|
|
170
|
+
if (!title) return null;
|
|
171
|
+
return {
|
|
172
|
+
title,
|
|
173
|
+
description: stringOrNone(value.description),
|
|
174
|
+
status: validStatus(value.status),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function parseHandoff(value: unknown): ExplorerBuilderHandoff | null {
|
|
179
|
+
if (!isRecord(value)) return null;
|
|
180
|
+
const title = stringOrNone(value.title);
|
|
181
|
+
if (!title) return null;
|
|
182
|
+
return {
|
|
183
|
+
title,
|
|
184
|
+
summary: stringOrNone(value.summary),
|
|
185
|
+
readiness: validHandoffReadiness(value.readiness),
|
|
186
|
+
artifactDir: stringOrNone(value.artifact_dir),
|
|
187
|
+
indexPath: stringOrNone(value.index_path),
|
|
188
|
+
exploratoryStoryPath: stringOrNone(value.exploratory_story_path),
|
|
189
|
+
handoffInfoPath: stringOrNone(value.handoff_info_path),
|
|
190
|
+
productDesignProposalPath: stringOrNone(value.product_design_proposal_path),
|
|
191
|
+
fullConversationPath: stringOrNone(value.full_conversation_path),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function parseSourceConversations(value: unknown): ExplorerSourceConversation[] {
|
|
196
|
+
if (!Array.isArray(value)) return [];
|
|
197
|
+
const sources: ExplorerSourceConversation[] = [];
|
|
198
|
+
for (const item of value) {
|
|
199
|
+
if (!isRecord(item)) continue;
|
|
200
|
+
const conversationId = stringOrNone(item.conversation_id);
|
|
201
|
+
if (!conversationId) continue;
|
|
202
|
+
sources.push({
|
|
203
|
+
conversationId,
|
|
204
|
+
title: stringOrNone(item.title),
|
|
205
|
+
role: stringOrNone(item.role) ?? "source evidence",
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return sources;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Python `_load_json`: a non-string, blank, or invalid column yields default. */
|
|
212
|
+
function loadJson(value: unknown, fallback: unknown): unknown {
|
|
213
|
+
if (typeof value !== "string" || pyStrip(value) === "") return fallback;
|
|
214
|
+
try {
|
|
215
|
+
return JSON.parse(value);
|
|
216
|
+
} catch {
|
|
217
|
+
return fallback;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// --- serialization ---------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
function attractorToDict(attractor: ExplorerAttractor): Record<string, unknown> {
|
|
224
|
+
return { label: attractor.label, description: attractor.description, status: attractor.status };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function experimentToDict(
|
|
228
|
+
proposal: ExplorerExperimentProposal | null,
|
|
229
|
+
): Record<string, unknown> | null {
|
|
230
|
+
if (proposal === null) return null;
|
|
231
|
+
return { title: proposal.title, description: proposal.description, status: proposal.status };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function handoffToDict(handoff: ExplorerBuilderHandoff | null): Record<string, unknown> | null {
|
|
235
|
+
if (handoff === null) return null;
|
|
236
|
+
return {
|
|
237
|
+
title: handoff.title,
|
|
238
|
+
summary: handoff.summary,
|
|
239
|
+
readiness: handoff.readiness,
|
|
240
|
+
artifact_dir: handoff.artifactDir,
|
|
241
|
+
index_path: handoff.indexPath,
|
|
242
|
+
exploratory_story_path: handoff.exploratoryStoryPath,
|
|
243
|
+
handoff_info_path: handoff.handoffInfoPath,
|
|
244
|
+
product_design_proposal_path: handoff.productDesignProposalPath,
|
|
245
|
+
full_conversation_path: handoff.fullConversationPath,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function sourceToDict(source: ExplorerSourceConversation): Record<string, unknown> {
|
|
250
|
+
return { conversation_id: source.conversationId, title: source.title, role: source.role };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// --- reads -----------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
function storyFromRow(row: Record<string, unknown>): ExplorerStory {
|
|
256
|
+
return {
|
|
257
|
+
journey: String(row.journey),
|
|
258
|
+
currentExploratoryStory: stringOrNone(row.current_story),
|
|
259
|
+
narrativeFieldSummary: stringOrNone(row.narrative_summary),
|
|
260
|
+
lastStoryCard: stringOrNone(row.last_story_card),
|
|
261
|
+
attractors: parseAttractors(loadJson(row.attractors_json, [])),
|
|
262
|
+
experimentProposal: parseExperiment(loadJson(row.experiment_proposal_json, null)),
|
|
263
|
+
builderHandoff: parseHandoff(loadJson(row.builder_handoff_json, null)),
|
|
264
|
+
sourceConversations: parseSourceConversations(loadJson(row.source_conversations_json, [])),
|
|
265
|
+
id: stringOrNone(row.id),
|
|
266
|
+
title: stringOrNone(row.title),
|
|
267
|
+
status: validStoryStatus(row.status),
|
|
268
|
+
createdAt: stringOrNone(row.created_at),
|
|
269
|
+
updatedAt: stringOrNone(row.updated_at),
|
|
270
|
+
promotedAt: stringOrNone(row.promoted_at),
|
|
271
|
+
archivedAt: stringOrNone(row.archived_at),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function activeRow(db: Database, journey: string): Record<string, unknown> | null {
|
|
276
|
+
const row = db
|
|
277
|
+
.prepare(
|
|
278
|
+
`SELECT * FROM exploratory_stories
|
|
279
|
+
WHERE journey = ? AND status = 'active'
|
|
280
|
+
ORDER BY updated_at DESC
|
|
281
|
+
LIMIT 1`,
|
|
282
|
+
)
|
|
283
|
+
.get(journey);
|
|
284
|
+
return row ?? null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Port of `_get_runtime_explorer_story` — the fallback that keeps pre-DS8
|
|
289
|
+
* stories readable. Every malformed shape resolves to Python's result rather
|
|
290
|
+
* than throwing: invalid JSON, a non-object payload, and non-string fields all
|
|
291
|
+
* degrade quietly, because a corrupt column must not take down a read.
|
|
292
|
+
*/
|
|
293
|
+
function runtimeStory(db: Database, journey: string): ExplorerStory | null {
|
|
294
|
+
const session = getRuntimeSession(db, sessionId(journey));
|
|
295
|
+
if (!session?.active || !session.metadata) return null;
|
|
296
|
+
|
|
297
|
+
let data: unknown;
|
|
298
|
+
try {
|
|
299
|
+
data = JSON.parse(session.metadata);
|
|
300
|
+
} catch {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
if (!isRecord(data)) return null;
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
journey,
|
|
307
|
+
currentExploratoryStory: stringOrNone(data.current_exploratory_story),
|
|
308
|
+
narrativeFieldSummary: stringOrNone(data.narrative_field_summary),
|
|
309
|
+
lastStoryCard: stringOrNone(data.last_story_card),
|
|
310
|
+
attractors: parseAttractors(data.attractors),
|
|
311
|
+
experimentProposal: parseExperiment(data.experiment_proposal),
|
|
312
|
+
builderHandoff: parseHandoff(data.builder_handoff),
|
|
313
|
+
sourceConversations: parseSourceConversations(data.source_conversations),
|
|
314
|
+
id: null,
|
|
315
|
+
title: null,
|
|
316
|
+
status: "active",
|
|
317
|
+
createdAt: null,
|
|
318
|
+
updatedAt: null,
|
|
319
|
+
promotedAt: null,
|
|
320
|
+
archivedAt: null,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Port of `get_explorer_story`: durable row first, legacy payload second. */
|
|
325
|
+
export function getExplorerStory(db: Database, journey: string): ExplorerStory | null {
|
|
326
|
+
const normalized = normalizeJourney(journey);
|
|
327
|
+
const row = activeRow(db, normalized);
|
|
328
|
+
if (row) return storyFromRow(row);
|
|
329
|
+
return runtimeStory(db, normalized);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** Port of `list_explorer_stories`: active first, then promoted, then archived. */
|
|
333
|
+
export function listExplorerStories(db: Database, journey: string): ExplorerStory[] {
|
|
334
|
+
const normalized = normalizeJourney(journey);
|
|
335
|
+
return db
|
|
336
|
+
.prepare(
|
|
337
|
+
`SELECT * FROM exploratory_stories
|
|
338
|
+
WHERE journey = ?
|
|
339
|
+
ORDER BY
|
|
340
|
+
CASE status
|
|
341
|
+
WHEN 'active' THEN 0
|
|
342
|
+
WHEN 'promoted' THEN 1
|
|
343
|
+
WHEN 'archived' THEN 2
|
|
344
|
+
ELSE 3
|
|
345
|
+
END,
|
|
346
|
+
updated_at DESC`,
|
|
347
|
+
)
|
|
348
|
+
.all(normalized)
|
|
349
|
+
.map(storyFromRow);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// --- writes ----------------------------------------------------------------
|
|
353
|
+
|
|
354
|
+
/** Python `_derive_title`: first line of the story, 80 code points, stripped. */
|
|
355
|
+
function deriveTitle(story: ExplorerStory): string {
|
|
356
|
+
const text = story.currentExploratoryStory ?? story.narrativeFieldSummary ?? "Exploratory Story";
|
|
357
|
+
const firstLine = pySplitLines(pyStrip(text))[0] ?? "";
|
|
358
|
+
return pyStrip(sliceCodePoints(firstLine, 80)) || "Exploratory Story";
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function storyPayload(story: ExplorerStory): Record<string, unknown> {
|
|
362
|
+
return {
|
|
363
|
+
id: story.id,
|
|
364
|
+
title: story.title,
|
|
365
|
+
status: story.status,
|
|
366
|
+
current_exploratory_story: story.currentExploratoryStory,
|
|
367
|
+
narrative_field_summary: story.narrativeFieldSummary,
|
|
368
|
+
last_story_card: story.lastStoryCard,
|
|
369
|
+
attractors: story.attractors.map(attractorToDict),
|
|
370
|
+
experiment_proposal: experimentToDict(story.experimentProposal),
|
|
371
|
+
builder_handoff: handoffToDict(story.builderHandoff),
|
|
372
|
+
source_conversations: story.sourceConversations.map(sourceToDict),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// The runtime-session row carries its OWN timestamp in Python: the session
|
|
377
|
+
// store calls its own `_now()`, not the one `storage/explorer_stories.py`
|
|
378
|
+
// imported. So these writes reuse the timestamp already minted for the durable
|
|
379
|
+
// row instead of asking the clock again — one mutation, one tick, exactly as
|
|
380
|
+
// the oracle consumes it. `runtime_sessions.updated_at` is not part of the
|
|
381
|
+
// graded contract; the metadata bytes and the `active` flag are.
|
|
382
|
+
function writeRuntimeStory(db: WritableDatabase, story: ExplorerStory, now: string): void {
|
|
383
|
+
upsertRuntimeSession(
|
|
384
|
+
db,
|
|
385
|
+
sessionId(story.journey),
|
|
386
|
+
{
|
|
387
|
+
interface: "explorer_story",
|
|
388
|
+
journey: story.journey,
|
|
389
|
+
active: true,
|
|
390
|
+
metadata: pythonJsonDumps(storyPayload(story)),
|
|
391
|
+
},
|
|
392
|
+
now,
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function clearRuntimeStory(db: WritableDatabase, journey: string, now: string): void {
|
|
397
|
+
upsertRuntimeSession(
|
|
398
|
+
db,
|
|
399
|
+
sessionId(journey),
|
|
400
|
+
{ interface: "explorer_story", journey, active: false, metadata: null },
|
|
401
|
+
now,
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Port of `upsert_active_explorer_story_record`. `id` and `created_at` are
|
|
407
|
+
* inherited from the existing active row; only `updated_at` moves. The four
|
|
408
|
+
* JSON columns go through `pythonJsonDumps` for `ensure_ascii=False` insertion
|
|
409
|
+
* order, and an absent experiment/handoff is written as SQL NULL — the string
|
|
410
|
+
* `"null"` would be invisible to a parsed comparison and wrong on the wire.
|
|
411
|
+
*/
|
|
412
|
+
function upsertActiveRow(
|
|
413
|
+
db: WritableDatabase,
|
|
414
|
+
story: ExplorerStory,
|
|
415
|
+
clock: StoryClock,
|
|
416
|
+
): { story: ExplorerStory; now: string } {
|
|
417
|
+
const now = clock.now();
|
|
418
|
+
const existing = activeRow(db, story.journey);
|
|
419
|
+
const recordId = existing ? String(existing.id) : clock.uuid();
|
|
420
|
+
const createdAt = existing ? String(existing.created_at) : now;
|
|
421
|
+
const experiment = experimentToDict(story.experimentProposal);
|
|
422
|
+
const handoff = handoffToDict(story.builderHandoff);
|
|
423
|
+
|
|
424
|
+
db.prepare(
|
|
425
|
+
`INSERT INTO exploratory_stories
|
|
426
|
+
(id, journey, title, status, current_story, narrative_summary,
|
|
427
|
+
last_story_card, attractors_json, experiment_proposal_json,
|
|
428
|
+
builder_handoff_json, source_conversations_json, created_at, updated_at)
|
|
429
|
+
VALUES (?, ?, ?, 'active', ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
430
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
431
|
+
title = excluded.title,
|
|
432
|
+
current_story = excluded.current_story,
|
|
433
|
+
narrative_summary = excluded.narrative_summary,
|
|
434
|
+
last_story_card = excluded.last_story_card,
|
|
435
|
+
attractors_json = excluded.attractors_json,
|
|
436
|
+
experiment_proposal_json = excluded.experiment_proposal_json,
|
|
437
|
+
builder_handoff_json = excluded.builder_handoff_json,
|
|
438
|
+
source_conversations_json = excluded.source_conversations_json,
|
|
439
|
+
updated_at = excluded.updated_at`,
|
|
440
|
+
).run(
|
|
441
|
+
recordId,
|
|
442
|
+
story.journey,
|
|
443
|
+
story.title ?? deriveTitle(story),
|
|
444
|
+
story.currentExploratoryStory,
|
|
445
|
+
story.narrativeFieldSummary,
|
|
446
|
+
story.lastStoryCard,
|
|
447
|
+
pythonJsonDumps(story.attractors.map(attractorToDict)),
|
|
448
|
+
experiment === null ? null : pythonJsonDumps(experiment),
|
|
449
|
+
handoff === null ? null : pythonJsonDumps(handoff),
|
|
450
|
+
pythonJsonDumps(story.sourceConversations.map(sourceToDict)),
|
|
451
|
+
createdAt,
|
|
452
|
+
now,
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
const persisted = activeRow(db, story.journey);
|
|
456
|
+
if (persisted === null) {
|
|
457
|
+
// Defensive, and reachable: an id collision updates a non-active row
|
|
458
|
+
// without restoring `status='active'`. Python raises here too.
|
|
459
|
+
throw new ExplorerStoryError("failed to persist active Exploratory Story");
|
|
460
|
+
}
|
|
461
|
+
return { story: storyFromRow(persisted), now };
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function emptyStory(journey: string): ExplorerStory {
|
|
465
|
+
return {
|
|
466
|
+
journey,
|
|
467
|
+
currentExploratoryStory: null,
|
|
468
|
+
narrativeFieldSummary: null,
|
|
469
|
+
lastStoryCard: null,
|
|
470
|
+
attractors: [],
|
|
471
|
+
experimentProposal: null,
|
|
472
|
+
builderHandoff: null,
|
|
473
|
+
sourceConversations: [],
|
|
474
|
+
id: null,
|
|
475
|
+
title: null,
|
|
476
|
+
status: "active",
|
|
477
|
+
createdAt: null,
|
|
478
|
+
updatedAt: null,
|
|
479
|
+
promotedAt: null,
|
|
480
|
+
archivedAt: null,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/** Port of `_store_story`: durable row, then the runtime payload. */
|
|
485
|
+
function storeStory(db: WritableDatabase, story: ExplorerStory, clock: StoryClock): StoryMutation {
|
|
486
|
+
const { story: persisted, now } = upsertActiveRow(db, story, clock);
|
|
487
|
+
writeRuntimeStory(db, persisted, now);
|
|
488
|
+
return { story: persisted };
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function carryForward(existing: ExplorerStory | null, journey: string): ExplorerStory {
|
|
492
|
+
return existing ?? emptyStory(journey);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export interface StoryMutation {
|
|
496
|
+
readonly story: ExplorerStory;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/** Port of `update_explorer_story`. Omitted scalars keep; explicit ones clear. */
|
|
500
|
+
export function updateExplorerStory(
|
|
501
|
+
db: WritableDatabase,
|
|
502
|
+
journey: string,
|
|
503
|
+
clock: StoryClock,
|
|
504
|
+
fields: {
|
|
505
|
+
currentExploratoryStory?: Settable;
|
|
506
|
+
narrativeFieldSummary?: Settable;
|
|
507
|
+
lastStoryCard?: Settable;
|
|
508
|
+
} = {},
|
|
509
|
+
): StoryMutation {
|
|
510
|
+
const normalized = normalizeJourney(journey);
|
|
511
|
+
const existing = getExplorerStory(db, normalized);
|
|
512
|
+
const base = carryForward(existing, normalized);
|
|
513
|
+
const hasExisting = existing !== null;
|
|
514
|
+
|
|
515
|
+
// Presence, never nullishness. `fields.x ?? UNSET` looks equivalent and is
|
|
516
|
+
// the bug: an explicit `null` means CLEAR, and `??` would fold it into the
|
|
517
|
+
// absent case, turning every clear into a keep. Python distinguishes the two
|
|
518
|
+
// with a sentinel default; the only faithful JavaScript equivalent is `in`.
|
|
519
|
+
const settable = (key: keyof typeof fields): Settable =>
|
|
520
|
+
key in fields ? (fields[key] as Settable) : UNSET;
|
|
521
|
+
|
|
522
|
+
return storeStory(
|
|
523
|
+
db,
|
|
524
|
+
{
|
|
525
|
+
...base,
|
|
526
|
+
journey: normalized,
|
|
527
|
+
currentExploratoryStory: resolveScalar(
|
|
528
|
+
settable("currentExploratoryStory"),
|
|
529
|
+
base.currentExploratoryStory,
|
|
530
|
+
hasExisting,
|
|
531
|
+
),
|
|
532
|
+
narrativeFieldSummary: resolveScalar(
|
|
533
|
+
settable("narrativeFieldSummary"),
|
|
534
|
+
base.narrativeFieldSummary,
|
|
535
|
+
hasExisting,
|
|
536
|
+
),
|
|
537
|
+
lastStoryCard: resolveScalar(settable("lastStoryCard"), base.lastStoryCard, hasExisting),
|
|
538
|
+
title: hasExisting ? base.title : null,
|
|
539
|
+
},
|
|
540
|
+
clock,
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function normalizeAttractor(attractor: {
|
|
545
|
+
label: string;
|
|
546
|
+
description?: string | null;
|
|
547
|
+
status?: string;
|
|
548
|
+
}): ExplorerAttractor {
|
|
549
|
+
const label = stringOrNone(attractor.label);
|
|
550
|
+
if (!label) throw new ExplorerStoryError("attractor label must not be empty");
|
|
551
|
+
return {
|
|
552
|
+
label,
|
|
553
|
+
description: stringOrNone(attractor.description),
|
|
554
|
+
status: validStatus(attractor.status),
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export function setExplorerAttractors(
|
|
559
|
+
db: WritableDatabase,
|
|
560
|
+
journey: string,
|
|
561
|
+
clock: StoryClock,
|
|
562
|
+
attractors: readonly { label: string; description?: string | null; status?: string }[],
|
|
563
|
+
): StoryMutation {
|
|
564
|
+
const normalized = normalizeJourney(journey);
|
|
565
|
+
const base = carryForward(getExplorerStory(db, normalized), normalized);
|
|
566
|
+
return storeStory(
|
|
567
|
+
db,
|
|
568
|
+
{ ...base, journey: normalized, attractors: attractors.map(normalizeAttractor) },
|
|
569
|
+
clock,
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export function setExplorerExperimentProposal(
|
|
574
|
+
db: WritableDatabase,
|
|
575
|
+
journey: string,
|
|
576
|
+
clock: StoryClock,
|
|
577
|
+
proposal: { title: string; description?: string | null; status?: string },
|
|
578
|
+
): StoryMutation {
|
|
579
|
+
const normalized = normalizeJourney(journey);
|
|
580
|
+
const title = stringOrNone(proposal.title);
|
|
581
|
+
if (!title) throw new ExplorerStoryError("experiment title must not be empty");
|
|
582
|
+
const base = carryForward(getExplorerStory(db, normalized), normalized);
|
|
583
|
+
return storeStory(
|
|
584
|
+
db,
|
|
585
|
+
{
|
|
586
|
+
...base,
|
|
587
|
+
journey: normalized,
|
|
588
|
+
experimentProposal: {
|
|
589
|
+
title,
|
|
590
|
+
description: stringOrNone(proposal.description),
|
|
591
|
+
status: validStatus(proposal.status),
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
clock,
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
export function setExplorerBuilderHandoff(
|
|
599
|
+
db: WritableDatabase,
|
|
600
|
+
journey: string,
|
|
601
|
+
clock: StoryClock,
|
|
602
|
+
handoff: {
|
|
603
|
+
title: string;
|
|
604
|
+
summary?: string | null;
|
|
605
|
+
readiness?: string;
|
|
606
|
+
artifactDir?: string | null;
|
|
607
|
+
indexPath?: string | null;
|
|
608
|
+
exploratoryStoryPath?: string | null;
|
|
609
|
+
handoffInfoPath?: string | null;
|
|
610
|
+
productDesignProposalPath?: string | null;
|
|
611
|
+
fullConversationPath?: string | null;
|
|
612
|
+
},
|
|
613
|
+
): StoryMutation {
|
|
614
|
+
const normalized = normalizeJourney(journey);
|
|
615
|
+
const title = stringOrNone(handoff.title);
|
|
616
|
+
if (!title) throw new ExplorerStoryError("builder handoff title must not be empty");
|
|
617
|
+
const base = carryForward(getExplorerStory(db, normalized), normalized);
|
|
618
|
+
return storeStory(
|
|
619
|
+
db,
|
|
620
|
+
{
|
|
621
|
+
...base,
|
|
622
|
+
journey: normalized,
|
|
623
|
+
builderHandoff: {
|
|
624
|
+
title,
|
|
625
|
+
summary: stringOrNone(handoff.summary),
|
|
626
|
+
readiness: validHandoffReadiness(handoff.readiness),
|
|
627
|
+
artifactDir: stringOrNone(handoff.artifactDir),
|
|
628
|
+
indexPath: stringOrNone(handoff.indexPath),
|
|
629
|
+
exploratoryStoryPath: stringOrNone(handoff.exploratoryStoryPath),
|
|
630
|
+
handoffInfoPath: stringOrNone(handoff.handoffInfoPath),
|
|
631
|
+
productDesignProposalPath: stringOrNone(handoff.productDesignProposalPath),
|
|
632
|
+
fullConversationPath: stringOrNone(handoff.fullConversationPath),
|
|
633
|
+
},
|
|
634
|
+
},
|
|
635
|
+
clock,
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
export function setExplorerSourceConversations(
|
|
640
|
+
db: WritableDatabase,
|
|
641
|
+
journey: string,
|
|
642
|
+
clock: StoryClock,
|
|
643
|
+
sources: readonly { conversationId: string; title?: string | null; role?: string }[],
|
|
644
|
+
): StoryMutation {
|
|
645
|
+
const normalized = normalizeJourney(journey);
|
|
646
|
+
const base = carryForward(getExplorerStory(db, normalized), normalized);
|
|
647
|
+
const normalizedSources = sources.map((source) => {
|
|
648
|
+
const conversationId = stringOrNone(source.conversationId);
|
|
649
|
+
if (!conversationId) {
|
|
650
|
+
throw new ExplorerStoryError("source conversation id must not be empty");
|
|
651
|
+
}
|
|
652
|
+
return {
|
|
653
|
+
conversationId,
|
|
654
|
+
title: stringOrNone(source.title),
|
|
655
|
+
role: stringOrNone(source.role) ?? "source evidence",
|
|
656
|
+
};
|
|
657
|
+
});
|
|
658
|
+
return storeStory(
|
|
659
|
+
db,
|
|
660
|
+
{ ...base, journey: normalized, sourceConversations: normalizedSources },
|
|
661
|
+
clock,
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function transitionActive(
|
|
666
|
+
db: WritableDatabase,
|
|
667
|
+
journey: string,
|
|
668
|
+
clock: StoryClock,
|
|
669
|
+
status: "archived" | "promoted",
|
|
670
|
+
timestampColumn: "archived_at" | "promoted_at",
|
|
671
|
+
): { story: ExplorerStory | null } {
|
|
672
|
+
const normalized = normalizeJourney(journey);
|
|
673
|
+
const existing = activeRow(db, normalized);
|
|
674
|
+
|
|
675
|
+
if (!existing) {
|
|
676
|
+
// Python clears the runtime payload even when nothing was active, and
|
|
677
|
+
// returns before minting a story timestamp.
|
|
678
|
+
clearRuntimeStory(db, normalized, clock.now());
|
|
679
|
+
return { story: null };
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const now = clock.now();
|
|
683
|
+
db.prepare(
|
|
684
|
+
`UPDATE exploratory_stories
|
|
685
|
+
SET status = ?, ${timestampColumn} = ?, updated_at = ?
|
|
686
|
+
WHERE id = ?`,
|
|
687
|
+
).run(status, now, now, String(existing.id));
|
|
688
|
+
|
|
689
|
+
const row = db.prepare("SELECT * FROM exploratory_stories WHERE id = ?").get(String(existing.id));
|
|
690
|
+
clearRuntimeStory(db, normalized, now);
|
|
691
|
+
return { story: row ? storyFromRow(row) : null };
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
export function archiveExplorerStory(
|
|
695
|
+
db: WritableDatabase,
|
|
696
|
+
journey: string,
|
|
697
|
+
clock: StoryClock,
|
|
698
|
+
): { story: ExplorerStory | null } {
|
|
699
|
+
return transitionActive(db, journey, clock, "archived", "archived_at");
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
export function markExplorerStoryPromoted(
|
|
703
|
+
db: WritableDatabase,
|
|
704
|
+
journey: string,
|
|
705
|
+
clock: StoryClock,
|
|
706
|
+
): { story: ExplorerStory | null } {
|
|
707
|
+
return transitionActive(db, journey, clock, "promoted", "promoted_at");
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/** Port of `clear_explorer_story`: archive under the pre-DS8 command's name. */
|
|
711
|
+
export function clearExplorerStory(db: WritableDatabase, journey: string, clock: StoryClock): void {
|
|
712
|
+
archiveExplorerStory(db, journey, clock);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// --- projection refresh decision -------------------------------------------
|
|
716
|
+
|
|
717
|
+
// --- context render --------------------------------------------------------
|
|
718
|
+
|
|
719
|
+
/** Port of `render_explorer_story_context` — prompt injection, not a card. */
|
|
720
|
+
export function renderExplorerStoryContext(story: ExplorerStory): string {
|
|
721
|
+
const lines: string[] = ["=== △ Exploratory Story ===", `journey: ${story.journey}`];
|
|
722
|
+
if (story.id) lines.push(`id: ${story.id}`);
|
|
723
|
+
if (story.status) lines.push(`status: ${story.status}`);
|
|
724
|
+
if (story.title) lines.push(`title: ${story.title}`);
|
|
725
|
+
if (story.currentExploratoryStory) {
|
|
726
|
+
lines.push("", "current exploratory story:", story.currentExploratoryStory);
|
|
727
|
+
}
|
|
728
|
+
if (story.narrativeFieldSummary) {
|
|
729
|
+
lines.push("", "narrative field summary:", story.narrativeFieldSummary);
|
|
730
|
+
}
|
|
731
|
+
if (story.lastStoryCard) lines.push("", "last story card:", story.lastStoryCard);
|
|
732
|
+
if (story.attractors.length > 0) {
|
|
733
|
+
lines.push("", "attractors:");
|
|
734
|
+
for (const attractor of story.attractors) {
|
|
735
|
+
lines.push(`- ${attractor.label} [${attractor.status}]`);
|
|
736
|
+
if (attractor.description) lines.push(` ${attractor.description}`);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
if (story.experimentProposal) {
|
|
740
|
+
lines.push(
|
|
741
|
+
"",
|
|
742
|
+
"experiment proposal:",
|
|
743
|
+
`${story.experimentProposal.title} [${story.experimentProposal.status}]`,
|
|
744
|
+
);
|
|
745
|
+
if (story.experimentProposal.description) lines.push(story.experimentProposal.description);
|
|
746
|
+
}
|
|
747
|
+
if (story.sourceConversations.length > 0) {
|
|
748
|
+
lines.push("", "source conversations:");
|
|
749
|
+
for (const source of story.sourceConversations) {
|
|
750
|
+
const title = source.title ? ` — ${source.title}` : "";
|
|
751
|
+
lines.push(`- ${source.conversationId}${title} [${source.role}]`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (story.builderHandoff) {
|
|
755
|
+
lines.push(
|
|
756
|
+
"",
|
|
757
|
+
"builder handoff:",
|
|
758
|
+
`${story.builderHandoff.title} [${story.builderHandoff.readiness}]`,
|
|
759
|
+
);
|
|
760
|
+
if (story.builderHandoff.artifactDir) {
|
|
761
|
+
lines.push(`artifact_dir: ${story.builderHandoff.artifactDir}`);
|
|
762
|
+
}
|
|
763
|
+
if (story.builderHandoff.indexPath) lines.push(`index: ${story.builderHandoff.indexPath}`);
|
|
764
|
+
if (story.builderHandoff.fullConversationPath) {
|
|
765
|
+
lines.push(`full conversation: ${story.builderHandoff.fullConversationPath}`);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return lines.join("\n");
|
|
769
|
+
}
|