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,137 @@
1
+ // The five MCP tools that never cross a provider (CV22.DS9.US2 plateau 3).
2
+ //
3
+ // `list_journeys`, `journey_status`, `list_conversations`, `recall_conversation`,
4
+ // and `detect_persona` are plain database reads. Each returns the exact text its
5
+ // Python handler returns -- `json.dumps(value, ensure_ascii=False, indent=2,
6
+ // default=str)` -- which is what the model on the other end reads.
7
+ //
8
+ // The two provider-crossing tools (`search_memories` with a query, and
9
+ // `mirror_context`) live in their own module.
10
+
11
+ import { listRecentConversationSummaries } from "#conversation/listing.ts";
12
+ import { findConversationByIdPrefix, pythonTailSliceStart } from "#conversation/recall.ts";
13
+ import type { Database, Row } from "#db/database.ts";
14
+ import { optionalString, requireString } from "#db/rowDecode.ts";
15
+ import { detectPersona, type PersonaRoutingRow } from "#persona/detectPersona.ts";
16
+ import { pyFloat, pythonJson } from "../payload.ts";
17
+ import { journeyStatus, listActiveJourneyDtos, recallMessages } from "../readModels.ts";
18
+
19
+ /** Python's `_json`: pretty, unescaped, two-space indent. */
20
+ export function toolJson(value: unknown): string {
21
+ return pythonJson(value, { indent: 2 });
22
+ }
23
+
24
+ /** Read an argument that Python would have taken straight off the dict. */
25
+ function argument(args: unknown, key: string): unknown {
26
+ if (args === null || typeof args !== "object" || Array.isArray(args)) return undefined;
27
+ return (args as Record<string, unknown>)[key];
28
+ }
29
+
30
+ /**
31
+ * Python's `int(args.get("limit", fallback))`.
32
+ *
33
+ * `int("5")` and `int(5.9)` both succeed in Python, so a string or float limit
34
+ * is coerced rather than rejected. Argument VALIDATION is CV22.DS9.TS1's; this
35
+ * reproduces the oracle, including the limits US1's threat model recorded as
36
+ * unbounded.
37
+ */
38
+ function pythonInt(value: unknown, fallback: number): number {
39
+ if (value === undefined || value === null) return fallback;
40
+ if (typeof value === "number") return Math.trunc(value);
41
+ if (typeof value === "string") {
42
+ const parsed = Number(value.trim());
43
+ if (Number.isFinite(parsed)) return Math.trunc(parsed);
44
+ throw new Error(`invalid literal for int() with base 10: '${value}'`);
45
+ }
46
+ if (typeof value === "boolean") return value ? 1 : 0;
47
+ throw new Error(`int() argument must be a string or a number, not '${typeof value}'`);
48
+ }
49
+
50
+ export function listJourneysTool(db: Database, _args: unknown): string {
51
+ return toolJson(listActiveJourneyDtos(db));
52
+ }
53
+
54
+ export function journeyStatusTool(db: Database, args: unknown): string {
55
+ const slug = argument(args, "slug");
56
+ return toolJson(journeyStatus(db, typeof slug === "string" ? slug : null));
57
+ }
58
+
59
+ export function listConversationsTool(db: Database, args: unknown): string {
60
+ const summaries = listRecentConversationSummaries(db, {
61
+ limit: pythonInt(argument(args, "limit"), 20),
62
+ journey: (argument(args, "journey") as string | undefined) ?? undefined,
63
+ persona: (argument(args, "persona") as string | undefined) ?? undefined,
64
+ });
65
+ return toolJson(
66
+ summaries.map((summary) => ({
67
+ id: summary.id,
68
+ title: summary.title,
69
+ started_at: summary.started_at,
70
+ persona: summary.persona,
71
+ journey: summary.journey,
72
+ message_count: summary.message_count,
73
+ })),
74
+ );
75
+ }
76
+
77
+ export function recallConversationTool(db: Database, args: unknown): string {
78
+ const conversationId = argument(args, "conversation_id");
79
+ if (!conversationId) throw new Error("'conversation_id' is required");
80
+ const conversation = findConversationByIdPrefix(db, String(conversationId));
81
+ if (conversation === null) throw new Error(`no conversation matching '${conversationId}'`);
82
+
83
+ const limit = pythonInt(argument(args, "limit"), 50);
84
+ const messages = recallMessages(db, conversation.id);
85
+ // `messages[-limit:]` -- and `-0 == 0` in Python, so limit=0 returns the WHOLE
86
+ // transcript rather than none. Recorded in the golden and owned by the DS9
87
+ // threat model; TS1 caps it, and this stays faithful until it does.
88
+ const tail = messages.slice(pythonTailSliceStart(messages.length, limit));
89
+
90
+ return toolJson({
91
+ conversation_id: conversation.id,
92
+ messages: tail.map((message) => ({
93
+ role: message.role,
94
+ content: message.content,
95
+ created_at: message.created_at,
96
+ })),
97
+ });
98
+ }
99
+
100
+ /** Python's `_persona_metadata(identity).get("routing_keywords") or []`. */
101
+ function routingKeywords(metadata: unknown): string[] {
102
+ if (typeof metadata !== "string" || metadata === "") return [];
103
+ try {
104
+ const parsed = JSON.parse(metadata) as Record<string, unknown>;
105
+ if (!Array.isArray(parsed.routing_keywords)) return [];
106
+ return parsed.routing_keywords.filter((item): item is string => typeof item === "string");
107
+ } catch {
108
+ return [];
109
+ }
110
+ }
111
+
112
+ function personaRoutingRows(db: Database): PersonaRoutingRow[] {
113
+ return db
114
+ .prepare("SELECT key, metadata FROM identity WHERE layer = 'persona' ORDER BY key")
115
+ .all()
116
+ .map((row: Row) => ({
117
+ key: requireString(row, "key"),
118
+ routing_keywords: routingKeywords(optionalString(row, "metadata")),
119
+ }));
120
+ }
121
+
122
+ export function detectPersonaTool(db: Database, args: unknown): string {
123
+ const query = argument(args, "query");
124
+ if (!query) throw new Error("'query' is required");
125
+ const matches = detectPersona(String(query), personaRoutingRows(db));
126
+ return toolJson(
127
+ matches.map((match) => ({
128
+ persona: match.key,
129
+ // Python types this as float, and hit counts are routinely whole -- 2.0,
130
+ // not 2. JavaScript cannot tell the two apart, so the mapper says so.
131
+ score: pyFloat(match.score),
132
+ // Python appends the literal "keyword" as the third tuple element; the TS
133
+ // port carries it as `matchType`, and the tool renders it as `descriptor`.
134
+ descriptor: match.matchType,
135
+ })),
136
+ );
137
+ }
@@ -0,0 +1,187 @@
1
+ // The two MCP tools that can reach a provider (CV22.DS9.US2 plateau 4).
2
+ //
3
+ // `search_memories` with a `query` embeds it; `mirror_context` with a `query`
4
+ // reaches attachment search and every installed extension context provider.
5
+ // Both are the paid, fan-out paths the DS9 threat model names -- and, per the
6
+ // AI-19 rider, the ones DS9.TS1 will put a wallet guard in front of.
7
+ //
8
+ // Everything else here is a deterministic read: `search_memories` without a
9
+ // query is a filter over `memories`, and `mirror_context` without one assembles
10
+ // identity layers from the database.
11
+
12
+ import type { Database } from "#db/database.ts";
13
+ import { collectExtensionContext } from "#extensions/contextRuntime.ts";
14
+ import { loadMirrorContext } from "#mirror/context.ts";
15
+ import type { EmbeddingAttemptInfo, EmbeddingProvider } from "#providers/embedding.ts";
16
+ import { searchMemories } from "#search/memorySearch.ts";
17
+ import { pyFloat, pythonJson } from "../payload.ts";
18
+ import { type MemoryDto, memoriesByFilter, memoriesByIds } from "../readModels.ts";
19
+
20
+ /** What the tools need from the process to reach providers and extensions. */
21
+ export interface ToolRuntime {
22
+ embeddingProvider: EmbeddingProvider;
23
+ /**
24
+ * Where an agent-initiated embedding is recorded as spend (CV22.DS9.TS2 D1).
25
+ *
26
+ * A FUNCTION, never a database handle, and the distinction is the control:
27
+ * the `llm_calls`-only connection lives in `main.ts` and is never reachable
28
+ * from a tool, so no tool -- present or future -- can write an
29
+ * agent-influenced value into the ledger. Omitted, nothing is recorded and
30
+ * US2's read-only posture stands unchanged.
31
+ */
32
+ embeddingLedger?: (info: EmbeddingAttemptInfo) => void;
33
+ /** Absolute mirror home, for extension resolution. Omit to disable fan-out. */
34
+ mirrorHome?: string;
35
+ /** Absolute database path, handed to extension providers. */
36
+ databasePath?: string;
37
+ user?: string;
38
+ /** Frozen clock for the ranker, used by the parity harness. */
39
+ frozenNowMs?: number;
40
+ }
41
+
42
+ function toolJson(value: unknown): string {
43
+ return pythonJson(value, { indent: 2 });
44
+ }
45
+
46
+ function argument(args: unknown, key: string): unknown {
47
+ if (args === null || typeof args !== "object" || Array.isArray(args)) return undefined;
48
+ return (args as Record<string, unknown>)[key];
49
+ }
50
+
51
+ function pythonInt(value: unknown, fallback: number): number {
52
+ if (value === undefined || value === null) return fallback;
53
+ if (typeof value === "number") return Math.trunc(value);
54
+ if (typeof value === "string") {
55
+ const parsed = Number(value.trim());
56
+ if (Number.isFinite(parsed)) return Math.trunc(parsed);
57
+ throw new Error(`invalid literal for int() with base 10: '${value}'`);
58
+ }
59
+ if (typeof value === "boolean") return value ? 1 : 0;
60
+ throw new Error(`int() argument must be a string or a number, not '${typeof value}'`);
61
+ }
62
+
63
+ function optionalText(value: unknown): string | undefined {
64
+ return typeof value === "string" && value !== "" ? value : undefined;
65
+ }
66
+
67
+ /**
68
+ * Port of `_search_memories`.
69
+ *
70
+ * With a `query`: hybrid search, then the ranked ids mapped back to full rows.
71
+ * Without: the first of `journey`, `layer`, `type` that is present, sliced to
72
+ * `limit`; none of them is an error. Python's order of preference is exactly
73
+ * this, and the error text is its text.
74
+ */
75
+ export async function searchMemoriesTool(
76
+ db: Database,
77
+ args: unknown,
78
+ runtime: ToolRuntime,
79
+ ): Promise<string> {
80
+ const query = argument(args, "query");
81
+ const limit = pythonInt(argument(args, "limit"), 5);
82
+ const memoryType = optionalText(argument(args, "type"));
83
+ const layer = optionalText(argument(args, "layer"));
84
+ const journey = optionalText(argument(args, "journey"));
85
+
86
+ if (query) {
87
+ const ranked = await searchMemories(db, {
88
+ query: String(query),
89
+ limit,
90
+ memoryType: memoryType ?? null,
91
+ layer: layer ?? null,
92
+ journey: journey ?? null,
93
+ provider: runtime.embeddingProvider,
94
+ // AI-12: an agent searching on its own behalf is not a genuine context
95
+ // load, so it must not teach the ranker. Python's MCP handler passes
96
+ // log_access=False for exactly this reason.
97
+ logAccess: false,
98
+ // Spend IS recorded, through the sink rather than through this handle
99
+ // (TS2 D1): the tools' database is read-only at the driver level, and the
100
+ // ledger connection it never sees can form no statement but the append.
101
+ // Without a sink, nothing is written -- US2's posture, unchanged.
102
+ recordEmbeddingLedger: runtime.embeddingLedger ?? false,
103
+ ...(runtime.frozenNowMs === undefined ? {} : { frozenNowMs: runtime.frozenNowMs }),
104
+ });
105
+ const rows = memoriesByIds(
106
+ db,
107
+ ranked.map((result) => result.id),
108
+ );
109
+ const byId = new Map(rows.map((row) => [row.id, row]));
110
+ return toolJson(
111
+ ranked.map((result) => {
112
+ const row = byId.get(result.id);
113
+ return {
114
+ ...(row ?? emptyMemoryDto(result.id)),
115
+ // Python types the score as float; a whole score renders 1.0, not 1.
116
+ score: pyFloat(result.score),
117
+ };
118
+ }),
119
+ );
120
+ }
121
+
122
+ let rows: MemoryDto[];
123
+ if (journey) rows = memoriesByFilter(db, "journey", journey);
124
+ else if (layer) rows = memoriesByFilter(db, "layer", layer);
125
+ else if (memoryType) rows = memoriesByFilter(db, "memory_type", memoryType);
126
+ else throw new Error("provide 'query' or one of 'type' / 'layer' / 'journey'");
127
+
128
+ return toolJson(rows.slice(0, Math.max(0, limit)));
129
+ }
130
+
131
+ function emptyMemoryDto(id: string): MemoryDto {
132
+ return {
133
+ id,
134
+ title: null,
135
+ memory_type: null,
136
+ layer: null,
137
+ journey: null,
138
+ tags: null,
139
+ content: null,
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Port of `_mirror_context`.
145
+ *
146
+ * Deliberately not the Mirror Mode load: that one activates the mode and
147
+ * mutates sticky defaults. This is the side-effect-free context builder, and it
148
+ * keeps `load_mirror_context`'s defaults -- including reinforcement, because
149
+ * unlike an agent search this IS a genuine context load (AI-12).
150
+ *
151
+ * Extension context is resolved through the same `collectExtensionContext` the
152
+ * front door uses (plan D4), not a second reading of the `mirror-context-v1`
153
+ * contract. The agent-supplied `query` reaches every installed provider, which
154
+ * the DS9 threat model names as the fan-out path.
155
+ */
156
+ export async function mirrorContextTool(
157
+ db: Database,
158
+ args: unknown,
159
+ runtime: ToolRuntime,
160
+ ): Promise<string> {
161
+ const persona = optionalText(argument(args, "persona"));
162
+ const journey = optionalText(argument(args, "journey"));
163
+ const query = optionalText(argument(args, "query"));
164
+
165
+ const extensionContext =
166
+ runtime.mirrorHome && runtime.databasePath
167
+ ? collectExtensionContext(db, {
168
+ mirrorHome: runtime.mirrorHome,
169
+ databasePath: runtime.databasePath,
170
+ personaId: persona ?? null,
171
+ journeyId: journey ?? null,
172
+ user: runtime.user ?? "",
173
+ query: query ?? null,
174
+ }).rendered
175
+ : "";
176
+
177
+ return loadMirrorContext(db, {
178
+ persona: persona ?? null,
179
+ journey: journey ?? null,
180
+ query: query ?? null,
181
+ embeddingProvider: runtime.embeddingProvider,
182
+ // TS1: attachment search is the tool's paid path and neither engine recorded it.
183
+ // Same sink as `search_memories`, so one window counts the whole surface.
184
+ ...(runtime.embeddingLedger ? { embeddingLedger: runtime.embeddingLedger } : {}),
185
+ extensionContext,
186
+ });
187
+ }
@@ -0,0 +1,8 @@
1
+ // Kept as the US1 call site's name; the rules live in payload.ts (US2 D8).
2
+ //
3
+ // US1 shipped this module with its own copy of the separator logic. US2 needed
4
+ // the same semantics with `indent=2` and a float rule, so the two were folded
5
+ // into one encoder rather than maintained twice. This re-export keeps US1's
6
+ // imports and tests unmoved.
7
+
8
+ export { encodeJsonLine } from "./payload.ts";
@@ -0,0 +1,180 @@
1
+ /**
2
+ * `journal` — CV22.DS7.US11 plateau 3.
3
+ *
4
+ * Records a journal entry: classify it through the model, then write the memory
5
+ * with its embedding. CR068 found this leaf unowned — US2 sent it to US5, US5
6
+ * was re-scoped without it, and the ledger reported the family `3/3 done` while
7
+ * Python answered every call.
8
+ *
9
+ * Graded by `ts/test/goldens/journal.golden.json`. The corpus pins several
10
+ * Python behaviours that a reasonable port gets wrong:
11
+ *
12
+ * - **AI-24 layer coercion.** A model layer outside `self|ego|shadow` becomes
13
+ * `ego` rather than reaching the write unchecked (CV9.E2.S23/S25).
14
+ * - **The non-JSON fallback** is `{title: content[:60], layer: "ego",
15
+ * tags: []}`, and the 60 is CODE POINTS.
16
+ * - **`", ".join(tags)` over a STRING iterates characters.** When the model
17
+ * returns `"not-a-list"`, Python's receipt reads `n, o, t, -, a, ...`. It
18
+ * looks like a bug and is reproduced deliberately: the receipt is what a
19
+ * user sees, and the row stores the same odd value.
20
+ * - **The embedding is generated BEFORE the row is inserted**, so a failing
21
+ * embedding persists NOTHING — while the classification and the (unpriced)
22
+ * embedding ledger rows already exist. Insert-then-embed passes every other
23
+ * case and fails this one.
24
+ */
25
+
26
+ import type { WritableDatabase } from "#db/database.ts";
27
+ import { createMemoryRow } from "#memory/memoryWrite.ts";
28
+ import { addEmbeddingProvenance } from "#providers/embedding.ts";
29
+ import { memoryEmbedText } from "#soul/harvest.ts";
30
+ import { pythonJsonDumpsEnsureAscii } from "#util/pyGenerators.ts";
31
+ import { sliceCodePoints } from "#util/pythonText.ts";
32
+
33
+ /** Python `VALID_MEMORY_LAYERS`. */
34
+ const VALID_MEMORY_LAYERS = new Set(["self", "ego", "shadow"]);
35
+
36
+ /** Python's `layer_labels`, with the raw layer as its own fallback. */
37
+ const LAYER_LABELS: Record<string, string> = {
38
+ self: "Self (identity)",
39
+ ego: "Ego (operational)",
40
+ shadow: "Shadow (tension)",
41
+ };
42
+
43
+ export interface JournalClassification {
44
+ title: string;
45
+ layer: string;
46
+ tags: unknown;
47
+ }
48
+
49
+ /**
50
+ * Python `classify_journal_entry`'s post-processing, given a raw model
51
+ * response body. Kept separate from transport so it can be graded directly.
52
+ */
53
+ export function interpretJournalClassification(
54
+ responseContent: string,
55
+ content: string,
56
+ parseJson: (raw: string) => unknown,
57
+ ): JournalClassification {
58
+ const data = parseJson(responseContent);
59
+ if (data === null || typeof data !== "object" || Array.isArray(data)) {
60
+ // Python: `{"title": content[:60], "layer": "ego", "tags": []}`.
61
+ return { title: sliceCodePoints(content, 60), layer: "ego", tags: [] };
62
+ }
63
+ const record = data as Record<string, unknown>;
64
+ const rawLayer = "layer" in record ? record.layer : "ego";
65
+ const layer =
66
+ typeof rawLayer === "string" && VALID_MEMORY_LAYERS.has(rawLayer) ? rawLayer : "ego";
67
+ const title = "title" in record ? record.title : sliceCodePoints(content, 60);
68
+ return {
69
+ title: typeof title === "string" ? title : String(title),
70
+ layer,
71
+ tags: "tags" in record ? record.tags : [],
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Python `", ".join(tags)`.
77
+ *
78
+ * `str.join` iterates its argument. Over a list that is the elements; over a
79
+ * STRING it is the characters, which is why a model returning `"not-a-list"`
80
+ * yields `n, o, t, -, a, -, l, i, s, t`. Reproduced, not corrected.
81
+ *
82
+ * The CLI reaches this only after `json.loads(memory.tags)`, so the value seen
83
+ * here is whatever round-tripped through the stored JSON.
84
+ */
85
+ export function renderJournalTags(tags: unknown): string {
86
+ if (typeof tags === "string") return Array.from(tags).join(", ");
87
+ if (Array.isArray(tags))
88
+ return tags.map((tag) => (typeof tag === "string" ? tag : String(tag))).join(", ");
89
+ return "";
90
+ }
91
+
92
+ /** Python: `json.dumps(tags) if tags else None` — an empty list stores NULL. */
93
+ function storedTags(tags: unknown): string | null {
94
+ if (tags === null || tags === undefined) return null;
95
+ if (Array.isArray(tags) && tags.length === 0) return null;
96
+ if (typeof tags === "string" && tags.length === 0) return null;
97
+ return pythonJsonDumpsEnsureAscii(tags);
98
+ }
99
+
100
+ export interface AddJournalInput {
101
+ content: string;
102
+ journey?: string | null;
103
+ classification: JournalClassification;
104
+ }
105
+
106
+ export interface AddJournalDeps {
107
+ newId: () => string;
108
+ nowIso: string;
109
+ /** Throws when the provider fails; called BEFORE the row is inserted. */
110
+ embed: (text: string) => Uint8Array;
111
+ }
112
+
113
+ export interface AddJournalResult {
114
+ memoryId: string;
115
+ title: string;
116
+ layer: string;
117
+ tags: unknown;
118
+ }
119
+
120
+ /**
121
+ * Python `MemoryService.add_journal` → `add_memory` for the journal path.
122
+ *
123
+ * The embedding is computed first, deliberately: `add_memory` embeds before it
124
+ * builds the row, so a provider failure leaves no memory behind.
125
+ */
126
+ export function addJournal(
127
+ db: WritableDatabase,
128
+ input: AddJournalInput,
129
+ deps: AddJournalDeps,
130
+ ): AddJournalResult {
131
+ const { title, layer, tags } = input.classification;
132
+
133
+ const embedding = deps.embed(memoryEmbedText(title, input.content, null));
134
+
135
+ const memoryId = deps.newId();
136
+ createMemoryRow(db, {
137
+ id: memoryId,
138
+ conversationId: null,
139
+ memoryType: "journal",
140
+ layer,
141
+ title,
142
+ content: input.content,
143
+ context: null,
144
+ journey: input.journey ?? null,
145
+ persona: null,
146
+ tags: storedTags(tags),
147
+ createdAt: deps.nowIso,
148
+ embedding,
149
+ metadata: addEmbeddingProvenance(null),
150
+ });
151
+
152
+ return { memoryId, title, layer, tags };
153
+ }
154
+
155
+ /** Python's printed receipt, line for line. */
156
+ export function renderJournalReceipt(
157
+ result: AddJournalResult,
158
+ journey: string | null | undefined,
159
+ storedTagsJson: string | null,
160
+ ): string {
161
+ const parsedTags: unknown = storedTagsJson === null ? [] : safeJsonParse(storedTagsJson);
162
+ const lines = [
163
+ "📓 Journal entry recorded",
164
+ ` Title: ${result.title}`,
165
+ ` Layer: ${LAYER_LABELS[result.layer] ?? result.layer}`,
166
+ ` Tags: ${renderJournalTags(parsedTags)}`,
167
+ ];
168
+ if (journey) lines.push(` Journey: ${journey}`);
169
+ lines.push(` ID: ${result.memoryId.slice(0, 8)}`);
170
+ return `${lines.join("\n")}\n`;
171
+ }
172
+
173
+ /** Python's `try: json.loads(...) except (JSONDecodeError, TypeError): []`. */
174
+ function safeJsonParse(raw: string): unknown {
175
+ try {
176
+ return JSON.parse(raw);
177
+ } catch {
178
+ return [];
179
+ }
180
+ }
@@ -0,0 +1,107 @@
1
+ // Memory listing parity port (CV22.DS2.US3).
2
+ //
3
+ // A faithful TypeScript port of Python `Store.list_recent_memory_summaries` and
4
+ // `Store.count_memories_by_type` (`src/memory/storage/memories.py`). Following
5
+ // the database-seam philosophy, the ordering is pushed down to SQLite
6
+ // (`ORDER BY created_at DESC`) — the TS unit is a query builder + row→summary
7
+ // mapper, not a re-implementation of SQLite's sort. The `--search` listing path
8
+ // is out of scope here: it reuses the US1 hybrid ranker.
9
+
10
+ import type { Database, SqlValue } from "#db/database.ts";
11
+ import { optionalString, requireString } from "#db/rowDecode.ts";
12
+
13
+ /** The listing projection, mirroring the Python `MemorySummary` DTO. */
14
+ export interface MemorySummary {
15
+ id: string;
16
+ memory_type: string;
17
+ layer: string;
18
+ title: string;
19
+ content: string;
20
+ context: string | null;
21
+ journey: string | null;
22
+ persona: string | null;
23
+ tags: string | null;
24
+ created_at: string;
25
+ }
26
+
27
+ /** Optional equality filters and limit for the recent-memories read. */
28
+ export interface ListRecentFilters {
29
+ limit?: number;
30
+ memoryType?: string | null;
31
+ layer?: string | null;
32
+ journey?: string | null;
33
+ }
34
+
35
+ const SUMMARY_COLUMNS =
36
+ "id, memory_type, layer, title, content, context, journey, persona, tags, created_at";
37
+
38
+ /**
39
+ * Build the parameterized recent-memories query, mirroring the Python builder:
40
+ * a `1=1` seed, one `col = ?` clause per provided filter (in
41
+ * type → layer → journey order), `ORDER BY created_at DESC`, and a trailing
42
+ * `LIMIT ?`. Params are ordered to match the placeholders.
43
+ */
44
+ export function buildListRecentQuery(filters: ListRecentFilters = {}): {
45
+ sql: string;
46
+ params: SqlValue[];
47
+ } {
48
+ const { limit = 20, memoryType = null, layer = null, journey = null } = filters;
49
+ const conditions = ["1=1"];
50
+ const params: SqlValue[] = [];
51
+ if (memoryType) {
52
+ conditions.push("memory_type = ?");
53
+ params.push(memoryType);
54
+ }
55
+ if (layer) {
56
+ conditions.push("layer = ?");
57
+ params.push(layer);
58
+ }
59
+ if (journey) {
60
+ conditions.push("journey = ?");
61
+ params.push(journey);
62
+ }
63
+ params.push(limit);
64
+ const sql =
65
+ `SELECT ${SUMMARY_COLUMNS} FROM memories ` +
66
+ `WHERE ${conditions.join(" AND ")} ORDER BY created_at DESC LIMIT ?`;
67
+ return { sql, params };
68
+ }
69
+
70
+ function toSummary(row: Record<string, unknown>): MemorySummary {
71
+ return {
72
+ id: requireString(row, "id"),
73
+ memory_type: requireString(row, "memory_type"),
74
+ layer: requireString(row, "layer"),
75
+ title: requireString(row, "title"),
76
+ content: requireString(row, "content"),
77
+ context: optionalString(row, "context"),
78
+ journey: optionalString(row, "journey"),
79
+ persona: optionalString(row, "persona"),
80
+ tags: optionalString(row, "tags"),
81
+ created_at: requireString(row, "created_at"),
82
+ };
83
+ }
84
+
85
+ /** Return recent memory summaries with optional equality filters, newest first. */
86
+ export function listRecentMemorySummaries(
87
+ db: Database,
88
+ filters: ListRecentFilters = {},
89
+ ): MemorySummary[] {
90
+ const { sql, params } = buildListRecentQuery(filters);
91
+ return db
92
+ .prepare(sql)
93
+ .all(...params)
94
+ .map(toSummary);
95
+ }
96
+
97
+ /**
98
+ * Return memory counts grouped by type. SQLite leaves `GROUP BY` order
99
+ * unspecified, so — like the Python CLI, which sorts before display — callers
100
+ * compare these as a map/sorted set, not as an ordered list.
101
+ */
102
+ export function countMemoriesByType(db: Database): [string, number][] {
103
+ return db
104
+ .prepare("SELECT memory_type, COUNT(*) as count FROM memories GROUP BY memory_type")
105
+ .all()
106
+ .map((row) => [requireString(row, "memory_type"), Number(row.count)] as [string, number]);
107
+ }