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,176 @@
|
|
|
1
|
+
// Claude Code hook entries (CV22.DS10.TS5 plateau 1, decision D4).
|
|
2
|
+
//
|
|
3
|
+
// Replaces the bodies of `.claude/hooks/*.sh` and the identical set under
|
|
4
|
+
// `plugins/mirror-mind/hooks/`, which spawned the Python interpreter for the
|
|
5
|
+
// core's CLI, for two hook modules, and for an inline import of the
|
|
6
|
+
// conversation logger's `hook_user_prompt` -- the last two reaching into
|
|
7
|
+
// modules that were never commands, and so never in DS7's denominator.
|
|
8
|
+
//
|
|
9
|
+
// Behavior is preserved case for case, including the cases that look like
|
|
10
|
+
// bugs and are not: a skill invocation is still logged (Claude's hook fires
|
|
11
|
+
// for every prompt and the logger decides), a missing session id still fails
|
|
12
|
+
// loud, and every failure is still non-fatal to the user's turn.
|
|
13
|
+
|
|
14
|
+
import { resolveDbPath } from "#frontDoor/dbPath.ts";
|
|
15
|
+
import { openLiveWriteDatabase } from "#frontDoor/liveBackup.ts";
|
|
16
|
+
import { markInjected, needsInject, readMirrorState } from "./mirrorState.ts";
|
|
17
|
+
import { parseHookPayload, readStdin } from "./payload.ts";
|
|
18
|
+
import {
|
|
19
|
+
noteHookFailure,
|
|
20
|
+
operatingInstructionsFor,
|
|
21
|
+
runFrontDoor,
|
|
22
|
+
runFrontDoorQuietly,
|
|
23
|
+
sessionStartOutput,
|
|
24
|
+
} from "./runtime.ts";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* `session-start.sh`: make sure logging is on for this session, and hand
|
|
28
|
+
* Claude Code the Operating Instructions unless the project it opened in is
|
|
29
|
+
* this same tree (US3, D13 -- `operatingInstructionsFor`). The plugin in
|
|
30
|
+
* Claude Code's cache delivers them this way; the checkout's own wrappers
|
|
31
|
+
* find the project already carrying the file and stay quiet, as before.
|
|
32
|
+
*/
|
|
33
|
+
export async function claudeSessionStart(env: NodeJS.ProcessEnv = process.env): Promise<number> {
|
|
34
|
+
await runFrontDoorQuietly("claude:session-start", ["conversation-logger", "session-start"]);
|
|
35
|
+
process.stdout.write(
|
|
36
|
+
sessionStartOutput(operatingInstructionsFor("claude:session-start", env.CLAUDE_PROJECT_DIR)),
|
|
37
|
+
);
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* `log-user-prompt.sh`: record the user's turn.
|
|
43
|
+
*
|
|
44
|
+
* The shell piped stdin into `hook_user_prompt()`. The front door already owns
|
|
45
|
+
* that surface -- `conversation-logger user-prompt` is one of its two
|
|
46
|
+
* stdin-reading subcommands -- so the payload is handed straight to it rather
|
|
47
|
+
* than re-parsed here.
|
|
48
|
+
*/
|
|
49
|
+
export async function claudeUserPrompt(): Promise<number> {
|
|
50
|
+
// stdin is deliberately NOT consumed here: the route reads it itself, so
|
|
51
|
+
// handing it the payload means not reading it first.
|
|
52
|
+
await runFrontDoorQuietly("claude:user-prompt", ["conversation-logger", "user-prompt"]);
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** `log-session-end.sh`: close the session, then take the silent backup. */
|
|
57
|
+
export async function claudeSessionEnd(): Promise<number> {
|
|
58
|
+
await runFrontDoorQuietly("claude:session-end", ["conversation-logger", "session-end"]);
|
|
59
|
+
await runFrontDoorQuietly("claude:session-end", ["backup", "--silent"]);
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* `mirror-inject.sh`: inject identity context before the model answers.
|
|
65
|
+
*
|
|
66
|
+
* Two cases, exactly as the shell had them:
|
|
67
|
+
*
|
|
68
|
+
* 1. an explicit `/mm:mirror [query]` -- load context for that query;
|
|
69
|
+
* 2. Mirror Mode already active for this session with context not yet
|
|
70
|
+
* injected -- load it with the session's persona and journey, then mark
|
|
71
|
+
* it injected so it happens once.
|
|
72
|
+
*
|
|
73
|
+
* `--context-only` matters in both: it loads context WITHOUT starting a
|
|
74
|
+
* conversation session, because the model does that. A hook that silently
|
|
75
|
+
* opened conversations would fork every session in two.
|
|
76
|
+
*/
|
|
77
|
+
export async function claudeInject(stdin: string): Promise<number> {
|
|
78
|
+
const payload = parseHookPayload(stdin);
|
|
79
|
+
const sessionId = payload.sessionId;
|
|
80
|
+
|
|
81
|
+
// Case 1: the explicit invocation.
|
|
82
|
+
if (/^\/mm:mirror/.test(payload.prompt)) {
|
|
83
|
+
const query = payload.prompt.replace(/^\/mm:mirror\s*/, "");
|
|
84
|
+
const argv = ["mirror", "load", "--context-only", "--query", query];
|
|
85
|
+
if (sessionId) argv.push("--session-id", sessionId);
|
|
86
|
+
const result = await runFrontDoor(argv);
|
|
87
|
+
if (result.stdout.trim()) process.stdout.write(result.stdout);
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Case 2: active Mirror Mode owed an injection.
|
|
92
|
+
if (!sessionId) {
|
|
93
|
+
// Python's `mirror_state` warned on stderr rather than no-op'ing silently,
|
|
94
|
+
// so a hook wired without a session id is visible. Kept: it is the
|
|
95
|
+
// difference between "Mirror Mode is off" and "this hook is misconfigured".
|
|
96
|
+
process.stderr.write("warning: mirror inject hook received no session_id; no state read.\n");
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let dbPath: string;
|
|
101
|
+
try {
|
|
102
|
+
dbPath = resolveDbPath([], process.env);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
noteHookFailure("claude:inject", error instanceof Error ? error.message : String(error));
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const state = readMirrorState(dbPath, sessionId);
|
|
109
|
+
if (!needsInject(state)) return 0;
|
|
110
|
+
|
|
111
|
+
const argv = [
|
|
112
|
+
"mirror",
|
|
113
|
+
"load",
|
|
114
|
+
"--context-only",
|
|
115
|
+
"--query",
|
|
116
|
+
payload.prompt,
|
|
117
|
+
"--session-id",
|
|
118
|
+
sessionId,
|
|
119
|
+
];
|
|
120
|
+
if (state.persona) argv.push("--persona", state.persona);
|
|
121
|
+
if (state.journey) argv.push("--journey", state.journey);
|
|
122
|
+
|
|
123
|
+
const result = await runFrontDoor(argv);
|
|
124
|
+
if (!result.stdout.trim()) return 0;
|
|
125
|
+
|
|
126
|
+
process.stdout.write(result.stdout);
|
|
127
|
+
// Only after the context actually reached the model: the shell marked it
|
|
128
|
+
// inside `if [ -n "$CONTEXT" ]` for the same reason. Marking first would
|
|
129
|
+
// lose the injection whenever the load produced nothing.
|
|
130
|
+
try {
|
|
131
|
+
// The sanctioned live-write path: a verified pre-write snapshot, same as
|
|
132
|
+
// every other routed write.
|
|
133
|
+
//
|
|
134
|
+
// Python's `mark_injected` wrote UNGATED, so this adds a `VACUUM INTO`
|
|
135
|
+
// that the old hook did not pay. Measured on a real 53 MB database:
|
|
136
|
+
// **95 ms**, once per Mirror Mode session -- only the single prompt that
|
|
137
|
+
// owes an injection reaches this line, and that turn is already loading
|
|
138
|
+
// identity and memories. The ordinary turn returns above without opening
|
|
139
|
+
// a writable handle at all (114 ms end to end, nothing written).
|
|
140
|
+
//
|
|
141
|
+
// Paying the front door's own safety contract for a real write to a real
|
|
142
|
+
// user database beat inventing a second narrowed write handle inside a
|
|
143
|
+
// deletion story. Recorded with the number so the trade is visible.
|
|
144
|
+
//
|
|
145
|
+
// This hook runs AT THE SAME TIME as `log-user-prompt`, which writes too.
|
|
146
|
+
// Until finding F21 the two snapshots collided on one fixed file and one
|
|
147
|
+
// write was lost -- in the Navigator walk, two injections in three
|
|
148
|
+
// prompts. `openLiveWriteDatabase` gives each writer its own staging
|
|
149
|
+
// snapshot, so concurrent hooks no longer race.
|
|
150
|
+
const db = openLiveWriteDatabase(dbPath);
|
|
151
|
+
try {
|
|
152
|
+
markInjected(db, sessionId, new Date().toISOString());
|
|
153
|
+
} finally {
|
|
154
|
+
db.close();
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
noteHookFailure("claude:inject", error instanceof Error ? error.message : String(error));
|
|
158
|
+
}
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function claudeHook(name: string): Promise<number> {
|
|
163
|
+
switch (name) {
|
|
164
|
+
case "session-start":
|
|
165
|
+
return claudeSessionStart();
|
|
166
|
+
case "user-prompt":
|
|
167
|
+
return claudeUserPrompt();
|
|
168
|
+
case "session-end":
|
|
169
|
+
return claudeSessionEnd();
|
|
170
|
+
case "inject":
|
|
171
|
+
return claudeInject(readStdin());
|
|
172
|
+
default:
|
|
173
|
+
noteHookFailure("claude", `unknown hook: ${name}`);
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Codex hook entries (CV22.DS10.TS5 handoff review, finding N3).
|
|
2
|
+
//
|
|
3
|
+
// Codex has no hook system, so `scripts/codex-mirror.sh` wraps the `codex`
|
|
4
|
+
// command itself and calls these through two generated wrappers under
|
|
5
|
+
// `scripts/codex-hooks/`. Until the handoff review the script called the front
|
|
6
|
+
// door directly: it found `node` on PATH alone, silenced every failure, and
|
|
7
|
+
// built its command as a string that a checkout path with a space split in
|
|
8
|
+
// two. Going through the wrappers gives it the same Node resolution and the
|
|
9
|
+
// same hooks.log record as every other runtime.
|
|
10
|
+
//
|
|
11
|
+
// The payload comes as arguments, not stdin: the wrapper, not a runtime,
|
|
12
|
+
// decides what to pass, and a path is safer as one quoted argument than
|
|
13
|
+
// embedded in hand-written JSON.
|
|
14
|
+
|
|
15
|
+
import { runFrontDoorQuietly } from "./runtime.ts";
|
|
16
|
+
|
|
17
|
+
/** Wrapper start: make sure logging is on for this session. */
|
|
18
|
+
export async function codexSessionStart(): Promise<number> {
|
|
19
|
+
await runFrontDoorQuietly("codex:session-start", ["conversation-logger", "session-start"]);
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Wrapper exit: import the session Codex wrote, close it, then take the silent
|
|
25
|
+
* backup. The transcript and its session id are both absent when Codex wrote
|
|
26
|
+
* no session for this directory; the backup runs either way, as it always has.
|
|
27
|
+
*/
|
|
28
|
+
export async function codexSessionEnd(transcriptPath: string, sessionId: string): Promise<number> {
|
|
29
|
+
if (transcriptPath && sessionId) {
|
|
30
|
+
await runFrontDoorQuietly("codex:session-end", [
|
|
31
|
+
"conversation-logger",
|
|
32
|
+
"backfill-codex-session",
|
|
33
|
+
transcriptPath,
|
|
34
|
+
"--interface",
|
|
35
|
+
"codex",
|
|
36
|
+
]);
|
|
37
|
+
await runFrontDoorQuietly("codex:session-end", [
|
|
38
|
+
"conversation-logger",
|
|
39
|
+
"session-end-pi",
|
|
40
|
+
sessionId,
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
await runFrontDoorQuietly("codex:session-end", ["backup", "--silent"]);
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// Gemini CLI hook entries (CV22.DS10.TS5 plateau 1, decision D4).
|
|
2
|
+
//
|
|
3
|
+
// These hooks already entered the TypeScript front door for every Mirror
|
|
4
|
+
// command -- the Python they carried was only inline interpreter one-liners
|
|
5
|
+
// importing `sys` and `json`, to read a field out of the payload and to print
|
|
6
|
+
// one back. Four spawns of an interpreter to parse JSON, in a Node process's
|
|
7
|
+
// hook.
|
|
8
|
+
//
|
|
9
|
+
// The Gemini contract, preserved exactly:
|
|
10
|
+
// stdin -- JSON: { session_id, prompt, ... }
|
|
11
|
+
// stdout -- JSON: { hookSpecificOutput: { additionalContext } } or {}
|
|
12
|
+
// stderr -- logs only; never parsed
|
|
13
|
+
//
|
|
14
|
+
// `GEMINI_SESSION_ID` still wins over the payload's `session_id`, because the
|
|
15
|
+
// runtime sets it and the payload is the fallback.
|
|
16
|
+
|
|
17
|
+
import { parseHookPayload } from "./payload.ts";
|
|
18
|
+
import {
|
|
19
|
+
operatingInstructionsFor,
|
|
20
|
+
runFrontDoor,
|
|
21
|
+
runFrontDoorQuietly,
|
|
22
|
+
sessionStartOutput,
|
|
23
|
+
} from "./runtime.ts";
|
|
24
|
+
|
|
25
|
+
function sessionIdFrom(payloadSessionId: string, env = process.env): string {
|
|
26
|
+
return env.GEMINI_SESSION_ID || payloadSessionId;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* BeforeAgent: log the user's turn, and inject context when Mirror Mode is on.
|
|
31
|
+
*
|
|
32
|
+
* A prompt starting with `/` is a skill invocation -- a command, not a
|
|
33
|
+
* conversational message -- and is not logged. That rule is Gemini-specific
|
|
34
|
+
* and stays; Claude's hook has no equivalent because its logger decides.
|
|
35
|
+
*/
|
|
36
|
+
export async function geminiLogUser(stdin: string): Promise<number> {
|
|
37
|
+
const payload = parseHookPayload(stdin);
|
|
38
|
+
const sessionId = sessionIdFrom(payload.sessionId);
|
|
39
|
+
|
|
40
|
+
if (payload.prompt.startsWith("/")) {
|
|
41
|
+
process.stdout.write("{}\n");
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (payload.prompt && sessionId) {
|
|
46
|
+
await runFrontDoorQuietly("gemini:log-user", [
|
|
47
|
+
"conversation-logger",
|
|
48
|
+
"log-user",
|
|
49
|
+
sessionId,
|
|
50
|
+
payload.prompt,
|
|
51
|
+
"--interface",
|
|
52
|
+
"gemini_cli",
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const context = await runFrontDoor([
|
|
57
|
+
"mirror",
|
|
58
|
+
"load",
|
|
59
|
+
"--context-only",
|
|
60
|
+
"--query",
|
|
61
|
+
payload.prompt,
|
|
62
|
+
"--session-id",
|
|
63
|
+
sessionId,
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
process.stdout.write(
|
|
67
|
+
context.stdout.trim()
|
|
68
|
+
? `${JSON.stringify({ hookSpecificOutput: { additionalContext: context.stdout } })}\n`
|
|
69
|
+
: "{}\n",
|
|
70
|
+
);
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** AfterAgent: record the assistant's turn. */
|
|
75
|
+
export async function geminiLogAssistant(stdin: string): Promise<number> {
|
|
76
|
+
const payload = parseHookPayload(stdin);
|
|
77
|
+
const sessionId = sessionIdFrom(payload.sessionId);
|
|
78
|
+
if (payload.response && sessionId) {
|
|
79
|
+
await runFrontDoorQuietly("gemini:log-assistant", [
|
|
80
|
+
"conversation-logger",
|
|
81
|
+
"log-assistant",
|
|
82
|
+
sessionId,
|
|
83
|
+
payload.response,
|
|
84
|
+
"--interface",
|
|
85
|
+
"gemini_cli",
|
|
86
|
+
]);
|
|
87
|
+
}
|
|
88
|
+
process.stdout.write("{}\n");
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** SessionEnd: close the conversation for this session. */
|
|
93
|
+
export async function geminiSessionEnd(stdin: string): Promise<number> {
|
|
94
|
+
const payload = parseHookPayload(stdin);
|
|
95
|
+
const sessionId = sessionIdFrom(payload.sessionId);
|
|
96
|
+
if (sessionId) {
|
|
97
|
+
await runFrontDoorQuietly("gemini:session-end", [
|
|
98
|
+
"conversation-logger",
|
|
99
|
+
"session-end-pi",
|
|
100
|
+
sessionId,
|
|
101
|
+
]);
|
|
102
|
+
}
|
|
103
|
+
process.stdout.write("{}\n");
|
|
104
|
+
return 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* SessionStart: unmute logging for the new session.
|
|
109
|
+
*
|
|
110
|
+
* Fires on startup, resume, and `/clear`. This wrapper was already free of
|
|
111
|
+
* Python -- it entered the front door directly -- but it is generated from the
|
|
112
|
+
* same template as the other eleven so it cannot drift away from them, which
|
|
113
|
+
* is the failure CR071 recorded for the skill copies.
|
|
114
|
+
*/
|
|
115
|
+
export async function geminiSessionStart(env: NodeJS.ProcessEnv = process.env): Promise<number> {
|
|
116
|
+
await runFrontDoorQuietly("gemini:session-start", ["conversation-logger", "session-start"]);
|
|
117
|
+
// US3, D13: the Operating Instructions as SessionStart context, unless the
|
|
118
|
+
// project is this tree -- the checkout's GEMINI/CLAUDE.md import the file.
|
|
119
|
+
process.stdout.write(
|
|
120
|
+
sessionStartOutput(
|
|
121
|
+
operatingInstructionsFor("gemini:session-start", env.GEMINI_PROJECT_DIR),
|
|
122
|
+
"{}\n",
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// The single entry point every Mirror hook wrapper calls.
|
|
4
|
+
//
|
|
5
|
+
// CV22.DS10.TS5 plateau 1, decision D4. Twelve shell scripts across four
|
|
6
|
+
// runtimes each spawned the interpreter one to five times per event. They now
|
|
7
|
+
// run one Node process and name which hook they are:
|
|
8
|
+
//
|
|
9
|
+
// node ts/src/hooks/main.ts claude:inject
|
|
10
|
+
//
|
|
11
|
+
// Arguments after the name are the hook's own; only the Codex wrapper passes
|
|
12
|
+
// any, since Codex has no hook payload to read (`codex.ts`).
|
|
13
|
+
//
|
|
14
|
+
// One entry rather than one file per hook because the generated wrappers then
|
|
15
|
+
// differ only in the hook name and their depth, and `hooks.test.ts` asserts
|
|
16
|
+
// that -- copies that can differ in only one place cannot drift the way the
|
|
17
|
+
// skill copies did (CR071, eleven skills, two runtimes wrong for months).
|
|
18
|
+
//
|
|
19
|
+
// Since CV22.DS10.US3 (D3, D5) the wrappers run this file through the
|
|
20
|
+
// `mirror-hook` bin (`bin/mirror-hook.js`), and it reads configuration and
|
|
21
|
+
// silences the sqlite warning itself, in `runAsEntry()`: the `--env-file`
|
|
22
|
+
// and `--no-warnings` flags the wrappers used to pass could only ever point
|
|
23
|
+
// at a checkout's `.env`, and an installed package has none.
|
|
24
|
+
//
|
|
25
|
+
// **A hook never fails the user's turn.** Every path returns 0 and every
|
|
26
|
+
// failure is recorded in `<mirror-home>/hooks.log` instead. That was the
|
|
27
|
+
// Python posture too (`2>/dev/null || true`), with one difference that
|
|
28
|
+
// matters: silence there hid nothing, because `python3` is on every macOS.
|
|
29
|
+
// Node is often absent from a GUI-launched runtime's PATH, so silence here
|
|
30
|
+
// would hide a NEW failure -- which is why the wrappers write the log line
|
|
31
|
+
// themselves when they cannot find a Node that runs this file. That is also
|
|
32
|
+
// why this file must exit 0: the wrappers read any other exit as Node failing.
|
|
33
|
+
|
|
34
|
+
import { pathToFileURL } from "node:url";
|
|
35
|
+
import { loadConfiguration, silenceExperimentalWarnings } from "#runtime/config.ts";
|
|
36
|
+
import { claudeHook } from "./claude.ts";
|
|
37
|
+
import { codexSessionEnd, codexSessionStart } from "./codex.ts";
|
|
38
|
+
import {
|
|
39
|
+
geminiLogAssistant,
|
|
40
|
+
geminiLogUser,
|
|
41
|
+
geminiSessionEnd,
|
|
42
|
+
geminiSessionStart,
|
|
43
|
+
} from "./gemini.ts";
|
|
44
|
+
import { readStdin } from "./payload.ts";
|
|
45
|
+
import { noteHookFailure } from "./runtime.ts";
|
|
46
|
+
|
|
47
|
+
async function dispatch(name: string, args: readonly string[]): Promise<number> {
|
|
48
|
+
const [runtime, hook] = name.split(":");
|
|
49
|
+
|
|
50
|
+
switch (runtime) {
|
|
51
|
+
case "claude":
|
|
52
|
+
return claudeHook(hook ?? "");
|
|
53
|
+
case "gemini":
|
|
54
|
+
switch (hook) {
|
|
55
|
+
case "log-user":
|
|
56
|
+
return geminiLogUser(readStdin());
|
|
57
|
+
case "log-assistant":
|
|
58
|
+
return geminiLogAssistant(readStdin());
|
|
59
|
+
case "session-end":
|
|
60
|
+
return geminiSessionEnd(readStdin());
|
|
61
|
+
case "session-start":
|
|
62
|
+
return geminiSessionStart();
|
|
63
|
+
default:
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
case "codex":
|
|
68
|
+
// Called by `scripts/codex-mirror.sh` through its generated wrappers.
|
|
69
|
+
switch (hook) {
|
|
70
|
+
case "session-start":
|
|
71
|
+
return codexSessionStart();
|
|
72
|
+
case "session-end":
|
|
73
|
+
return codexSessionEnd(args[0] ?? "", args[1] ?? "");
|
|
74
|
+
default:
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
noteHookFailure("hooks", `unknown hook name: ${name}`);
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* What the entry does, for the two ways it is started: this file run directly
|
|
88
|
+
* (a checkout), or the `bin/mirror-hook.js` shim (D15). Configuration and the
|
|
89
|
+
* warning filter run here and nowhere else, so importing this module does
|
|
90
|
+
* neither.
|
|
91
|
+
*/
|
|
92
|
+
export async function runAsEntry(rawArgv = process.argv.slice(2)): Promise<number> {
|
|
93
|
+
silenceExperimentalWarnings();
|
|
94
|
+
loadConfiguration({ entryPath: import.meta.filename });
|
|
95
|
+
const name = rawArgv[0] ?? "";
|
|
96
|
+
try {
|
|
97
|
+
return await dispatch(name, rawArgv.slice(1));
|
|
98
|
+
} catch (error) {
|
|
99
|
+
// The last line of defense. A hook that throws must still not break the
|
|
100
|
+
// turn, and must still leave a trace that it happened.
|
|
101
|
+
noteHookFailure(name || "hooks", error instanceof Error ? error.message : String(error));
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Run only when invoked as the entry, not when imported.
|
|
107
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
|
|
108
|
+
process.exitCode = await runAsEntry();
|
|
109
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Mirror Mode state for one runtime session, as the inject hook needs it.
|
|
2
|
+
//
|
|
3
|
+
// The TypeScript replacement for the Python hook module `mirror_state` (144
|
|
4
|
+
// lines, deleted with the core at CV22.DS10.TS5), which
|
|
5
|
+
// `mirror-inject.sh` invoked up to three times per prompt -- `needs-inject`,
|
|
6
|
+
// `get persona`, `get journey` -- each a separate interpreter start, each
|
|
7
|
+
// re-opening the database and re-running bootstrap.
|
|
8
|
+
//
|
|
9
|
+
// It is NOT ported as a front-door command. Its only caller was ever that one
|
|
10
|
+
// script, and the state it reads already belongs to `#mirror/runtimeSession.ts`;
|
|
11
|
+
// adding two commands whose sole consumer is a hook would widen the command
|
|
12
|
+
// surface this story exists to shrink.
|
|
13
|
+
//
|
|
14
|
+
// One Python behavior is deliberately kept, because it is a real safeguard:
|
|
15
|
+
// a missing session id FAILS LOUD on stderr rather than silently answering
|
|
16
|
+
// "nothing to do". A hook integration that forgets to pass one would otherwise
|
|
17
|
+
// look like a user who simply has Mirror Mode off.
|
|
18
|
+
|
|
19
|
+
import { openDatabaseReadOnly, type WritableDatabase } from "#db/database.ts";
|
|
20
|
+
import { getRuntimeSession, upsertRuntimeSession } from "#mirror/runtimeSession.ts";
|
|
21
|
+
|
|
22
|
+
export interface MirrorHookState {
|
|
23
|
+
readonly active: boolean;
|
|
24
|
+
readonly hookInjected: boolean;
|
|
25
|
+
readonly persona: string;
|
|
26
|
+
readonly journey: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const NO_STATE: MirrorHookState = {
|
|
30
|
+
active: false,
|
|
31
|
+
hookInjected: false,
|
|
32
|
+
persona: "",
|
|
33
|
+
journey: "",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Python `_load_state`: the session's Mirror fields, or empty when unknown. */
|
|
37
|
+
export function readMirrorState(dbPath: string, sessionId: string): MirrorHookState {
|
|
38
|
+
if (!sessionId) return NO_STATE;
|
|
39
|
+
let db: ReturnType<typeof openDatabaseReadOnly>;
|
|
40
|
+
try {
|
|
41
|
+
db = openDatabaseReadOnly(dbPath);
|
|
42
|
+
} catch {
|
|
43
|
+
return NO_STATE;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const session = getRuntimeSession(db, sessionId);
|
|
47
|
+
if (!session) return NO_STATE;
|
|
48
|
+
return {
|
|
49
|
+
active: session.mirrorActive,
|
|
50
|
+
hookInjected: session.hookInjected,
|
|
51
|
+
persona: session.persona ?? "",
|
|
52
|
+
journey: session.journey ?? "",
|
|
53
|
+
};
|
|
54
|
+
} catch {
|
|
55
|
+
return NO_STATE;
|
|
56
|
+
} finally {
|
|
57
|
+
db.close();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Python `needs_inject`: Mirror Mode is on and context has not been injected. */
|
|
62
|
+
export function needsInject(state: MirrorHookState): boolean {
|
|
63
|
+
return state.active && !state.hookInjected;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Python `mark_injected`, over an already-open writable connection. */
|
|
67
|
+
export function markInjected(db: WritableDatabase, sessionId: string, nowIso: string): void {
|
|
68
|
+
if (!sessionId) return;
|
|
69
|
+
if (!getRuntimeSession(db, sessionId)) return;
|
|
70
|
+
upsertRuntimeSession(db, sessionId, { hookInjected: true }, nowIso);
|
|
71
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Reading a runtime's hook payload from stdin.
|
|
2
|
+
//
|
|
3
|
+
// Replaces the Python hook module `extract_prompt` (25 lines) and the four
|
|
4
|
+
// inline interpreter one-liners the Claude and Gemini hooks used to parse the
|
|
5
|
+
// same JSON. None of them was a command, so none was in
|
|
6
|
+
// DS7's denominator of 29 -- which is why TS5's inventory had to find them by
|
|
7
|
+
// reading the hooks rather than by counting commands.
|
|
8
|
+
//
|
|
9
|
+
// Python's `extract_prompt` returns "" for malformed JSON rather than raising,
|
|
10
|
+
// because a hook that throws on a payload it did not recognize would break the
|
|
11
|
+
// user's turn. That posture is preserved exactly.
|
|
12
|
+
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
|
|
15
|
+
export interface HookPayload {
|
|
16
|
+
/** The user's prompt text, or "" when the payload has none. */
|
|
17
|
+
readonly prompt: string;
|
|
18
|
+
/** The runtime's session id, or "" when the payload has none. */
|
|
19
|
+
readonly sessionId: string;
|
|
20
|
+
/** The assistant's response text (Gemini's `prompt_response`), or "". */
|
|
21
|
+
readonly response: string;
|
|
22
|
+
/** Everything else, for hooks that need a field these three do not cover. */
|
|
23
|
+
readonly raw: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const EMPTY: HookPayload = { prompt: "", sessionId: "", response: "", raw: {} };
|
|
27
|
+
|
|
28
|
+
function text(value: unknown): string {
|
|
29
|
+
return typeof value === "string" ? value : "";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Parse a hook payload. Never throws: an unparseable payload is empty. */
|
|
33
|
+
export function parseHookPayload(input: string): HookPayload {
|
|
34
|
+
let parsed: unknown;
|
|
35
|
+
try {
|
|
36
|
+
parsed = JSON.parse(input);
|
|
37
|
+
} catch {
|
|
38
|
+
return EMPTY;
|
|
39
|
+
}
|
|
40
|
+
if (typeof parsed !== "object" || parsed === null) return EMPTY;
|
|
41
|
+
const raw = parsed as Record<string, unknown>;
|
|
42
|
+
return {
|
|
43
|
+
prompt: text(raw.prompt),
|
|
44
|
+
sessionId: text(raw.session_id),
|
|
45
|
+
// Gemini's AfterAgent payload names it `prompt_response`, and that is the
|
|
46
|
+
// field the Python hook read. The first port read three guessed names
|
|
47
|
+
// instead and dropped every Gemini assistant turn in silence until
|
|
48
|
+
// `smoke_gemini_cli.sh` caught it (CV22.DS10.TS5 plateau 3).
|
|
49
|
+
response: text(raw.prompt_response),
|
|
50
|
+
raw,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Read stdin to the end, synchronously.
|
|
56
|
+
*
|
|
57
|
+
* Hooks are short-lived processes whose whole job is one payload, and the
|
|
58
|
+
* runtimes close the pipe. Reading fd 0 directly avoids an async entry point
|
|
59
|
+
* for every hook, and returns "" when there is no stdin at all -- which is the
|
|
60
|
+
* case for `session-start`, invoked with no payload.
|
|
61
|
+
*/
|
|
62
|
+
export function readStdin(): string {
|
|
63
|
+
try {
|
|
64
|
+
return readFileSync(0, "utf8");
|
|
65
|
+
} catch {
|
|
66
|
+
return "";
|
|
67
|
+
}
|
|
68
|
+
}
|