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,130 @@
1
+ // How each runtime is wired to an installed Mirror Mind. CV22.DS10.US3 plateau 3.
2
+ //
3
+ // `mirror init` prints these for the runtimes it finds on the PATH, so the
4
+ // person who just created a home is handed the next line rather than a page.
5
+ // Printing is not automating: the `runtime install <runtime>` non-goal stands,
6
+ // and every step here is one the person runs and can read first.
7
+ //
8
+ // Each step was verified at plateau 3 against the runtime's own documentation
9
+ // or, where the docs were silent, against the runtime itself from a scratch
10
+ // configuration directory (test guide, route 3):
11
+ //
12
+ // Pi `pi install <path>` loads a package from a local path without
13
+ // copying (D6); the manifest's `pi` key names the extension and
14
+ // the skills. One step.
15
+ // Claude Code a plugin directory under `~/.claude/skills/` loads every
16
+ // session as `<name>@skills-dir` with its skills, hooks, and MCP
17
+ // server (`claude plugin list` / `details`, 2.1.283). One step.
18
+ // The Operating Instructions arrive through the plugin's
19
+ // SessionStart hook (D13).
20
+ // Gemini CLI `gemini skills link --consent <dir>` links every skill in the
21
+ // directory into `~/.gemini/skills/` (0.61.0; a nested directory
22
+ // symlink is NOT discovered, the user tier is one level deep),
23
+ // and hooks live in `~/.gemini/settings.json`, absolute paths.
24
+ // Two steps. The Operating Instructions arrive through the
25
+ // SessionStart hook (D13).
26
+ // Codex `~/.codex/skills/<dir>` is discovered recursively, and
27
+ // `~/.codex/AGENTS.md` is the global instructions file
28
+ // (`codex debug prompt-input`, 0.157.0). Two steps, and the
29
+ // session is run through the shipped wrapper, since Codex has
30
+ // no hooks this integration uses.
31
+
32
+ import { join } from "node:path";
33
+ import { commandOnPath } from "#util/paths.ts";
34
+
35
+ export type RuntimeName = "pi" | "claude" | "gemini" | "codex";
36
+
37
+ export interface RuntimeWiring {
38
+ /** The runtime, as a person names it. */
39
+ readonly title: string;
40
+ /** The command whose presence on the PATH means the runtime is installed. */
41
+ readonly command: string;
42
+ /** What to run or edit, one entry per step, built from the package root. */
43
+ readonly steps: readonly ((root: string) => readonly string[])[];
44
+ }
45
+
46
+ /** Gemini CLI's four hooks, as the user-level settings.json wants them. */
47
+ function geminiHooksSettings(root: string): string {
48
+ const hook = (event: string, name: string, script: string) =>
49
+ ` "${event}": [{ "hooks": [{ "name": "mirror-${name}", "type": "command", "command": "${join(root, ".gemini/hooks", script)}", "timeout": 30000 }] }]`;
50
+ return [
51
+ "{",
52
+ ' "hooks": {',
53
+ [
54
+ hook("SessionStart", "session-start", "session-start.sh"),
55
+ hook("BeforeAgent", "log-user", "log-user.sh"),
56
+ hook("AfterAgent", "log-assistant", "log-assistant.sh"),
57
+ hook("SessionEnd", "session-end", "session-end.sh"),
58
+ ].join(",\n"),
59
+ " }",
60
+ "}",
61
+ ].join("\n");
62
+ }
63
+
64
+ export const RUNTIME_WIRING: Readonly<Record<RuntimeName, RuntimeWiring>> = {
65
+ pi: {
66
+ title: "Pi",
67
+ command: "pi",
68
+ steps: [(root) => [`pi install "${root}"`]],
69
+ },
70
+ claude: {
71
+ title: "Claude Code",
72
+ command: "claude",
73
+ steps: [
74
+ (root) => [
75
+ `ln -s "${join(root, "plugins/mirror-mind")}" ~/.claude/skills/mirror-mind`,
76
+ "(the plugin loads every session: its skills, the hooks, and the MCP server)",
77
+ ],
78
+ ],
79
+ },
80
+ gemini: {
81
+ title: "Gemini CLI",
82
+ command: "gemini",
83
+ steps: [
84
+ (root) => [`gemini skills link --consent "${join(root, ".pi/skills")}"`],
85
+ (root) => [
86
+ "merge into ~/.gemini/settings.json:",
87
+ ...geminiHooksSettings(root)
88
+ .split("\n")
89
+ .map((line) => ` ${line}`),
90
+ ],
91
+ ],
92
+ },
93
+ codex: {
94
+ title: "Codex",
95
+ command: "codex",
96
+ steps: [
97
+ (root) => [`ln -s "${join(root, ".pi/skills")}" ~/.codex/skills/mirror-mind`],
98
+ (root) => [
99
+ `ln -s "${join(root, "AGENTS.md")}" ~/.codex/AGENTS.md`,
100
+ "(or append that file to the ~/.codex/AGENTS.md you already have)",
101
+ `then start Codex through "${join(root, "scripts/codex-mirror.sh")}" instead of codex`,
102
+ ],
103
+ ],
104
+ },
105
+ };
106
+
107
+ const ORDER: readonly RuntimeName[] = ["pi", "claude", "gemini", "codex"];
108
+
109
+ /** The runtimes whose command is on the PATH, in display order. */
110
+ export function detectRuntimes(
111
+ env: NodeJS.ProcessEnv,
112
+ isExecutable: (path: string) => boolean,
113
+ ): RuntimeName[] {
114
+ return ORDER.filter(
115
+ (runtime) => commandOnPath(RUNTIME_WIRING[runtime].command, env, isExecutable) !== null,
116
+ );
117
+ }
118
+
119
+ /** The wiring for one runtime, numbered when there is more than one step. */
120
+ export function runtimeWiringLines(runtime: RuntimeName, root: string): string[] {
121
+ const wiring = RUNTIME_WIRING[runtime];
122
+ const lines: string[] = [`${wiring.title}:`];
123
+ wiring.steps.forEach((step, index) => {
124
+ const [first = "", ...rest] = step(root);
125
+ const prefix = wiring.steps.length > 1 ? `${index + 1}. ` : "";
126
+ lines.push(` ${prefix}${first}`);
127
+ for (const line of rest) lines.push(` ${" ".repeat(prefix.length)}${line}`);
128
+ });
129
+ return lines;
130
+ }
@@ -0,0 +1,338 @@
1
+ import type { Database, SqlValue, WritableDatabase } from "#db/database.ts";
2
+ import { optionalNumber, optionalString, requireString } from "#db/rowDecode.ts";
3
+ import { logAccess } from "#memory/reinforcement.ts";
4
+ import { embeddingLedgerHook } from "#observability/ledgerHooks.ts";
5
+ import { ProviderConfigError } from "#providers/config.ts";
6
+ import {
7
+ type EmbeddingAttemptInfo,
8
+ EmbeddingError,
9
+ type EmbeddingProvider,
10
+ generateEmbeddingSafely,
11
+ } from "#providers/embedding.ts";
12
+ import { LlmTransportError } from "#providers/openrouter.ts";
13
+ import { nowIso } from "#util/pyGenerators.ts";
14
+ import {
15
+ type RankableMemory,
16
+ type RankedMemory,
17
+ type RankerConfig,
18
+ rankMemories,
19
+ } from "./ranker.ts";
20
+
21
+ export interface FreshSearchFilters {
22
+ memoryType?: string | null;
23
+ layer?: string | null;
24
+ journey?: string | null;
25
+ }
26
+
27
+ export interface FreshSearchOptions extends FreshSearchFilters {
28
+ query: string;
29
+ limit?: number;
30
+ /**
31
+ * Reinforce retrieval for the returned memories. Default `true`, mirroring
32
+ * Python's `log_access: bool = True`.
33
+ *
34
+ * `access_count` feeds `reinforcement_score` and the hybrid ranker, so a
35
+ * caller that searches on the system's behalf rather than the user's must opt
36
+ * out -- otherwise the ranker learns from its own exhaust (AI-12). Python's
37
+ * non-genuine callers pass `log_access=False`: the curation pass, the MCP
38
+ * agent search, and `memories --search`. Builder context load keeps the
39
+ * default and still reinforces, because that one is a genuine load.
40
+ */
41
+ logAccess?: boolean;
42
+ /**
43
+ * Record each embedding attempt in the `llm_calls` ledger (AI-09/D-003).
44
+ * Default `true`.
45
+ *
46
+ * - `true`/unset -- write through `db`, which must be writable.
47
+ * - `false` -- record nothing.
48
+ * - a function -- record through that sink, leaving `db` untouched.
49
+ *
50
+ * The sink exists for the MCP server (CV22.DS9.TS2 D1), whose tools hold a
51
+ * driver-level READ-ONLY handle while its single sanctioned write goes to a
52
+ * separate `llm_calls`-only connection. Passing a function rather than a
53
+ * second handle keeps that connection out of this module and out of every
54
+ * tool: search never learns it exists.
55
+ */
56
+ recordEmbeddingLedger?: boolean | ((info: EmbeddingAttemptInfo) => void);
57
+ frozenNowMs?: number;
58
+ now?: string;
59
+ provider: EmbeddingProvider;
60
+ /** Test-injectable override for generateEmbeddingSafely's retry backoff
61
+ * (real timers by default) -- avoids paying real backoff wait time in tests
62
+ * that exercise the transient-retry path. */
63
+ embeddingRetrySleep?: (ms: number) => Promise<void>;
64
+ }
65
+
66
+ export interface FreshSearchResult extends RankedMemory {}
67
+
68
+ export interface FreshSearchOutcome {
69
+ results: FreshSearchResult[];
70
+ degraded: boolean;
71
+ /**
72
+ * Metadata-only cause of a degraded search: the transport taxonomy kind, or
73
+ * `config` when no key is set. Never a message, never the query -- this is
74
+ * written to the front-door log, which forbids payloads (RS005/CR026).
75
+ */
76
+ degradedKind?: string;
77
+ }
78
+
79
+ export const DEFAULT_SEARCH_RANKER_CONFIG = {
80
+ weights: { semantic: 0.5, recency: 0.15, reinforcement: 0.1, relevance: 0.1, lexical: 0.15 },
81
+ mmrThreshold: 0.92,
82
+ recencyHalfLifeDays: 90,
83
+ reinforcementDecayDays: 180,
84
+ reinforcementUseWeight: 0.7,
85
+ reinforcementRetrievalWeight: 0.3,
86
+ } satisfies Omit<RankerConfig, "queryEmbedding" | "frozenNowMs" | "limit">;
87
+
88
+ interface MemoryRow {
89
+ id: string;
90
+ created_at: string;
91
+ last_accessed_at: string | null;
92
+ use_count: number;
93
+ relevance_score: number;
94
+ embedding_b64: string;
95
+ }
96
+
97
+ /**
98
+ * Hybrid search that also reports whether it ran degraded (lexical-only),
99
+ * mirroring Python's `search_with_status` (AI-04, CV9.E2.S10).
100
+ *
101
+ * When the query embedding cannot be generated (offline, missing key,
102
+ * timeout) the search falls back to the local FTS5 index: the semantic term
103
+ * is dropped (an empty `queryEmbedding` makes `cosineSimilarity`'s zero-norm
104
+ * path return 0 for every candidate, so `ranker.ts` needs no change) and only
105
+ * FTS-matched memories are ranked -- a hard filter, not just a lower score,
106
+ * matching Python's `mem.id not in fts_lookup: continue`. MMR dedup is
107
+ * unaffected -- it ranks on the stored memory embeddings, not the query.
108
+ */
109
+ export async function searchMemoriesWithStatus(
110
+ db: Database,
111
+ options: FreshSearchOptions,
112
+ ): Promise<FreshSearchOutcome> {
113
+ const limit = options.limit ?? 5;
114
+ let queryEmbedding: readonly number[] = [];
115
+ let degraded = false;
116
+ let degradedKind: string | undefined;
117
+ try {
118
+ // generateEmbeddingSafely (CR043) retries a transient empty response up to
119
+ // its default budget before giving up -- matching Python's search, which
120
+ // calls the same generate_embedding used everywhere else, not a bespoke
121
+ // single-shot attempt. Any exhausted/permanent/provider-exception failure
122
+ // still maps to degraded=true here, preserving CR037's contract exactly.
123
+ queryEmbedding = await generateEmbeddingSafely(options.provider, options.query, {
124
+ onAttempt: resolveLedgerSink(db, options.recordEmbeddingLedger),
125
+ sleep: options.embeddingRetrySleep,
126
+ });
127
+ } catch (error) {
128
+ degraded = true;
129
+ // Keep the CLASS of failure (AI-18 taxonomy) so an operator can tell an
130
+ // expired key from a timeout from a rate limit. Without this the front
131
+ // door only ever says "offline or no API key", which is a guess -- and
132
+ // the taxonomy the live transport builds would die here unseen.
133
+ degradedKind = classifyDegradedCause(error);
134
+ }
135
+
136
+ const memories = listSearchMemoryRows(db, options);
137
+ const accessCounts = accessCountsByMemoryId(
138
+ db,
139
+ memories.map((memory) => memory.id),
140
+ );
141
+ const lexicalScores = ftsLexicalScores(db, options.query, options);
142
+
143
+ const candidateMemories = degraded
144
+ ? memories.filter((memory) => lexicalScores.has(memory.id))
145
+ : memories;
146
+ const rankable = candidateMemories.map(
147
+ (memory): RankableMemory => ({
148
+ ...memory,
149
+ access_count: accessCounts.get(memory.id) ?? 0,
150
+ lexical_score: lexicalScores.get(memory.id) ?? 0,
151
+ }),
152
+ );
153
+ const ranked = rankMemories(rankable, {
154
+ ...DEFAULT_SEARCH_RANKER_CONFIG,
155
+ queryEmbedding,
156
+ frozenNowMs: options.frozenNowMs ?? Date.now(),
157
+ limit,
158
+ });
159
+
160
+ // Reinforce only on genuine context loads. This mirrors Python's log_access
161
+ // param (AI-12) firing regardless of degraded status -- the two concerns are
162
+ // orthogonal and intentionally not coupled; do not make this conditional.
163
+ if (options.logAccess ?? true) {
164
+ const writable = asWritable(db, "logAccess");
165
+ const accessNow = options.now ?? nowIso();
166
+ for (const result of ranked) {
167
+ logAccess(writable, result.id, accessNow, options.query.slice(0, 200));
168
+ }
169
+ }
170
+ return { results: ranked, degraded, degradedKind };
171
+ }
172
+
173
+ /** Thin, non-breaking wrapper over `searchMemoriesWithStatus`, mirroring
174
+ * Python's `search()` over `search_with_status()`: existing callers keep the
175
+ * plain-array shape, and an embedding failure no longer propagates uncaught. */
176
+ export async function searchMemories(
177
+ db: Database,
178
+ options: FreshSearchOptions,
179
+ ): Promise<FreshSearchResult[]> {
180
+ return (await searchMemoriesWithStatus(db, options)).results;
181
+ }
182
+
183
+ export function listSearchMemoryRows(db: Database, filters: FreshSearchFilters = {}): MemoryRow[] {
184
+ const conditions = ["embedding IS NOT NULL"];
185
+ const params: SqlValue[] = [];
186
+ if (filters.memoryType) {
187
+ conditions.push("memory_type = ?");
188
+ params.push(filters.memoryType);
189
+ }
190
+ if (filters.layer) {
191
+ conditions.push("layer = ?");
192
+ params.push(filters.layer);
193
+ }
194
+ if (filters.journey) {
195
+ conditions.push("journey = ?");
196
+ params.push(filters.journey);
197
+ }
198
+
199
+ return db
200
+ .prepare(
201
+ `SELECT id, created_at, last_accessed_at, use_count, relevance_score, embedding ` +
202
+ `FROM memories WHERE ${conditions.join(" AND ")} ORDER BY created_at DESC`,
203
+ )
204
+ .all(...params)
205
+ .map(toMemoryRow);
206
+ }
207
+
208
+ export function accessCountsByMemoryId(
209
+ db: Database,
210
+ memoryIds: readonly string[],
211
+ ): Map<string, number> {
212
+ if (memoryIds.length === 0) return new Map();
213
+ const placeholders = memoryIds.map(() => "?").join(", ");
214
+ const rows = db
215
+ .prepare(
216
+ `SELECT memory_id, COUNT(*) AS count FROM memory_access_log ` +
217
+ `WHERE memory_id IN (${placeholders}) GROUP BY memory_id`,
218
+ )
219
+ .all(...memoryIds);
220
+ const counts = new Map(memoryIds.map((id): [string, number] => [id, 0]));
221
+ for (const row of rows) {
222
+ counts.set(requireString(row, "memory_id"), Number(row.count));
223
+ }
224
+ return counts;
225
+ }
226
+
227
+ export function ftsLexicalScores(
228
+ db: Database,
229
+ query: string,
230
+ filters: FreshSearchFilters = {},
231
+ limit = 100,
232
+ ): Map<string, number> {
233
+ const safeQuery = ftsQuery(query);
234
+ if (!safeQuery) return new Map();
235
+ const conditions: string[] = [];
236
+ const params: SqlValue[] = [safeQuery];
237
+ if (filters.memoryType) {
238
+ conditions.push("m.memory_type = ?");
239
+ params.push(filters.memoryType);
240
+ }
241
+ if (filters.layer) {
242
+ conditions.push("m.layer = ?");
243
+ params.push(filters.layer);
244
+ }
245
+ if (filters.journey) {
246
+ conditions.push("m.journey = ?");
247
+ params.push(filters.journey);
248
+ }
249
+ params.push(limit);
250
+ const whereExtra = conditions.length > 0 ? ` AND ${conditions.join(" AND ")}` : "";
251
+ try {
252
+ const rows = db
253
+ .prepare(
254
+ `SELECT m.id FROM memories_fts f ` +
255
+ `JOIN memories m ON m.rowid = f.rowid ` +
256
+ `WHERE memories_fts MATCH ?${whereExtra} ` +
257
+ `ORDER BY bm25(memories_fts) LIMIT ?`,
258
+ )
259
+ .all(...params);
260
+ return new Map(rows.map((row, index) => [requireString(row, "id"), 1 / (1 + index)]));
261
+ } catch {
262
+ return new Map();
263
+ }
264
+ }
265
+
266
+ export function ftsQuery(query: string): string {
267
+ const words = query
268
+ .split(/\s+/)
269
+ .map((word) => word.replaceAll('"', ""))
270
+ .filter((word) => word.length > 0);
271
+ return words.map((word) => `"${word}"`).join(" ");
272
+ }
273
+
274
+ /**
275
+ * Narrow a handle to a writable one, or fail with the option that asked for it.
276
+ *
277
+ * Search reads through a plain `Database` so a read-only caller (the MCP
278
+ * server) can use it. The two paths that WRITE -- reinforcement and the
279
+ * embedding ledger -- are opt-in, and asking for either with a read-only handle
280
+ * is a caller bug worth naming rather than a SQLite error to decode.
281
+ */
282
+ function asWritable(db: Database, requestedBy: string): WritableDatabase {
283
+ if (typeof (db as WritableDatabase).exec !== "function") {
284
+ throw new Error(`${requestedBy} requires a writable database handle; this one is read-only`);
285
+ }
286
+ return db as WritableDatabase;
287
+ }
288
+
289
+ /** The query embedding's ledger row (AI-09/D-003). The query text is not tied
290
+ * to a conversation, so no conversationId travels with it. */
291
+ function logQueryEmbeddingAttempt(db: WritableDatabase): (info: EmbeddingAttemptInfo) => void {
292
+ return embeddingLedgerHook(db);
293
+ }
294
+
295
+ /**
296
+ * Resolve where an embedding attempt is recorded: `db` by default, nowhere when
297
+ * disabled, or a caller-supplied sink. Only the default narrows `db` to a
298
+ * writable handle -- a caller passing a sink may hold a read-only one.
299
+ */
300
+ function resolveLedgerSink(
301
+ db: Database,
302
+ record: boolean | ((info: EmbeddingAttemptInfo) => void) | undefined,
303
+ ): ((info: EmbeddingAttemptInfo) => void) | undefined {
304
+ if (typeof record === "function") return record;
305
+ if (record === false) return undefined;
306
+ return logQueryEmbeddingAttempt(asWritable(db, "recordEmbeddingLedger"));
307
+ }
308
+
309
+ function toMemoryRow(row: Record<string, unknown>): MemoryRow {
310
+ const embedding = row.embedding;
311
+ if (!(embedding instanceof Uint8Array)) {
312
+ throw new Error("memory embedding must be a BLOB/Uint8Array");
313
+ }
314
+ return {
315
+ id: requireString(row, "id"),
316
+ created_at: requireString(row, "created_at"),
317
+ last_accessed_at: optionalString(row, "last_accessed_at"),
318
+ use_count: optionalNumber(row, "use_count") ?? 0,
319
+ relevance_score: optionalNumber(row, "relevance_score") ?? 0,
320
+ embedding_b64: Buffer.from(embedding).toString("base64"),
321
+ };
322
+ }
323
+
324
+ /**
325
+ * Reduce a degraded-search cause to a short, content-free category
326
+ * (CV22.DS8.US1). Only the CLASS travels: provider messages can echo request
327
+ * content, and the front-door log forbids payloads.
328
+ */
329
+ function classifyDegradedCause(error: unknown): string {
330
+ if (error instanceof ProviderConfigError) return "config";
331
+ if (error instanceof LlmTransportError) return error.kind;
332
+ if (error instanceof EmbeddingError) {
333
+ // generateEmbeddingSafely wraps a provider exception; the inner kind is
334
+ // gone by then, so the honest answer is the layer that failed.
335
+ return error.permanent ? "embedding_permanent" : "embedding_exhausted";
336
+ }
337
+ return "unknown";
338
+ }
@@ -0,0 +1,154 @@
1
+ import { blobToFloat32, parseUtcMs } from "#db/decode.ts";
2
+
3
+ export interface SearchWeights {
4
+ semantic: number;
5
+ recency: number;
6
+ reinforcement: number;
7
+ relevance: number;
8
+ lexical?: number;
9
+ }
10
+
11
+ export interface RankerConfig {
12
+ queryEmbedding: readonly number[];
13
+ frozenNowMs: number;
14
+ limit: number;
15
+ weights: SearchWeights;
16
+ mmrThreshold: number;
17
+ recencyHalfLifeDays: number;
18
+ reinforcementDecayDays: number;
19
+ reinforcementUseWeight: number;
20
+ reinforcementRetrievalWeight: number;
21
+ }
22
+
23
+ export interface RankableMemory {
24
+ id: string;
25
+ created_at: string;
26
+ last_accessed_at?: string | null;
27
+ use_count: number;
28
+ relevance_score: number;
29
+ access_count: number;
30
+ lexical_score?: number;
31
+ embedding_b64: string;
32
+ }
33
+
34
+ export interface RankedMemory {
35
+ id: string;
36
+ score: number;
37
+ }
38
+
39
+ interface Candidate {
40
+ id: string;
41
+ score: number;
42
+ embedding: Float32Array;
43
+ }
44
+
45
+ export function cosineSimilarity(a: ArrayLike<number>, b: ArrayLike<number>): number {
46
+ const length = Math.min(a.length, b.length);
47
+ let dot = 0;
48
+ let normA = 0;
49
+ let normB = 0;
50
+ for (let i = 0; i < length; i += 1) {
51
+ dot += a[i] * b[i];
52
+ normA += a[i] * a[i];
53
+ normB += b[i] * b[i];
54
+ }
55
+ const norm = Math.sqrt(normA) * Math.sqrt(normB);
56
+ return norm === 0 ? 0 : dot / norm;
57
+ }
58
+
59
+ export function recencyScore(
60
+ createdAt: string,
61
+ config: Pick<RankerConfig, "frozenNowMs" | "recencyHalfLifeDays">,
62
+ ): number {
63
+ const createdMs = parseUtcMs(createdAt);
64
+ if (createdMs === null) return 0.5;
65
+ const daysAgo = Math.max(0, (config.frozenNowMs - createdMs) / 86_400_000);
66
+ return Math.exp((-Math.LN2 * daysAgo) / config.recencyHalfLifeDays);
67
+ }
68
+
69
+ export function reinforcementScore(
70
+ accessCount: number,
71
+ useCount: number,
72
+ lastAccessedAt: string | null | undefined,
73
+ config: Pick<
74
+ RankerConfig,
75
+ | "frozenNowMs"
76
+ | "reinforcementDecayDays"
77
+ | "reinforcementUseWeight"
78
+ | "reinforcementRetrievalWeight"
79
+ >,
80
+ ): number {
81
+ const useSignal = Math.min(1, useCount / 5);
82
+ const retrievalRaw = Math.min(1, Math.log1p(accessCount) / 3);
83
+ const lastMs = accessCount > 0 && lastAccessedAt ? parseUtcMs(lastAccessedAt) : null;
84
+ let retrievalSignal = retrievalRaw;
85
+ if (lastMs !== null) {
86
+ const days = Math.max(0, (config.frozenNowMs - lastMs) / 86_400_000);
87
+ retrievalSignal = retrievalRaw * Math.exp((-Math.LN2 * days) / config.reinforcementDecayDays);
88
+ }
89
+ return (
90
+ config.reinforcementUseWeight * useSignal +
91
+ config.reinforcementRetrievalWeight * retrievalSignal
92
+ );
93
+ }
94
+
95
+ export function hybridScore(args: {
96
+ semantic: number;
97
+ recency: number;
98
+ reinforcement: number;
99
+ relevance: number;
100
+ lexical: number;
101
+ weights: SearchWeights;
102
+ }): number {
103
+ return (
104
+ args.weights.semantic * args.semantic +
105
+ args.weights.recency * args.recency +
106
+ args.weights.reinforcement * args.reinforcement +
107
+ args.weights.relevance * args.relevance +
108
+ (args.weights.lexical ?? 0) * args.lexical
109
+ );
110
+ }
111
+
112
+ export function rankMemories(
113
+ memories: readonly RankableMemory[],
114
+ config: RankerConfig,
115
+ ): RankedMemory[] {
116
+ const candidates: Candidate[] = memories.map((memory) => {
117
+ const embedding = blobToFloat32(Buffer.from(memory.embedding_b64, "base64"));
118
+ const semantic = cosineSimilarity(config.queryEmbedding, embedding);
119
+ const recency = recencyScore(memory.created_at, config);
120
+ const reinforcement = reinforcementScore(
121
+ memory.access_count,
122
+ memory.use_count,
123
+ memory.last_accessed_at,
124
+ config,
125
+ );
126
+ const score = hybridScore({
127
+ semantic,
128
+ recency,
129
+ reinforcement,
130
+ relevance: memory.relevance_score,
131
+ lexical: memory.lexical_score ?? 0,
132
+ weights: config.weights,
133
+ });
134
+ return { id: memory.id, score, embedding };
135
+ });
136
+
137
+ candidates.sort((a, b) => b.score - a.score);
138
+
139
+ const selected: Candidate[] = [];
140
+ for (const candidate of candidates) {
141
+ if (
142
+ selected.some(
143
+ (existing) =>
144
+ cosineSimilarity(candidate.embedding, existing.embedding) >= config.mmrThreshold,
145
+ )
146
+ ) {
147
+ continue;
148
+ }
149
+ selected.push(candidate);
150
+ if (selected.length >= config.limit) break;
151
+ }
152
+
153
+ return selected.map((candidate) => ({ id: candidate.id, score: candidate.score }));
154
+ }