mirror-mind 0.0.1 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (474) hide show
  1. package/.claude/hooks/log-assistant-reply.sh +85 -0
  2. package/.claude/hooks/log-session-end.sh +85 -0
  3. package/.claude/hooks/log-user-prompt.sh +85 -0
  4. package/.claude/hooks/mirror-inject.sh +85 -0
  5. package/.claude/hooks/session-start.sh +85 -0
  6. package/.gemini/hooks/log-assistant.sh +85 -0
  7. package/.gemini/hooks/log-user.sh +85 -0
  8. package/.gemini/hooks/session-end.sh +85 -0
  9. package/.gemini/hooks/session-start.sh +85 -0
  10. package/.pi/extensions/mirror-logger.ts +646 -0
  11. package/.pi/skills/mm-backup/SKILL.md +21 -0
  12. package/.pi/skills/mm-build/SKILL.md +1206 -0
  13. package/.pi/skills/mm-consolidate/SKILL.md +112 -0
  14. package/.pi/skills/mm-consult/SKILL.md +70 -0
  15. package/.pi/skills/mm-conversations/SKILL.md +16 -0
  16. package/.pi/skills/mm-discard/SKILL.md +39 -0
  17. package/.pi/skills/mm-explore/SKILL.md +255 -0
  18. package/.pi/skills/mm-help/SKILL.md +74 -0
  19. package/.pi/skills/mm-identity/SKILL.md +96 -0
  20. package/.pi/skills/mm-journal/SKILL.md +16 -0
  21. package/.pi/skills/mm-journey/SKILL.md +23 -0
  22. package/.pi/skills/mm-journeys/SKILL.md +15 -0
  23. package/.pi/skills/mm-memories/SKILL.md +17 -0
  24. package/.pi/skills/mm-mirror/SKILL.md +98 -0
  25. package/.pi/skills/mm-mute/SKILL.md +18 -0
  26. package/.pi/skills/mm-new/SKILL.md +18 -0
  27. package/.pi/skills/mm-recall/SKILL.md +17 -0
  28. package/.pi/skills/mm-release-notes/SKILL.md +52 -0
  29. package/.pi/skills/mm-seed/SKILL.md +19 -0
  30. package/.pi/skills/mm-shadow/SKILL.md +117 -0
  31. package/.pi/skills/mm-soul/SKILL.md +502 -0
  32. package/.pi/skills/mm-tasks/SKILL.md +24 -0
  33. package/.pi/skills/mm-update/SKILL.md +32 -0
  34. package/.pi/skills/mm-week/SKILL.md +28 -0
  35. package/.pi/skills/mm-welcome/SKILL.md +24 -0
  36. package/AGENTS.md +188 -0
  37. package/README.md +126 -9
  38. package/bin/loader.js +67 -0
  39. package/bin/mirror-hook.js +7 -0
  40. package/bin/mirror.js +7 -0
  41. package/docs/releases/index.md +100 -0
  42. package/docs/releases/pending-cutoffs.md +510 -0
  43. package/docs/releases/v0.10.0.md +123 -0
  44. package/docs/releases/v0.10.1.md +66 -0
  45. package/docs/releases/v0.10.10.md +51 -0
  46. package/docs/releases/v0.10.11.md +39 -0
  47. package/docs/releases/v0.10.12.md +57 -0
  48. package/docs/releases/v0.10.13.md +35 -0
  49. package/docs/releases/v0.10.14.md +42 -0
  50. package/docs/releases/v0.10.15.md +53 -0
  51. package/docs/releases/v0.10.16.md +35 -0
  52. package/docs/releases/v0.10.17.md +43 -0
  53. package/docs/releases/v0.10.2.md +64 -0
  54. package/docs/releases/v0.10.3.md +78 -0
  55. package/docs/releases/v0.10.4.md +42 -0
  56. package/docs/releases/v0.10.5.md +57 -0
  57. package/docs/releases/v0.10.6.md +47 -0
  58. package/docs/releases/v0.10.7.md +53 -0
  59. package/docs/releases/v0.10.8.md +49 -0
  60. package/docs/releases/v0.10.9.md +37 -0
  61. package/docs/releases/v0.11.0.md +86 -0
  62. package/docs/releases/v0.12.0.md +67 -0
  63. package/docs/releases/v0.13.0.md +64 -0
  64. package/docs/releases/v0.14.0.md +68 -0
  65. package/docs/releases/v0.15.0.md +48 -0
  66. package/docs/releases/v0.16.0.md +50 -0
  67. package/docs/releases/v0.16.1.md +55 -0
  68. package/docs/releases/v0.16.2.md +36 -0
  69. package/docs/releases/v0.17.0.md +50 -0
  70. package/docs/releases/v0.17.1.md +58 -0
  71. package/docs/releases/v0.17.2.md +42 -0
  72. package/docs/releases/v0.17.3.md +58 -0
  73. package/docs/releases/v0.18.0.md +84 -0
  74. package/docs/releases/v0.18.1.md +51 -0
  75. package/docs/releases/v0.18.2.md +69 -0
  76. package/docs/releases/v0.19.0.md +85 -0
  77. package/docs/releases/v0.19.1.md +46 -0
  78. package/docs/releases/v0.20.0.md +85 -0
  79. package/docs/releases/v0.21.0.md +61 -0
  80. package/docs/releases/v0.21.1.md +47 -0
  81. package/docs/releases/v0.22.0.md +77 -0
  82. package/docs/releases/v0.22.1.md +42 -0
  83. package/docs/releases/v0.23.0.md +105 -0
  84. package/docs/releases/v0.24.0.md +125 -0
  85. package/docs/releases/v0.25.0.md +107 -0
  86. package/docs/releases/v0.26.0.md +139 -0
  87. package/docs/releases/v0.26.1.md +65 -0
  88. package/docs/releases/v0.26.2.md +43 -0
  89. package/docs/releases/v0.27.0.md +63 -0
  90. package/docs/releases/v0.28.0.md +59 -0
  91. package/docs/releases/v0.29.0.md +61 -0
  92. package/docs/releases/v0.29.1.md +59 -0
  93. package/docs/releases/v0.29.2.md +59 -0
  94. package/docs/releases/v0.29.3.md +93 -0
  95. package/docs/releases/v0.30.0.md +75 -0
  96. package/docs/releases/v0.30.1.md +93 -0
  97. package/docs/releases/v0.30.2.md +66 -0
  98. package/docs/releases/v0.31.0.md +122 -0
  99. package/docs/releases/v0.31.1.md +142 -0
  100. package/docs/releases/v0.31.10.md +118 -0
  101. package/docs/releases/v0.31.11.md +88 -0
  102. package/docs/releases/v0.31.12.md +105 -0
  103. package/docs/releases/v0.31.13.md +99 -0
  104. package/docs/releases/v0.31.14.md +97 -0
  105. package/docs/releases/v0.31.2.md +115 -0
  106. package/docs/releases/v0.31.3.md +142 -0
  107. package/docs/releases/v0.31.4.md +117 -0
  108. package/docs/releases/v0.31.5.md +155 -0
  109. package/docs/releases/v0.31.6.md +132 -0
  110. package/docs/releases/v0.31.7.md +132 -0
  111. package/docs/releases/v0.31.8.md +120 -0
  112. package/docs/releases/v0.31.9.md +120 -0
  113. package/docs/releases/v0.32.0.md +261 -0
  114. package/docs/releases/v0.32.1.md +59 -0
  115. package/docs/releases/v0.33.0.md +164 -0
  116. package/docs/releases/v0.8.0.md +118 -0
  117. package/docs/releases/v0.9.0.md +98 -0
  118. package/docs/releases/v0.9.1.md +83 -0
  119. package/package.json +102 -6
  120. package/plugins/mirror-mind/.claude-plugin/plugin.json +17 -0
  121. package/plugins/mirror-mind/hooks/hooks.json +62 -0
  122. package/plugins/mirror-mind/hooks/log-assistant-reply.sh +98 -0
  123. package/plugins/mirror-mind/hooks/log-session-end.sh +98 -0
  124. package/plugins/mirror-mind/hooks/log-user-prompt.sh +98 -0
  125. package/plugins/mirror-mind/hooks/mirror-inject.sh +98 -0
  126. package/plugins/mirror-mind/hooks/session-start.sh +98 -0
  127. package/plugins/mirror-mind/skills/mm-backup/SKILL.md +27 -0
  128. package/plugins/mirror-mind/skills/mm-build/SKILL.md +1206 -0
  129. package/plugins/mirror-mind/skills/mm-consolidate/SKILL.md +112 -0
  130. package/plugins/mirror-mind/skills/mm-consult/SKILL.md +83 -0
  131. package/plugins/mirror-mind/skills/mm-conversations/SKILL.md +17 -0
  132. package/plugins/mirror-mind/skills/mm-discard/SKILL.md +40 -0
  133. package/plugins/mirror-mind/skills/mm-explore/SKILL.md +241 -0
  134. package/plugins/mirror-mind/skills/mm-help/SKILL.md +104 -0
  135. package/plugins/mirror-mind/skills/mm-identity/SKILL.md +96 -0
  136. package/plugins/mirror-mind/skills/mm-journal/SKILL.md +21 -0
  137. package/plugins/mirror-mind/skills/mm-journey/SKILL.md +37 -0
  138. package/plugins/mirror-mind/skills/mm-journeys/SKILL.md +15 -0
  139. package/plugins/mirror-mind/skills/mm-memories/SKILL.md +20 -0
  140. package/plugins/mirror-mind/skills/mm-mirror/SKILL.md +67 -0
  141. package/plugins/mirror-mind/skills/mm-mute/SKILL.md +18 -0
  142. package/plugins/mirror-mind/skills/mm-new/SKILL.md +18 -0
  143. package/plugins/mirror-mind/skills/mm-recall/SKILL.md +17 -0
  144. package/plugins/mirror-mind/skills/mm-release-notes/SKILL.md +31 -0
  145. package/plugins/mirror-mind/skills/mm-seed/SKILL.md +15 -0
  146. package/plugins/mirror-mind/skills/mm-shadow/SKILL.md +117 -0
  147. package/plugins/mirror-mind/skills/mm-soul/SKILL.md +488 -0
  148. package/plugins/mirror-mind/skills/mm-tasks/SKILL.md +36 -0
  149. package/plugins/mirror-mind/skills/mm-update/SKILL.md +30 -0
  150. package/plugins/mirror-mind/skills/mm-week/SKILL.md +46 -0
  151. package/plugins/mirror-mind/skills/mm-welcome/SKILL.md +22 -0
  152. package/scripts/codex-hooks/session-end.sh +85 -0
  153. package/scripts/codex-hooks/session-start.sh +85 -0
  154. package/scripts/codex-mirror.sh +48 -0
  155. package/templates/identity/README.md +119 -0
  156. package/templates/identity/ego/behavior.yaml +52 -0
  157. package/templates/identity/ego/constraints.yaml +15 -0
  158. package/templates/identity/ego/identity.yaml +43 -0
  159. package/templates/identity/journeys/personal-growth.yaml +39 -0
  160. package/templates/identity/organization/identity.yaml +24 -0
  161. package/templates/identity/organization/principles.yaml +17 -0
  162. package/templates/identity/personas/coach.yaml +94 -0
  163. package/templates/identity/personas/designer.yaml +93 -0
  164. package/templates/identity/personas/doctor.yaml +96 -0
  165. package/templates/identity/personas/engineer.yaml +77 -0
  166. package/templates/identity/personas/financial.yaml +97 -0
  167. package/templates/identity/personas/prompt-engineer.yaml +122 -0
  168. package/templates/identity/personas/researcher.yaml +88 -0
  169. package/templates/identity/personas/strategist.yaml +91 -0
  170. package/templates/identity/personas/teacher.yaml +95 -0
  171. package/templates/identity/personas/therapist.yaml +91 -0
  172. package/templates/identity/personas/thinker.yaml +74 -0
  173. package/templates/identity/personas/writer.yaml +76 -0
  174. package/templates/identity/self/config.yaml +16 -0
  175. package/templates/identity/self/soul.yaml +105 -0
  176. package/templates/identity/user/identity.yaml +30 -0
  177. package/ts/src/backup/staging.ts +62 -0
  178. package/ts/src/backup/zipBackup.ts +267 -0
  179. package/ts/src/backup/zipReader.ts +109 -0
  180. package/ts/src/backup/zipWriter.ts +148 -0
  181. package/ts/src/builder/activeCheckpoint.ts +134 -0
  182. package/ts/src/builder/aggregateStatus.ts +34 -0
  183. package/ts/src/builder/approve.ts +149 -0
  184. package/ts/src/builder/argv.ts +292 -0
  185. package/ts/src/builder/ariadMethod.ts +536 -0
  186. package/ts/src/builder/artifacts/artifactSurfaces.ts +106 -0
  187. package/ts/src/builder/artifacts/artifactWriter.ts +113 -0
  188. package/ts/src/builder/artifacts/closureArtifacts.ts +199 -0
  189. package/ts/src/builder/artifacts/deliveryStoryArtifacts.ts +316 -0
  190. package/ts/src/builder/artifacts/planArtifacts.ts +242 -0
  191. package/ts/src/builder/artifacts/scaffoldSections.ts +273 -0
  192. package/ts/src/builder/artifacts/scaffoldState.ts +227 -0
  193. package/ts/src/builder/artifacts/storyFiles.ts +61 -0
  194. package/ts/src/builder/artifacts/storyIndex.ts +166 -0
  195. package/ts/src/builder/cadence.ts +59 -0
  196. package/ts/src/builder/card.ts +192 -0
  197. package/ts/src/builder/checkpointRefused.ts +66 -0
  198. package/ts/src/builder/closure.ts +964 -0
  199. package/ts/src/builder/commands.ts +2053 -0
  200. package/ts/src/builder/cursorPosition.ts +176 -0
  201. package/ts/src/builder/cursorTransitions.ts +77 -0
  202. package/ts/src/builder/deliveryCursor.ts +577 -0
  203. package/ts/src/builder/deliveryStoryClosure.ts +521 -0
  204. package/ts/src/builder/deliveryStoryPlan.ts +536 -0
  205. package/ts/src/builder/deliveryStoryReady.ts +160 -0
  206. package/ts/src/builder/deliveryStoryRoadmapClosure.ts +151 -0
  207. package/ts/src/builder/expand.ts +336 -0
  208. package/ts/src/builder/flowUnit.ts +339 -0
  209. package/ts/src/builder/homeSurface.ts +122 -0
  210. package/ts/src/builder/implementationGuard.ts +125 -0
  211. package/ts/src/builder/index.ts +11 -0
  212. package/ts/src/builder/lifecycleRefusal.ts +92 -0
  213. package/ts/src/builder/lifecycleRibbon.ts +159 -0
  214. package/ts/src/builder/load.ts +313 -0
  215. package/ts/src/builder/methodAdoption.ts +113 -0
  216. package/ts/src/builder/methodDefinition.ts +429 -0
  217. package/ts/src/builder/methodInspection.ts +371 -0
  218. package/ts/src/builder/plan.ts +408 -0
  219. package/ts/src/builder/planPreauthorization.ts +265 -0
  220. package/ts/src/builder/prepare.ts +225 -0
  221. package/ts/src/builder/projectPaths.ts +64 -0
  222. package/ts/src/builder/pull.ts +356 -0
  223. package/ts/src/builder/pullCandidates.ts +463 -0
  224. package/ts/src/builder/pullCandidatesRender.ts +238 -0
  225. package/ts/src/builder/refinementField.ts +93 -0
  226. package/ts/src/builder/releaseIntent.ts +173 -0
  227. package/ts/src/builder/resumeState.ts +84 -0
  228. package/ts/src/builder/resumeSurface.ts +149 -0
  229. package/ts/src/builder/roadmapGrammar.ts +300 -0
  230. package/ts/src/builder/roadmapScan.ts +97 -0
  231. package/ts/src/builder/roadmapScope.ts +210 -0
  232. package/ts/src/builder/scopePhrases.ts +146 -0
  233. package/ts/src/builder/storyPaths.ts +196 -0
  234. package/ts/src/builder/storyPlanPreauthorization.ts +287 -0
  235. package/ts/src/builder/surfaceProtocol.ts +42 -0
  236. package/ts/src/builder/templateGeneration.ts +148 -0
  237. package/ts/src/builder/transition.ts +178 -0
  238. package/ts/src/consult/args.ts +99 -0
  239. package/ts/src/consult/core.ts +122 -0
  240. package/ts/src/consult/modelCatalog.ts +55 -0
  241. package/ts/src/consult/render.ts +47 -0
  242. package/ts/src/conversation/append.ts +414 -0
  243. package/ts/src/conversation/backfill.ts +387 -0
  244. package/ts/src/conversation/closeTail.ts +467 -0
  245. package/ts/src/conversation/extraction.ts +333 -0
  246. package/ts/src/conversation/extractionDriver.ts +130 -0
  247. package/ts/src/conversation/extractionRun.ts +70 -0
  248. package/ts/src/conversation/hookPayload.ts +51 -0
  249. package/ts/src/conversation/journeyInference.ts +165 -0
  250. package/ts/src/conversation/journeyRepair.ts +114 -0
  251. package/ts/src/conversation/lifecycleFaces.ts +197 -0
  252. package/ts/src/conversation/lifecycleWrites.ts +313 -0
  253. package/ts/src/conversation/listing.ts +77 -0
  254. package/ts/src/conversation/logger.ts +851 -0
  255. package/ts/src/conversation/loggerCli.ts +321 -0
  256. package/ts/src/conversation/loggerRuntime.ts +259 -0
  257. package/ts/src/conversation/metadataLifecycle.ts +443 -0
  258. package/ts/src/conversation/recall.ts +66 -0
  259. package/ts/src/conversation/sessionComposites.ts +258 -0
  260. package/ts/src/conversation/sessionImport.ts +90 -0
  261. package/ts/src/conversation/transcriptBackfill.ts +141 -0
  262. package/ts/src/cultivation/applyActions.ts +241 -0
  263. package/ts/src/cultivation/cluster.ts +86 -0
  264. package/ts/src/cultivation/consolidationStore.ts +281 -0
  265. package/ts/src/cultivation/promptContext.ts +62 -0
  266. package/ts/src/cultivation/propose.ts +305 -0
  267. package/ts/src/cultivation/scan.ts +155 -0
  268. package/ts/src/db/backupGate.ts +47 -0
  269. package/ts/src/db/bootstrap.ts +108 -0
  270. package/ts/src/db/bootstrapLock.ts +223 -0
  271. package/ts/src/db/copyGuard.ts +60 -0
  272. package/ts/src/db/database.ts +397 -0
  273. package/ts/src/db/decode.ts +66 -0
  274. package/ts/src/db/ftsIntegrity.ts +41 -0
  275. package/ts/src/db/migrateOnOpen.ts +214 -0
  276. package/ts/src/db/migrations.ts +576 -0
  277. package/ts/src/db/readyOnOpen.ts +35 -0
  278. package/ts/src/db/rowDecode.ts +42 -0
  279. package/ts/src/db/schema.ts +432 -0
  280. package/ts/src/db/schemaInventory.ts +225 -0
  281. package/ts/src/db/schemaInventorySnapshot.ts +2107 -0
  282. package/ts/src/db/schemaState.ts +91 -0
  283. package/ts/src/db/schemaTsDivergence.ts +114 -0
  284. package/ts/src/descriptor/descriptorRead.ts +49 -0
  285. package/ts/src/docs/docsLint.ts +265 -0
  286. package/ts/src/explorer/handoff.ts +522 -0
  287. package/ts/src/explorer/render.ts +335 -0
  288. package/ts/src/explorer/story.ts +769 -0
  289. package/ts/src/explorer/transition.ts +72 -0
  290. package/ts/src/extensions/bindings.ts +170 -0
  291. package/ts/src/extensions/catalog.ts +249 -0
  292. package/ts/src/extensions/catalogCommands.ts +513 -0
  293. package/ts/src/extensions/catalogWrites.ts +652 -0
  294. package/ts/src/extensions/contextRuntime.ts +325 -0
  295. package/ts/src/extensions/dispatch.ts +374 -0
  296. package/ts/src/extensions/errors.ts +24 -0
  297. package/ts/src/extensions/manifest.ts +356 -0
  298. package/ts/src/extensions/migrations.ts +350 -0
  299. package/ts/src/extraction/conversation.ts +304 -0
  300. package/ts/src/extraction/conversationMetadata.ts +206 -0
  301. package/ts/src/extraction/fencing.ts +106 -0
  302. package/ts/src/extraction/json.ts +17 -0
  303. package/ts/src/extraction/prompts.ts +470 -0
  304. package/ts/src/frontDoor/args.ts +30 -0
  305. package/ts/src/frontDoor/argvShape.ts +106 -0
  306. package/ts/src/frontDoor/buildLoadRuntime.ts +139 -0
  307. package/ts/src/frontDoor/buildRoute.ts +398 -0
  308. package/ts/src/frontDoor/cli.ts +1872 -0
  309. package/ts/src/frontDoor/cloneRoleGuard.ts +98 -0
  310. package/ts/src/frontDoor/consultRoute.ts +50 -0
  311. package/ts/src/frontDoor/contentTailRoute.ts +239 -0
  312. package/ts/src/frontDoor/conversationLoggerRoute.ts +83 -0
  313. package/ts/src/frontDoor/cultivationRoute.ts +210 -0
  314. package/ts/src/frontDoor/dbPath.ts +131 -0
  315. package/ts/src/frontDoor/dbSafetyToolsRoute.ts +240 -0
  316. package/ts/src/frontDoor/exploreRoute.ts +572 -0
  317. package/ts/src/frontDoor/extensionCatalogRoute.ts +252 -0
  318. package/ts/src/frontDoor/frontDoorLog.ts +57 -0
  319. package/ts/src/frontDoor/identityWrite.ts +36 -0
  320. package/ts/src/frontDoor/journeyWriteRoute.ts +26 -0
  321. package/ts/src/frontDoor/lifecycleWriteRoute.ts +110 -0
  322. package/ts/src/frontDoor/liveBackup.ts +100 -0
  323. package/ts/src/frontDoor/mirrorModeRoute.ts +163 -0
  324. package/ts/src/frontDoor/namedRefusal.ts +35 -0
  325. package/ts/src/frontDoor/nodeSupport.ts +24 -0
  326. package/ts/src/frontDoor/render/consolidate.ts +199 -0
  327. package/ts/src/frontDoor/render/conversations.ts +33 -0
  328. package/ts/src/frontDoor/render/cultivationShared.ts +21 -0
  329. package/ts/src/frontDoor/render/descriptor.ts +20 -0
  330. package/ts/src/frontDoor/render/detectPersona.ts +50 -0
  331. package/ts/src/frontDoor/render/icons.ts +13 -0
  332. package/ts/src/frontDoor/render/identity.ts +59 -0
  333. package/ts/src/frontDoor/render/identityRows.ts +17 -0
  334. package/ts/src/frontDoor/render/inspectPersona.ts +53 -0
  335. package/ts/src/frontDoor/render/journeyStatus.ts +78 -0
  336. package/ts/src/frontDoor/render/journeys.ts +79 -0
  337. package/ts/src/frontDoor/render/list.ts +41 -0
  338. package/ts/src/frontDoor/render/memories.ts +67 -0
  339. package/ts/src/frontDoor/render/recall.ts +49 -0
  340. package/ts/src/frontDoor/render/shadow.ts +155 -0
  341. package/ts/src/frontDoor/render/tasks.ts +99 -0
  342. package/ts/src/frontDoor/render/tasksImportSync.ts +63 -0
  343. package/ts/src/frontDoor/render/week.ts +113 -0
  344. package/ts/src/frontDoor/routing.ts +1001 -0
  345. package/ts/src/frontDoor/runtimeRoute.ts +623 -0
  346. package/ts/src/frontDoor/searchRoute.ts +166 -0
  347. package/ts/src/frontDoor/seedPaths.ts +98 -0
  348. package/ts/src/frontDoor/sessionStartWelcomeRoute.ts +166 -0
  349. package/ts/src/frontDoor/soulRoute.ts +433 -0
  350. package/ts/src/frontDoor/stdinSource.ts +37 -0
  351. package/ts/src/frontDoor/tasksImportSyncRoute.ts +110 -0
  352. package/ts/src/frontDoor/tasksWriteRoute.ts +101 -0
  353. package/ts/src/frontDoor/usage.ts +176 -0
  354. package/ts/src/guards/claudePlugin.ts +298 -0
  355. package/ts/src/guards/packContents.ts +306 -0
  356. package/ts/src/guards/piSourcedSkills.ts +70 -0
  357. package/ts/src/guards/retiredRouteShadows.ts +57 -0
  358. package/ts/src/guards/retiredSurfaces.ts +564 -0
  359. package/ts/src/hooks/claude.ts +214 -0
  360. package/ts/src/hooks/codex.ts +45 -0
  361. package/ts/src/hooks/gemini.ts +126 -0
  362. package/ts/src/hooks/main.ts +109 -0
  363. package/ts/src/hooks/mirrorState.ts +71 -0
  364. package/ts/src/hooks/payload.ts +73 -0
  365. package/ts/src/hooks/runtime.ts +179 -0
  366. package/ts/src/hooks/runtimeDetection.ts +34 -0
  367. package/ts/src/identity/applyConsolidationIdentityUpdate.ts +67 -0
  368. package/ts/src/identity/descriptorGenerate.ts +162 -0
  369. package/ts/src/identity/identityEdit.ts +143 -0
  370. package/ts/src/identity/identityKey.ts +77 -0
  371. package/ts/src/identity/identityRead.ts +69 -0
  372. package/ts/src/identity/identityStore.ts +80 -0
  373. package/ts/src/identity/journeyListing.ts +53 -0
  374. package/ts/src/identity/personaInspect.ts +44 -0
  375. package/ts/src/identity/personaListing.ts +37 -0
  376. package/ts/src/identity/setIdentity.ts +64 -0
  377. package/ts/src/index.ts +231 -0
  378. package/ts/src/init/init.ts +111 -0
  379. package/ts/src/journey/journeyOptions.ts +131 -0
  380. package/ts/src/journey/journeyRemoval.ts +158 -0
  381. package/ts/src/journey/journeyStatus.ts +149 -0
  382. package/ts/src/journey/journeySyncFile.ts +87 -0
  383. package/ts/src/journey/journeyWrite.ts +203 -0
  384. package/ts/src/journey/parentJourney.ts +41 -0
  385. package/ts/src/journey/validateParentJourney.ts +55 -0
  386. package/ts/src/mcp/boundary.ts +151 -0
  387. package/ts/src/mcp/guards.ts +244 -0
  388. package/ts/src/mcp/main.ts +121 -0
  389. package/ts/src/mcp/payload.ts +141 -0
  390. package/ts/src/mcp/protocol.ts +141 -0
  391. package/ts/src/mcp/readModels.ts +241 -0
  392. package/ts/src/mcp/registry.ts +201 -0
  393. package/ts/src/mcp/serve.ts +120 -0
  394. package/ts/src/mcp/tools/deterministic.ts +137 -0
  395. package/ts/src/mcp/tools/providerCrossing.ts +187 -0
  396. package/ts/src/mcp/wire.ts +8 -0
  397. package/ts/src/memory/journal.ts +180 -0
  398. package/ts/src/memory/listing.ts +107 -0
  399. package/ts/src/memory/memoryWrite.ts +88 -0
  400. package/ts/src/memory/reinforcement.ts +35 -0
  401. package/ts/src/mirror/context.ts +161 -0
  402. package/ts/src/mirror/defaultResolution.ts +199 -0
  403. package/ts/src/mirror/mirrorForm.ts +43 -0
  404. package/ts/src/mirror/orchestration.ts +364 -0
  405. package/ts/src/mirror/reception.ts +194 -0
  406. package/ts/src/mirror/render.ts +75 -0
  407. package/ts/src/mirror/runtimeSession.ts +205 -0
  408. package/ts/src/mode/operatingMode.ts +148 -0
  409. package/ts/src/observability/callOutcome.ts +115 -0
  410. package/ts/src/observability/inspectLedger.ts +329 -0
  411. package/ts/src/observability/ledgerHooks.ts +126 -0
  412. package/ts/src/observability/llmCalls.ts +154 -0
  413. package/ts/src/persona/detectPersona.ts +81 -0
  414. package/ts/src/planning/promptAssembly.ts +111 -0
  415. package/ts/src/planning/weekPending.ts +81 -0
  416. package/ts/src/planning/weekPlan.ts +183 -0
  417. package/ts/src/planning/weekSave.ts +117 -0
  418. package/ts/src/providers/config.ts +189 -0
  419. package/ts/src/providers/cost.ts +64 -0
  420. package/ts/src/providers/credits.ts +204 -0
  421. package/ts/src/providers/embedding.ts +321 -0
  422. package/ts/src/providers/familyProviders.ts +147 -0
  423. package/ts/src/providers/llm.ts +334 -0
  424. package/ts/src/providers/openrouter.ts +244 -0
  425. package/ts/src/providers/redaction.ts +110 -0
  426. package/ts/src/providers/replay.ts +15 -0
  427. package/ts/src/providers/transport.ts +410 -0
  428. package/ts/src/repair/encodingRepair.ts +253 -0
  429. package/ts/src/runtime/backup.ts +151 -0
  430. package/ts/src/runtime/channel.ts +146 -0
  431. package/ts/src/runtime/config.ts +168 -0
  432. package/ts/src/runtime/diagnose.ts +686 -0
  433. package/ts/src/runtime/git.ts +510 -0
  434. package/ts/src/runtime/installKind.ts +220 -0
  435. package/ts/src/runtime/migrate.ts +154 -0
  436. package/ts/src/runtime/packageIdentity.ts +102 -0
  437. package/ts/src/runtime/releaseNotes.ts +244 -0
  438. package/ts/src/runtime/status.ts +557 -0
  439. package/ts/src/runtime/strategies/clone.ts +200 -0
  440. package/ts/src/runtime/strategies/package.ts +328 -0
  441. package/ts/src/runtime/treeRoot.ts +30 -0
  442. package/ts/src/runtime/update.ts +445 -0
  443. package/ts/src/runtime/updateGate.ts +40 -0
  444. package/ts/src/runtime/updatePipeline.ts +159 -0
  445. package/ts/src/runtime/version.ts +36 -0
  446. package/ts/src/runtime/wiring.ts +130 -0
  447. package/ts/src/search/memorySearch.ts +338 -0
  448. package/ts/src/search/ranker.ts +154 -0
  449. package/ts/src/seed/seed.ts +256 -0
  450. package/ts/src/seed/seedYaml.ts +133 -0
  451. package/ts/src/soul/apply.ts +218 -0
  452. package/ts/src/soul/harvest.ts +267 -0
  453. package/ts/src/soul/prompts/soul_beauty_voice.md +103 -0
  454. package/ts/src/soul/prompts/soul_self_voice.md +219 -0
  455. package/ts/src/soul/prompts/soul_wisdom_voice.md +121 -0
  456. package/ts/src/soul/prompts.ts +85 -0
  457. package/ts/src/soul/render.ts +358 -0
  458. package/ts/src/soul/state.ts +212 -0
  459. package/ts/src/soul/transition.ts +37 -0
  460. package/ts/src/tasks/journeyPathParse.ts +113 -0
  461. package/ts/src/tasks/taskImportSync.ts +142 -0
  462. package/ts/src/tasks/taskStore.ts +312 -0
  463. package/ts/src/tasks/weekView.ts +120 -0
  464. package/ts/src/util/clipCodePoints.ts +26 -0
  465. package/ts/src/util/paths.ts +63 -0
  466. package/ts/src/util/program.ts +9 -0
  467. package/ts/src/util/pyGenerators.ts +144 -0
  468. package/ts/src/util/pythonText.ts +317 -0
  469. package/ts/src/util/semver.ts +45 -0
  470. package/ts/src/util/shellWord.ts +25 -0
  471. package/ts/src/util/slug.ts +64 -0
  472. package/ts/src/welcome/card.ts +339 -0
  473. package/ts/src/welcome/statusLine.ts +101 -0
  474. package/ts/src/welcome/updateCache.ts +129 -0
@@ -0,0 +1,364 @@
1
+ import type { Database, WritableDatabase } from "#db/database.ts";
2
+ import {
3
+ type CollectExtensionContextOptions,
4
+ collectExtensionContext,
5
+ type ExtensionContextDiagnostic,
6
+ } from "#extensions/contextRuntime.ts";
7
+ import { activateOperatingMode } from "#mode/operatingMode.ts";
8
+ import type { OnProviderCallOutcome } from "#observability/callOutcome.ts";
9
+ import { logLlmCall } from "#observability/llmCalls.ts";
10
+ import { computeCost } from "#providers/cost.ts";
11
+ import type { EmbeddingProvider } from "#providers/embedding.ts";
12
+ import type { LlmProvider } from "#providers/llm.ts";
13
+ import { loadMirrorContext } from "./context.ts";
14
+ import { type ResolvedMirrorDefaults, resolveMirrorDefaults } from "./defaultResolution.ts";
15
+ import { pythonTruthy, sliceCodePoints } from "./reception.ts";
16
+ import {
17
+ renderJourneyDetected,
18
+ renderMirrorBanner,
19
+ renderMirrorJourneys,
20
+ renderMirrorModeTransition,
21
+ } from "./render.ts";
22
+ import {
23
+ decodeMetadata,
24
+ GLOBAL_STICKY_DEFAULTS_SESSION_ID,
25
+ getRuntimeSession,
26
+ resolveRuntimeSessionId,
27
+ upsertRuntimeSession,
28
+ } from "./runtimeSession.ts";
29
+
30
+ export interface MirrorLoadInput {
31
+ identity: string;
32
+ persona?: string | null;
33
+ journey?: string | null;
34
+ query?: string | null;
35
+ org?: boolean;
36
+ contextOnly?: boolean;
37
+ sessionId?: string | null;
38
+ environmentSessionId?: string | null;
39
+ receptionEnabled: boolean;
40
+ /**
41
+ * Reception's outcome, for the front-door log. `mirror load --query` is the
42
+ * per-activation surface: it swallows every failure into an empty context,
43
+ * so without this a provider outage, a prompt-layer parse failure, and a
44
+ * genuinely unclassifiable message all look like "no persona routed".
45
+ */
46
+ onReceptionOutcome?: OnProviderCallOutcome;
47
+ llmProvider?: LlmProvider;
48
+ embeddingProvider?: EmbeddingProvider;
49
+ databasePath?: string;
50
+ mirrorHome?: string;
51
+ user?: string;
52
+ extensionRuntime?: Pick<
53
+ CollectExtensionContextOptions,
54
+ "timeoutMs" | "maxOutputBytes" | "environment"
55
+ >;
56
+ newId: () => string;
57
+ nowIso: () => string;
58
+ }
59
+
60
+ export interface RenderedMirrorLoad {
61
+ stdout: string;
62
+ stderr: string;
63
+ resolved: ResolvedMirrorDefaults;
64
+ extensionDiagnostics: ExtensionContextDiagnostic[];
65
+ }
66
+
67
+ export async function runMirrorLoad(
68
+ db: WritableDatabase,
69
+ input: MirrorLoadInput,
70
+ ): Promise<RenderedMirrorLoad> {
71
+ const resolved = await resolveMirrorDefaults(db, {
72
+ ...input,
73
+ onReceptionOutcome: input.onReceptionOutcome,
74
+ // Priced, as Python's `build_llm_logger(role="reception")` prices it
75
+ // (CV22.DS8.US3). The row existed before but carried a null cost, so
76
+ // every Mirror Mode activation with a query would have logged live spend
77
+ // as free.
78
+ onReceptionLlmCall: (response, prompt) =>
79
+ logLlmCall(
80
+ db,
81
+ {
82
+ role: "reception",
83
+ model: response.model ?? "unknown",
84
+ prompt,
85
+ response: response.content,
86
+ promptTokens: response.promptTokens,
87
+ completionTokens: response.completionTokens,
88
+ latencyMs: response.latencyMs,
89
+ costUsd: computeCost(
90
+ response.model ?? "unknown",
91
+ response.promptTokens ?? null,
92
+ response.completionTokens ?? null,
93
+ ),
94
+ sessionId: input.sessionId ?? null,
95
+ },
96
+ { id: input.newId, now: input.nowIso },
97
+ ),
98
+ });
99
+ const extensionContext =
100
+ input.databasePath && input.mirrorHome
101
+ ? collectExtensionContext(db, {
102
+ mirrorHome: input.mirrorHome,
103
+ databasePath: input.databasePath,
104
+ personaId: resolved.persona,
105
+ journeyId: resolved.journey,
106
+ user: input.user ?? "",
107
+ query: input.query,
108
+ ...input.extensionRuntime,
109
+ })
110
+ : { rendered: "", diagnostics: [] };
111
+ const context = await loadMirrorContext(db, {
112
+ persona: resolved.persona,
113
+ journey: resolved.journey,
114
+ org: input.org,
115
+ query: input.query,
116
+ touchesIdentity: resolved.touchesIdentity,
117
+ touchesShadow: resolved.touchesShadow,
118
+ embeddingProvider: input.embeddingProvider,
119
+ extensionContext: extensionContext.rendered,
120
+ });
121
+ persistStickyDefaults(db, resolved.persona, resolved.journey, input.nowIso());
122
+ activateOperatingMode(db, { mode: "Mirror Mode", journey: resolved.journey }, input.nowIso());
123
+ writeMirrorState(
124
+ db,
125
+ {
126
+ active: true,
127
+ persona: resolved.persona,
128
+ journey: resolved.journey,
129
+ sessionId: input.sessionId ?? null,
130
+ },
131
+ input.nowIso(),
132
+ );
133
+ if (!input.contextOnly) {
134
+ bindConversationContext(
135
+ db,
136
+ input.sessionId ?? null,
137
+ input.environmentSessionId ?? null,
138
+ resolved.persona,
139
+ resolved.journey,
140
+ input.newId,
141
+ input.nowIso,
142
+ );
143
+ }
144
+ const personas = db
145
+ .prepare("SELECT key FROM identity WHERE layer = 'persona' ORDER BY key")
146
+ .all()
147
+ .map((row) => String(row.key));
148
+ const transition = renderMirrorModeTransition({
149
+ identity: input.identity,
150
+ journey: resolved.journey,
151
+ personas,
152
+ });
153
+ const detected = resolved.detectedJourney?.[0];
154
+ return {
155
+ stdout: `${transition}\n${context}\n`,
156
+ stderr: `${detected ? renderJourneyDetected(detected) : ""}${renderMirrorBanner(resolved.persona)}`,
157
+ resolved,
158
+ extensionDiagnostics: extensionContext.diagnostics,
159
+ };
160
+ }
161
+
162
+ export function deactivateMirrorState(
163
+ db: WritableDatabase,
164
+ sessionId: string,
165
+ nowIso: string,
166
+ ): void {
167
+ writeMirrorState(db, { active: false, sessionId }, nowIso);
168
+ }
169
+
170
+ export function logMirrorResponse(
171
+ db: WritableDatabase,
172
+ input: {
173
+ summary: string;
174
+ sessionId?: string | null;
175
+ environmentSessionId?: string | null;
176
+ muted: boolean;
177
+ newId: () => string;
178
+ nowIso: () => string;
179
+ },
180
+ ): void {
181
+ if (input.muted) return;
182
+ const sessionId = resolveRuntimeSessionId(
183
+ db,
184
+ input.sessionId ?? null,
185
+ input.environmentSessionId ?? null,
186
+ );
187
+ if (!sessionId) return;
188
+ const session = getRuntimeSession(db, sessionId);
189
+ const discarded = pythonTruthy(
190
+ decodeMetadata(session?.metadata ?? null).discard_current_conversation,
191
+ );
192
+ const conversationId = discarded
193
+ ? session?.conversationId &&
194
+ db.prepare("SELECT 1 FROM conversations WHERE id = ?").get(session.conversationId)
195
+ ? session.conversationId
196
+ : null
197
+ : getOrCreateConversation(db, sessionId, input.newId, input.nowIso);
198
+ if (!conversationId) return;
199
+ if (!discarded) {
200
+ db.prepare(
201
+ `INSERT INTO messages (id, conversation_id, role, content, created_at, token_count, metadata)
202
+ VALUES (?, ?, 'assistant', ?, ?, NULL, NULL)`,
203
+ ).run(input.newId(), conversationId, input.summary, input.nowIso());
204
+ }
205
+ db.prepare("UPDATE conversations SET title = ? WHERE id = ?").run(
206
+ titleFromSummary(input.summary),
207
+ conversationId,
208
+ );
209
+ }
210
+
211
+ export function listActiveMirrorJourneys(db: Database): string {
212
+ const rows = db
213
+ .prepare("SELECT key, content FROM identity WHERE layer = 'journey' ORDER BY key")
214
+ .all()
215
+ .map((row) => journeyDto(String(row.key), typeof row.content === "string" ? row.content : ""))
216
+ .filter((row) => row.status === "active");
217
+ return renderMirrorJourneys(rows);
218
+ }
219
+
220
+ export function titleFromSummary(summary: string): string {
221
+ let title = summary.split(/[.!?]/, 1)[0]?.trim() ?? "";
222
+ if ([...title].length > 60) {
223
+ const prefix = [...title].slice(0, 60).join("");
224
+ const space = prefix.lastIndexOf(" ");
225
+ title = `${space >= 0 ? prefix.slice(0, space) : prefix}...`;
226
+ }
227
+ return title;
228
+ }
229
+
230
+ /**
231
+ * Port of `_persist_global_sticky_defaults`. Exported for `soul load`, which
232
+ * calls the same writer with `persona=None` (CV22.DS7.US6).
233
+ */
234
+ export function persistStickyDefaults(
235
+ db: WritableDatabase,
236
+ persona: string | null,
237
+ journey: string | null,
238
+ nowIso: string,
239
+ ): void {
240
+ if (persona === null && journey === null) return;
241
+ upsertRuntimeSession(
242
+ db,
243
+ GLOBAL_STICKY_DEFAULTS_SESSION_ID,
244
+ {
245
+ interface: "global_defaults",
246
+ mirrorActive: false,
247
+ ...(persona !== null ? { persona } : {}),
248
+ ...(journey !== null ? { journey } : {}),
249
+ hookInjected: true,
250
+ active: false,
251
+ },
252
+ nowIso,
253
+ );
254
+ }
255
+
256
+ function writeMirrorState(
257
+ db: WritableDatabase,
258
+ input: {
259
+ active: boolean;
260
+ persona?: string | null;
261
+ journey?: string | null;
262
+ sessionId: string | null;
263
+ },
264
+ nowIso: string,
265
+ ): void {
266
+ if (!input.sessionId) return;
267
+ upsertRuntimeSession(
268
+ db,
269
+ input.sessionId,
270
+ {
271
+ mirrorActive: input.active,
272
+ ...(input.persona !== undefined && input.persona !== null ? { persona: input.persona } : {}),
273
+ ...(input.journey !== undefined && input.journey !== null ? { journey: input.journey } : {}),
274
+ hookInjected: !input.active,
275
+ },
276
+ nowIso,
277
+ );
278
+ }
279
+
280
+ function bindConversationContext(
281
+ db: WritableDatabase,
282
+ explicitSessionId: string | null,
283
+ environmentSessionId: string | null,
284
+ persona: string | null,
285
+ journey: string | null,
286
+ newId: () => string,
287
+ nowIso: () => string,
288
+ ): string | null {
289
+ const sessionId = resolveRuntimeSessionId(db, explicitSessionId, environmentSessionId);
290
+ if (!sessionId) return null;
291
+ const session = getRuntimeSession(db, sessionId);
292
+ const existing = session?.conversationId
293
+ ? db.prepare("SELECT id, ended_at FROM conversations WHERE id = ?").get(session.conversationId)
294
+ : undefined;
295
+ let conversationId: string;
296
+ if (existing && existing.ended_at === null) {
297
+ conversationId = String(existing.id);
298
+ db.prepare("UPDATE conversations SET persona = ?, journey = ? WHERE id = ?").run(
299
+ persona,
300
+ journey,
301
+ conversationId,
302
+ );
303
+ } else {
304
+ conversationId = newId();
305
+ db.prepare(
306
+ `INSERT INTO conversations
307
+ (id, title, started_at, ended_at, interface, persona, journey, summary, tags, metadata)
308
+ VALUES (?, NULL, ?, NULL, ?, ?, ?, NULL, NULL, NULL)`,
309
+ ).run(conversationId, nowIso(), session?.interface ?? "claude_code", persona, journey);
310
+ }
311
+ upsertRuntimeSession(
312
+ db,
313
+ sessionId,
314
+ {
315
+ conversationId,
316
+ interface: session?.interface ?? "claude_code",
317
+ ...(persona !== null ? { persona } : {}),
318
+ ...(journey !== null ? { journey } : {}),
319
+ active: true,
320
+ closedAt: null,
321
+ },
322
+ nowIso(),
323
+ );
324
+ return conversationId;
325
+ }
326
+
327
+ function getOrCreateConversation(
328
+ db: WritableDatabase,
329
+ sessionId: string,
330
+ newId: () => string,
331
+ nowIso: () => string,
332
+ ): string {
333
+ const session = getRuntimeSession(db, sessionId);
334
+ if (session?.conversationId) {
335
+ const existing = db
336
+ .prepare("SELECT id FROM conversations WHERE id = ?")
337
+ .get(session.conversationId);
338
+ if (existing) return String(existing.id);
339
+ }
340
+ const id = newId();
341
+ const now = nowIso();
342
+ const interfaceName = session?.interface ?? "claude_code";
343
+ db.prepare(
344
+ `INSERT INTO conversations
345
+ (id, title, started_at, ended_at, interface, persona, journey, summary, tags, metadata)
346
+ VALUES (?, NULL, ?, NULL, ?, ?, ?, NULL, NULL, NULL)`,
347
+ ).run(id, now, interfaceName, session?.persona ?? null, session?.journey ?? null);
348
+ upsertRuntimeSession(
349
+ db,
350
+ sessionId,
351
+ { conversationId: id, interface: interfaceName, active: true, closedAt: null },
352
+ now,
353
+ );
354
+ return id;
355
+ }
356
+
357
+ function journeyDto(id: string, content: string) {
358
+ const name = (content.split("\n")[0] ?? "").trim().replace(/^[# ]+/, "");
359
+ const status = content.match(/\*\*Status:\*\*\s*([\p{L}\p{N}_]+)/u)?.[1] ?? "unknown";
360
+ const description =
361
+ content.match(/## (?:Description|Descrição)\s*\n+(.+?)(?:\n\n|\n##)/s)?.[1]?.trim() ?? "";
362
+ const truncatedDescription = sliceCodePoints(description, 150);
363
+ return { id, name, description: truncatedDescription, status };
364
+ }
@@ -0,0 +1,194 @@
1
+ import { parseJsonResponse } from "#extraction/json.ts";
2
+ import { classifyProviderError, type OnProviderCallOutcome } from "#observability/callOutcome.ts";
3
+ import type { LlmProvider, LlmResponse } from "#providers/llm.ts";
4
+ import { pyFormat } from "#util/pythonText.ts";
5
+
6
+ export interface ReceptionPersona {
7
+ slug: string;
8
+ description: string;
9
+ routingKeywords: readonly string[];
10
+ }
11
+
12
+ export interface ReceptionJourney {
13
+ slug: string;
14
+ description: string;
15
+ }
16
+
17
+ export interface ReceptionResult {
18
+ personas: string[];
19
+ journey: string | null;
20
+ touchesIdentity: boolean;
21
+ touchesShadow: boolean;
22
+ }
23
+
24
+ export const EMPTY_RECEPTION_RESULT: ReceptionResult = Object.freeze({
25
+ personas: [],
26
+ journey: null,
27
+ touchesIdentity: false,
28
+ touchesShadow: false,
29
+ });
30
+
31
+ /**
32
+ * Byte-identical to Python's `RECEPTION_PROMPT`, doubled braces included.
33
+ *
34
+ * The braces are not cosmetic. Python substitutes through `str.format`, which
35
+ * requires `{{` to emit a literal `{`, so the JSON example in the response
36
+ * format section is doubled at the source. Holding the same bytes here means
37
+ * the template can be compared to the oracle directly, and it forces assembly
38
+ * through `pyFormat` -- which is the point: `String.replace()` with a string
39
+ * pattern treats `$&`, `` $` ``, `$'`, and `$1` in the REPLACEMENT as
40
+ * substitution directives, so a persona described as "Cost: $& per hour"
41
+ * assembled as "Cost: {personas} per hour" (CV22.DS8.US3). Python's `format`
42
+ * has no such behavior, no fixture ever carried a `$` pattern, and replay
43
+ * never reads a prompt -- so only a live call would have shown it.
44
+ */
45
+ export const RECEPTION_PROMPT = `You are the reception classifier for Mirror Mind, a Jungian mirror AI.
46
+
47
+ Your job is to classify a single user message on four axes so the mirror can
48
+ compose the right context for its response. Read the message carefully, then
49
+ return a JSON object — nothing else.
50
+
51
+ ## Available personas
52
+ {personas}
53
+
54
+ ## Active journeys
55
+ {journeys}
56
+
57
+ ## Classification rules
58
+
59
+ **personas** (array of slugs, ordered most-to-least relevant, or [] if none apply)
60
+ - Return the personas whose domain clearly covers this message.
61
+ - Action verbs dominate topic: "write a post about X" → writer, not the X-domain persona.
62
+ - When a single persona's domain covers the message, return only that one.
63
+ - When genuinely ambiguous, return the most relevant one only.
64
+ - Return [] when the ego should answer alone (open questions, general curiosity,
65
+ meta questions about the mirror itself).
66
+
67
+ **journey** (slug string or null)
68
+ - Return the slug whose description best matches the context of this message.
69
+ - Conservative: prefer null over a speculative match.
70
+ - Return null if no journey is clearly relevant.
71
+
72
+ **touches_identity** (boolean)
73
+ - true ONLY when the message explicitly invites reflection on personal values,
74
+ life purpose, meaning, or deep self-examination.
75
+ - Operational and technical questions are false even if they involve important decisions.
76
+ - Default false. The cost of missing a touch is a lighter context load;
77
+ the cost of over-triggering is token waste on every routine turn.
78
+
79
+ **touches_shadow** (boolean)
80
+ - true ONLY when there is explicit evidence of avoidance, internal contradiction,
81
+ or a recurring pattern the user is naming or circling around.
82
+ - Requires positive signal. Vague discomfort or uncertainty alone is false.
83
+ - Default false. Conservative by design.
84
+
85
+ ## Response format
86
+ Return ONLY a JSON object, no markdown:
87
+ {{
88
+ "personas": ["slug", ...],
89
+ "journey": "slug" or null,
90
+ "touches_identity": true or false,
91
+ "touches_shadow": true or false
92
+ }}
93
+
94
+ ## User message
95
+ `;
96
+
97
+ export async function runReception(
98
+ query: string,
99
+ personas: readonly ReceptionPersona[],
100
+ journeys: readonly ReceptionJourney[],
101
+ provider: LlmProvider,
102
+ onLlmCall?: (response: LlmResponse, prompt: string) => void,
103
+ onOutcome?: OnProviderCallOutcome,
104
+ ): Promise<ReceptionResult> {
105
+ if (!query.trim()) return { ...EMPTY_RECEPTION_RESULT, personas: [] };
106
+ const prompt = buildReceptionPrompt(query, personas, journeys);
107
+ try {
108
+ const response = await provider.complete({ role: "reception", prompt, temperature: 0.1 });
109
+ onLlmCall?.(response, prompt);
110
+ const parsed = parseJsonResponse(response.content);
111
+ if (!isRecord(parsed)) {
112
+ // The classifier answered with something the parser could not use. The
113
+ // result is the same empty context as a provider outage, and the cause
114
+ // is not: this one is prompt-layer, and it was paid for.
115
+ onOutcome?.({ outcome: "parse_failed" });
116
+ return { ...EMPTY_RECEPTION_RESULT, personas: [] };
117
+ }
118
+ const personasOut = Array.isArray(parsed.personas)
119
+ ? parsed.personas.filter((item): item is string => typeof item === "string")
120
+ : [];
121
+ const result = {
122
+ personas: personasOut,
123
+ journey: typeof parsed.journey === "string" ? parsed.journey : null,
124
+ touchesIdentity: pythonTruthy(parsed.touches_identity),
125
+ touchesShadow: pythonTruthy(parsed.touches_shadow),
126
+ };
127
+ const carriedSignal =
128
+ result.personas.length > 0 ||
129
+ result.journey !== null ||
130
+ result.touchesIdentity ||
131
+ result.touchesShadow;
132
+ onOutcome?.({ outcome: carriedSignal ? "answered" : "empty" });
133
+ return result;
134
+ } catch (error) {
135
+ onOutcome?.({ outcome: "transport_failed", kind: classifyProviderError(error) });
136
+ return { ...EMPTY_RECEPTION_RESULT, personas: [] };
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Python's assembled reception prompt: the formatted template, then the raw
142
+ * query appended after the `## User message` heading.
143
+ *
144
+ * Pure and exported so the assembled BYTES can be graded against the oracle
145
+ * corpus without a provider (CV22.DS8.US3). Replay resolves a fixture by role
146
+ * alone and never reads the prompt, so a drift here would have surfaced first
147
+ * against a real model.
148
+ */
149
+ export function buildReceptionPrompt(
150
+ query: string,
151
+ personas: readonly ReceptionPersona[],
152
+ journeys: readonly ReceptionJourney[],
153
+ ): string {
154
+ return (
155
+ pyFormat(RECEPTION_PROMPT, {
156
+ personas: formatPersonas(personas),
157
+ journeys: formatJourneys(journeys),
158
+ }) + query
159
+ );
160
+ }
161
+
162
+ export function formatPersonas(personas: readonly ReceptionPersona[]): string {
163
+ if (personas.length === 0) return "(none available)";
164
+ return personas
165
+ .map((persona) => {
166
+ const keywords = persona.routingKeywords.slice(0, 6);
167
+ const suffix = keywords.length > 0 ? ` [keywords: ${keywords.join(", ")}]` : "";
168
+ return `- ${persona.slug}: ${sliceCodePoints(persona.description, 120)}${suffix}`;
169
+ })
170
+ .join("\n");
171
+ }
172
+
173
+ export function formatJourneys(journeys: readonly ReceptionJourney[]): string {
174
+ if (journeys.length === 0) return "(none available)";
175
+ return journeys
176
+ .map((journey) => `- ${journey.slug}: ${sliceCodePoints(journey.description, 120)}`)
177
+ .join("\n");
178
+ }
179
+
180
+ export function sliceCodePoints(value: string, limit: number): string {
181
+ return [...value].slice(0, limit).join("");
182
+ }
183
+
184
+ export function pythonTruthy(value: unknown): boolean {
185
+ if (value === null || value === undefined || value === false) return false;
186
+ if (typeof value === "number") return value !== 0;
187
+ if (typeof value === "string" || Array.isArray(value)) return value.length > 0;
188
+ if (typeof value === "object") return Object.keys(value).length > 0;
189
+ return true;
190
+ }
191
+
192
+ function isRecord(value: unknown): value is Record<string, unknown> {
193
+ return typeof value === "object" && value !== null && !Array.isArray(value);
194
+ }
@@ -0,0 +1,75 @@
1
+ const WIDTH = 56;
2
+
3
+ export function renderMirrorModeTransition(input: {
4
+ identity: string;
5
+ journey?: string | null;
6
+ personas?: readonly string[];
7
+ }): string {
8
+ const rows: [string, string][] = [["identity", input.identity]];
9
+ if (input.journey) rows.push(["active journey", input.journey]);
10
+ rows.push([
11
+ "what this mode is",
12
+ "Identity lens. Mirror reflects from memory, values, journeys, tensions, and personas.",
13
+ ]);
14
+ rows.push(["persona routing", personaRoutingLine([...(input.personas ?? [])].sort())]);
15
+ rows.push(["available lenses", "◌ Mirror Mode · ■ Builder Mode · △ Explorer Mode · ☾ Soul Mode"]);
16
+ return box("◌ MIRROR MODE ACTIVE", rows);
17
+ }
18
+
19
+ export function renderMirrorBanner(persona: string | null): string {
20
+ const first = "\u001b[38;5;183m⏺ Mirror Mode active\u001b[0m\n";
21
+ return persona
22
+ ? `${first}\u001b[38;5;183m ✦ Persona: ${persona}\u001b[0m\n`
23
+ : `${first}\u001b[38;5;183m Ego responding without persona\u001b[0m\n`;
24
+ }
25
+
26
+ export function renderJourneyDetected(match: [string, number, string]): string {
27
+ return `\u001b[38;5;183m 🧭 Journey detected: ${match[0]} (${match[2]}, score: ${match[1].toFixed(2)})\u001b[0m\n`;
28
+ }
29
+
30
+ export function renderMirrorJourneys(
31
+ journeys: readonly { id: string; name: string; description: string }[],
32
+ ): string {
33
+ return journeys
34
+ .map((journey) => `- **${journey.id}** — ${journey.name}: ${journey.description}\n`)
35
+ .join("");
36
+ }
37
+
38
+ function personaRoutingLine(personas: string[]): string {
39
+ if (personas.length === 0) return "when the topic asks: personas route naturally";
40
+ const shown = personas.slice(0, 3);
41
+ const remaining = personas.length - shown.length;
42
+ return `when the topic asks: ${shown.join(", ")}${remaining > 0 ? ` and ${remaining} more available` : ""}`;
43
+ }
44
+
45
+ function box(title: string, rows: readonly [string, string][]): string {
46
+ const lines = ["Mirror", `╭${"─".repeat(WIDTH)}╮`, line(` ${title}`)];
47
+ for (const [label, value] of rows) {
48
+ lines.push(line(""));
49
+ lines.push(line(` ${label}`));
50
+ for (const wrapped of wrap(value)) lines.push(line(` ${wrapped}`));
51
+ }
52
+ lines.push(`╰${"─".repeat(WIDTH)}╯`);
53
+ return lines.join("\n");
54
+ }
55
+
56
+ function line(text: string): string {
57
+ const content = [...text].slice(0, WIDTH).join("");
58
+ return `│${content}${" ".repeat(Math.max(0, WIDTH - [...content].length))}│`;
59
+ }
60
+
61
+ function wrap(text: string): string[] {
62
+ const words = text.split(/\s+/).filter(Boolean);
63
+ if (words.length === 0) return [""];
64
+ const lines: string[] = [];
65
+ let current = words[0] ?? "";
66
+ for (const word of words.slice(1)) {
67
+ if ([...current].length + [...word].length + 1 <= WIDTH - 2) current += ` ${word}`;
68
+ else {
69
+ lines.push(current);
70
+ current = word;
71
+ }
72
+ }
73
+ lines.push(current);
74
+ return lines;
75
+ }