mirror-mind 0.0.1 → 0.32.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (467) hide show
  1. package/.claude/hooks/log-session-end.sh +85 -0
  2. package/.claude/hooks/log-user-prompt.sh +85 -0
  3. package/.claude/hooks/mirror-inject.sh +85 -0
  4. package/.claude/hooks/session-start.sh +85 -0
  5. package/.gemini/hooks/log-assistant.sh +85 -0
  6. package/.gemini/hooks/log-user.sh +85 -0
  7. package/.gemini/hooks/session-end.sh +85 -0
  8. package/.gemini/hooks/session-start.sh +85 -0
  9. package/.pi/extensions/mirror-logger.ts +561 -0
  10. package/.pi/skills/mm-backup/SKILL.md +21 -0
  11. package/.pi/skills/mm-build/SKILL.md +1206 -0
  12. package/.pi/skills/mm-consolidate/SKILL.md +112 -0
  13. package/.pi/skills/mm-consult/SKILL.md +70 -0
  14. package/.pi/skills/mm-conversations/SKILL.md +16 -0
  15. package/.pi/skills/mm-discard/SKILL.md +39 -0
  16. package/.pi/skills/mm-explore/SKILL.md +255 -0
  17. package/.pi/skills/mm-help/SKILL.md +74 -0
  18. package/.pi/skills/mm-identity/SKILL.md +96 -0
  19. package/.pi/skills/mm-journal/SKILL.md +16 -0
  20. package/.pi/skills/mm-journey/SKILL.md +23 -0
  21. package/.pi/skills/mm-journeys/SKILL.md +15 -0
  22. package/.pi/skills/mm-memories/SKILL.md +17 -0
  23. package/.pi/skills/mm-mirror/SKILL.md +98 -0
  24. package/.pi/skills/mm-mute/SKILL.md +18 -0
  25. package/.pi/skills/mm-new/SKILL.md +18 -0
  26. package/.pi/skills/mm-recall/SKILL.md +17 -0
  27. package/.pi/skills/mm-release-notes/SKILL.md +52 -0
  28. package/.pi/skills/mm-seed/SKILL.md +19 -0
  29. package/.pi/skills/mm-shadow/SKILL.md +117 -0
  30. package/.pi/skills/mm-soul/SKILL.md +502 -0
  31. package/.pi/skills/mm-tasks/SKILL.md +24 -0
  32. package/.pi/skills/mm-update/SKILL.md +32 -0
  33. package/.pi/skills/mm-week/SKILL.md +28 -0
  34. package/.pi/skills/mm-welcome/SKILL.md +24 -0
  35. package/AGENTS.md +188 -0
  36. package/README.md +126 -9
  37. package/bin/loader.js +67 -0
  38. package/bin/mirror-hook.js +7 -0
  39. package/bin/mirror.js +7 -0
  40. package/docs/releases/index.md +99 -0
  41. package/docs/releases/pending-cutoffs.md +510 -0
  42. package/docs/releases/v0.10.0.md +123 -0
  43. package/docs/releases/v0.10.1.md +66 -0
  44. package/docs/releases/v0.10.10.md +51 -0
  45. package/docs/releases/v0.10.11.md +39 -0
  46. package/docs/releases/v0.10.12.md +57 -0
  47. package/docs/releases/v0.10.13.md +35 -0
  48. package/docs/releases/v0.10.14.md +42 -0
  49. package/docs/releases/v0.10.15.md +53 -0
  50. package/docs/releases/v0.10.16.md +35 -0
  51. package/docs/releases/v0.10.17.md +43 -0
  52. package/docs/releases/v0.10.2.md +64 -0
  53. package/docs/releases/v0.10.3.md +78 -0
  54. package/docs/releases/v0.10.4.md +42 -0
  55. package/docs/releases/v0.10.5.md +57 -0
  56. package/docs/releases/v0.10.6.md +47 -0
  57. package/docs/releases/v0.10.7.md +53 -0
  58. package/docs/releases/v0.10.8.md +49 -0
  59. package/docs/releases/v0.10.9.md +37 -0
  60. package/docs/releases/v0.11.0.md +86 -0
  61. package/docs/releases/v0.12.0.md +67 -0
  62. package/docs/releases/v0.13.0.md +64 -0
  63. package/docs/releases/v0.14.0.md +68 -0
  64. package/docs/releases/v0.15.0.md +48 -0
  65. package/docs/releases/v0.16.0.md +50 -0
  66. package/docs/releases/v0.16.1.md +55 -0
  67. package/docs/releases/v0.16.2.md +36 -0
  68. package/docs/releases/v0.17.0.md +50 -0
  69. package/docs/releases/v0.17.1.md +58 -0
  70. package/docs/releases/v0.17.2.md +42 -0
  71. package/docs/releases/v0.17.3.md +58 -0
  72. package/docs/releases/v0.18.0.md +84 -0
  73. package/docs/releases/v0.18.1.md +51 -0
  74. package/docs/releases/v0.18.2.md +69 -0
  75. package/docs/releases/v0.19.0.md +85 -0
  76. package/docs/releases/v0.19.1.md +46 -0
  77. package/docs/releases/v0.20.0.md +85 -0
  78. package/docs/releases/v0.21.0.md +61 -0
  79. package/docs/releases/v0.21.1.md +47 -0
  80. package/docs/releases/v0.22.0.md +77 -0
  81. package/docs/releases/v0.22.1.md +42 -0
  82. package/docs/releases/v0.23.0.md +105 -0
  83. package/docs/releases/v0.24.0.md +125 -0
  84. package/docs/releases/v0.25.0.md +107 -0
  85. package/docs/releases/v0.26.0.md +139 -0
  86. package/docs/releases/v0.26.1.md +65 -0
  87. package/docs/releases/v0.26.2.md +43 -0
  88. package/docs/releases/v0.27.0.md +63 -0
  89. package/docs/releases/v0.28.0.md +59 -0
  90. package/docs/releases/v0.29.0.md +61 -0
  91. package/docs/releases/v0.29.1.md +59 -0
  92. package/docs/releases/v0.29.2.md +59 -0
  93. package/docs/releases/v0.29.3.md +93 -0
  94. package/docs/releases/v0.30.0.md +75 -0
  95. package/docs/releases/v0.30.1.md +93 -0
  96. package/docs/releases/v0.30.2.md +66 -0
  97. package/docs/releases/v0.31.0.md +122 -0
  98. package/docs/releases/v0.31.1.md +142 -0
  99. package/docs/releases/v0.31.10.md +118 -0
  100. package/docs/releases/v0.31.11.md +88 -0
  101. package/docs/releases/v0.31.12.md +105 -0
  102. package/docs/releases/v0.31.13.md +99 -0
  103. package/docs/releases/v0.31.14.md +97 -0
  104. package/docs/releases/v0.31.2.md +115 -0
  105. package/docs/releases/v0.31.3.md +142 -0
  106. package/docs/releases/v0.31.4.md +117 -0
  107. package/docs/releases/v0.31.5.md +155 -0
  108. package/docs/releases/v0.31.6.md +132 -0
  109. package/docs/releases/v0.31.7.md +132 -0
  110. package/docs/releases/v0.31.8.md +120 -0
  111. package/docs/releases/v0.31.9.md +120 -0
  112. package/docs/releases/v0.32.0.md +261 -0
  113. package/docs/releases/v0.32.1.md +59 -0
  114. package/docs/releases/v0.8.0.md +118 -0
  115. package/docs/releases/v0.9.0.md +98 -0
  116. package/docs/releases/v0.9.1.md +83 -0
  117. package/package.json +102 -6
  118. package/plugins/mirror-mind/.claude-plugin/plugin.json +13 -0
  119. package/plugins/mirror-mind/hooks/hooks.json +50 -0
  120. package/plugins/mirror-mind/hooks/log-session-end.sh +98 -0
  121. package/plugins/mirror-mind/hooks/log-user-prompt.sh +98 -0
  122. package/plugins/mirror-mind/hooks/mirror-inject.sh +98 -0
  123. package/plugins/mirror-mind/hooks/session-start.sh +98 -0
  124. package/plugins/mirror-mind/mcp/launch.sh +52 -0
  125. package/plugins/mirror-mind/skills/mm-backup/SKILL.md +27 -0
  126. package/plugins/mirror-mind/skills/mm-build/SKILL.md +1206 -0
  127. package/plugins/mirror-mind/skills/mm-consolidate/SKILL.md +112 -0
  128. package/plugins/mirror-mind/skills/mm-consult/SKILL.md +83 -0
  129. package/plugins/mirror-mind/skills/mm-conversations/SKILL.md +17 -0
  130. package/plugins/mirror-mind/skills/mm-discard/SKILL.md +40 -0
  131. package/plugins/mirror-mind/skills/mm-explore/SKILL.md +241 -0
  132. package/plugins/mirror-mind/skills/mm-help/SKILL.md +104 -0
  133. package/plugins/mirror-mind/skills/mm-identity/SKILL.md +96 -0
  134. package/plugins/mirror-mind/skills/mm-journal/SKILL.md +21 -0
  135. package/plugins/mirror-mind/skills/mm-journey/SKILL.md +37 -0
  136. package/plugins/mirror-mind/skills/mm-journeys/SKILL.md +15 -0
  137. package/plugins/mirror-mind/skills/mm-memories/SKILL.md +20 -0
  138. package/plugins/mirror-mind/skills/mm-mirror/SKILL.md +65 -0
  139. package/plugins/mirror-mind/skills/mm-mute/SKILL.md +18 -0
  140. package/plugins/mirror-mind/skills/mm-new/SKILL.md +18 -0
  141. package/plugins/mirror-mind/skills/mm-recall/SKILL.md +17 -0
  142. package/plugins/mirror-mind/skills/mm-release-notes/SKILL.md +31 -0
  143. package/plugins/mirror-mind/skills/mm-seed/SKILL.md +15 -0
  144. package/plugins/mirror-mind/skills/mm-shadow/SKILL.md +117 -0
  145. package/plugins/mirror-mind/skills/mm-soul/SKILL.md +488 -0
  146. package/plugins/mirror-mind/skills/mm-tasks/SKILL.md +36 -0
  147. package/plugins/mirror-mind/skills/mm-update/SKILL.md +30 -0
  148. package/plugins/mirror-mind/skills/mm-week/SKILL.md +46 -0
  149. package/plugins/mirror-mind/skills/mm-welcome/SKILL.md +22 -0
  150. package/scripts/codex-hooks/session-end.sh +85 -0
  151. package/scripts/codex-hooks/session-start.sh +85 -0
  152. package/scripts/codex-mirror.sh +48 -0
  153. package/templates/identity/README.md +119 -0
  154. package/templates/identity/ego/behavior.yaml +52 -0
  155. package/templates/identity/ego/constraints.yaml +15 -0
  156. package/templates/identity/ego/identity.yaml +43 -0
  157. package/templates/identity/journeys/personal-growth.yaml +39 -0
  158. package/templates/identity/organization/identity.yaml +24 -0
  159. package/templates/identity/organization/principles.yaml +17 -0
  160. package/templates/identity/personas/coach.yaml +94 -0
  161. package/templates/identity/personas/designer.yaml +93 -0
  162. package/templates/identity/personas/doctor.yaml +96 -0
  163. package/templates/identity/personas/engineer.yaml +77 -0
  164. package/templates/identity/personas/financial.yaml +97 -0
  165. package/templates/identity/personas/prompt-engineer.yaml +122 -0
  166. package/templates/identity/personas/researcher.yaml +88 -0
  167. package/templates/identity/personas/strategist.yaml +91 -0
  168. package/templates/identity/personas/teacher.yaml +95 -0
  169. package/templates/identity/personas/therapist.yaml +91 -0
  170. package/templates/identity/personas/thinker.yaml +74 -0
  171. package/templates/identity/personas/writer.yaml +76 -0
  172. package/templates/identity/self/config.yaml +16 -0
  173. package/templates/identity/self/soul.yaml +105 -0
  174. package/templates/identity/user/identity.yaml +30 -0
  175. package/ts/src/backup/staging.ts +62 -0
  176. package/ts/src/backup/zipBackup.ts +267 -0
  177. package/ts/src/backup/zipReader.ts +109 -0
  178. package/ts/src/backup/zipWriter.ts +148 -0
  179. package/ts/src/builder/activeCheckpoint.ts +134 -0
  180. package/ts/src/builder/aggregateStatus.ts +34 -0
  181. package/ts/src/builder/approve.ts +149 -0
  182. package/ts/src/builder/argv.ts +292 -0
  183. package/ts/src/builder/ariadMethod.ts +536 -0
  184. package/ts/src/builder/artifacts/artifactSurfaces.ts +106 -0
  185. package/ts/src/builder/artifacts/artifactWriter.ts +113 -0
  186. package/ts/src/builder/artifacts/closureArtifacts.ts +199 -0
  187. package/ts/src/builder/artifacts/deliveryStoryArtifacts.ts +316 -0
  188. package/ts/src/builder/artifacts/planArtifacts.ts +242 -0
  189. package/ts/src/builder/artifacts/scaffoldSections.ts +273 -0
  190. package/ts/src/builder/artifacts/scaffoldState.ts +227 -0
  191. package/ts/src/builder/artifacts/storyFiles.ts +61 -0
  192. package/ts/src/builder/artifacts/storyIndex.ts +166 -0
  193. package/ts/src/builder/cadence.ts +59 -0
  194. package/ts/src/builder/card.ts +192 -0
  195. package/ts/src/builder/checkpointRefused.ts +66 -0
  196. package/ts/src/builder/closure.ts +964 -0
  197. package/ts/src/builder/commands.ts +2053 -0
  198. package/ts/src/builder/cursorPosition.ts +176 -0
  199. package/ts/src/builder/cursorTransitions.ts +77 -0
  200. package/ts/src/builder/deliveryCursor.ts +577 -0
  201. package/ts/src/builder/deliveryStoryClosure.ts +521 -0
  202. package/ts/src/builder/deliveryStoryPlan.ts +536 -0
  203. package/ts/src/builder/deliveryStoryReady.ts +160 -0
  204. package/ts/src/builder/deliveryStoryRoadmapClosure.ts +151 -0
  205. package/ts/src/builder/expand.ts +336 -0
  206. package/ts/src/builder/flowUnit.ts +339 -0
  207. package/ts/src/builder/homeSurface.ts +122 -0
  208. package/ts/src/builder/implementationGuard.ts +125 -0
  209. package/ts/src/builder/index.ts +11 -0
  210. package/ts/src/builder/lifecycleRefusal.ts +92 -0
  211. package/ts/src/builder/lifecycleRibbon.ts +159 -0
  212. package/ts/src/builder/load.ts +313 -0
  213. package/ts/src/builder/methodAdoption.ts +113 -0
  214. package/ts/src/builder/methodDefinition.ts +429 -0
  215. package/ts/src/builder/methodInspection.ts +371 -0
  216. package/ts/src/builder/plan.ts +408 -0
  217. package/ts/src/builder/planPreauthorization.ts +265 -0
  218. package/ts/src/builder/prepare.ts +225 -0
  219. package/ts/src/builder/projectPaths.ts +64 -0
  220. package/ts/src/builder/pull.ts +356 -0
  221. package/ts/src/builder/pullCandidates.ts +463 -0
  222. package/ts/src/builder/pullCandidatesRender.ts +238 -0
  223. package/ts/src/builder/refinementField.ts +93 -0
  224. package/ts/src/builder/releaseIntent.ts +173 -0
  225. package/ts/src/builder/resumeState.ts +84 -0
  226. package/ts/src/builder/resumeSurface.ts +149 -0
  227. package/ts/src/builder/roadmapGrammar.ts +300 -0
  228. package/ts/src/builder/roadmapScan.ts +97 -0
  229. package/ts/src/builder/roadmapScope.ts +210 -0
  230. package/ts/src/builder/scopePhrases.ts +146 -0
  231. package/ts/src/builder/storyPaths.ts +196 -0
  232. package/ts/src/builder/storyPlanPreauthorization.ts +287 -0
  233. package/ts/src/builder/surfaceProtocol.ts +42 -0
  234. package/ts/src/builder/templateGeneration.ts +148 -0
  235. package/ts/src/builder/transition.ts +178 -0
  236. package/ts/src/consult/args.ts +99 -0
  237. package/ts/src/consult/core.ts +122 -0
  238. package/ts/src/consult/modelCatalog.ts +55 -0
  239. package/ts/src/consult/render.ts +47 -0
  240. package/ts/src/conversation/append.ts +414 -0
  241. package/ts/src/conversation/backfill.ts +387 -0
  242. package/ts/src/conversation/closeTail.ts +467 -0
  243. package/ts/src/conversation/extraction.ts +333 -0
  244. package/ts/src/conversation/extractionDriver.ts +130 -0
  245. package/ts/src/conversation/extractionRun.ts +70 -0
  246. package/ts/src/conversation/journeyInference.ts +165 -0
  247. package/ts/src/conversation/journeyRepair.ts +114 -0
  248. package/ts/src/conversation/lifecycleFaces.ts +197 -0
  249. package/ts/src/conversation/lifecycleWrites.ts +313 -0
  250. package/ts/src/conversation/listing.ts +77 -0
  251. package/ts/src/conversation/logger.ts +576 -0
  252. package/ts/src/conversation/loggerCli.ts +282 -0
  253. package/ts/src/conversation/loggerRuntime.ts +259 -0
  254. package/ts/src/conversation/metadataLifecycle.ts +443 -0
  255. package/ts/src/conversation/recall.ts +66 -0
  256. package/ts/src/conversation/sessionComposites.ts +258 -0
  257. package/ts/src/conversation/sessionImport.ts +90 -0
  258. package/ts/src/conversation/transcriptBackfill.ts +141 -0
  259. package/ts/src/cultivation/applyActions.ts +241 -0
  260. package/ts/src/cultivation/cluster.ts +86 -0
  261. package/ts/src/cultivation/consolidationStore.ts +281 -0
  262. package/ts/src/cultivation/promptContext.ts +62 -0
  263. package/ts/src/cultivation/propose.ts +305 -0
  264. package/ts/src/cultivation/scan.ts +155 -0
  265. package/ts/src/db/backupGate.ts +47 -0
  266. package/ts/src/db/bootstrap.ts +108 -0
  267. package/ts/src/db/bootstrapLock.ts +223 -0
  268. package/ts/src/db/copyGuard.ts +60 -0
  269. package/ts/src/db/database.ts +397 -0
  270. package/ts/src/db/decode.ts +66 -0
  271. package/ts/src/db/ftsIntegrity.ts +41 -0
  272. package/ts/src/db/migrateOnOpen.ts +214 -0
  273. package/ts/src/db/migrations.ts +576 -0
  274. package/ts/src/db/readyOnOpen.ts +35 -0
  275. package/ts/src/db/rowDecode.ts +42 -0
  276. package/ts/src/db/schema.ts +432 -0
  277. package/ts/src/db/schemaInventory.ts +225 -0
  278. package/ts/src/db/schemaInventorySnapshot.ts +2107 -0
  279. package/ts/src/db/schemaState.ts +91 -0
  280. package/ts/src/db/schemaTsDivergence.ts +114 -0
  281. package/ts/src/descriptor/descriptorRead.ts +49 -0
  282. package/ts/src/docs/docsLint.ts +265 -0
  283. package/ts/src/explorer/handoff.ts +522 -0
  284. package/ts/src/explorer/render.ts +335 -0
  285. package/ts/src/explorer/story.ts +769 -0
  286. package/ts/src/explorer/transition.ts +72 -0
  287. package/ts/src/extensions/bindings.ts +170 -0
  288. package/ts/src/extensions/catalog.ts +249 -0
  289. package/ts/src/extensions/catalogCommands.ts +513 -0
  290. package/ts/src/extensions/catalogWrites.ts +652 -0
  291. package/ts/src/extensions/contextRuntime.ts +325 -0
  292. package/ts/src/extensions/dispatch.ts +374 -0
  293. package/ts/src/extensions/errors.ts +24 -0
  294. package/ts/src/extensions/manifest.ts +356 -0
  295. package/ts/src/extensions/migrations.ts +350 -0
  296. package/ts/src/extraction/conversation.ts +304 -0
  297. package/ts/src/extraction/conversationMetadata.ts +206 -0
  298. package/ts/src/extraction/fencing.ts +106 -0
  299. package/ts/src/extraction/json.ts +17 -0
  300. package/ts/src/extraction/prompts.ts +470 -0
  301. package/ts/src/frontDoor/args.ts +30 -0
  302. package/ts/src/frontDoor/argvShape.ts +106 -0
  303. package/ts/src/frontDoor/buildLoadRuntime.ts +139 -0
  304. package/ts/src/frontDoor/buildRoute.ts +398 -0
  305. package/ts/src/frontDoor/cli.ts +1865 -0
  306. package/ts/src/frontDoor/cloneRoleGuard.ts +98 -0
  307. package/ts/src/frontDoor/consultRoute.ts +50 -0
  308. package/ts/src/frontDoor/contentTailRoute.ts +239 -0
  309. package/ts/src/frontDoor/conversationLoggerRoute.ts +73 -0
  310. package/ts/src/frontDoor/cultivationRoute.ts +210 -0
  311. package/ts/src/frontDoor/dbPath.ts +131 -0
  312. package/ts/src/frontDoor/dbSafetyToolsRoute.ts +240 -0
  313. package/ts/src/frontDoor/exploreRoute.ts +572 -0
  314. package/ts/src/frontDoor/extensionCatalogRoute.ts +252 -0
  315. package/ts/src/frontDoor/frontDoorLog.ts +57 -0
  316. package/ts/src/frontDoor/identityWrite.ts +36 -0
  317. package/ts/src/frontDoor/journeyWriteRoute.ts +26 -0
  318. package/ts/src/frontDoor/lifecycleWriteRoute.ts +110 -0
  319. package/ts/src/frontDoor/liveBackup.ts +100 -0
  320. package/ts/src/frontDoor/mirrorModeRoute.ts +163 -0
  321. package/ts/src/frontDoor/namedRefusal.ts +35 -0
  322. package/ts/src/frontDoor/nodeSupport.ts +24 -0
  323. package/ts/src/frontDoor/render/consolidate.ts +199 -0
  324. package/ts/src/frontDoor/render/conversations.ts +33 -0
  325. package/ts/src/frontDoor/render/cultivationShared.ts +21 -0
  326. package/ts/src/frontDoor/render/descriptor.ts +20 -0
  327. package/ts/src/frontDoor/render/detectPersona.ts +50 -0
  328. package/ts/src/frontDoor/render/icons.ts +13 -0
  329. package/ts/src/frontDoor/render/identity.ts +59 -0
  330. package/ts/src/frontDoor/render/identityRows.ts +17 -0
  331. package/ts/src/frontDoor/render/inspectPersona.ts +53 -0
  332. package/ts/src/frontDoor/render/journeyStatus.ts +78 -0
  333. package/ts/src/frontDoor/render/journeys.ts +79 -0
  334. package/ts/src/frontDoor/render/list.ts +41 -0
  335. package/ts/src/frontDoor/render/memories.ts +67 -0
  336. package/ts/src/frontDoor/render/recall.ts +49 -0
  337. package/ts/src/frontDoor/render/shadow.ts +155 -0
  338. package/ts/src/frontDoor/render/tasks.ts +99 -0
  339. package/ts/src/frontDoor/render/tasksImportSync.ts +63 -0
  340. package/ts/src/frontDoor/render/week.ts +113 -0
  341. package/ts/src/frontDoor/routing.ts +999 -0
  342. package/ts/src/frontDoor/runtimeRoute.ts +623 -0
  343. package/ts/src/frontDoor/searchRoute.ts +166 -0
  344. package/ts/src/frontDoor/seedPaths.ts +98 -0
  345. package/ts/src/frontDoor/soulRoute.ts +433 -0
  346. package/ts/src/frontDoor/tasksImportSyncRoute.ts +110 -0
  347. package/ts/src/frontDoor/tasksWriteRoute.ts +101 -0
  348. package/ts/src/frontDoor/usage.ts +176 -0
  349. package/ts/src/guards/claudePlugin.ts +252 -0
  350. package/ts/src/guards/packContents.ts +306 -0
  351. package/ts/src/guards/piSourcedSkills.ts +70 -0
  352. package/ts/src/guards/retiredRouteShadows.ts +57 -0
  353. package/ts/src/guards/retiredSurfaces.ts +564 -0
  354. package/ts/src/hooks/claude.ts +176 -0
  355. package/ts/src/hooks/codex.ts +45 -0
  356. package/ts/src/hooks/gemini.ts +126 -0
  357. package/ts/src/hooks/main.ts +109 -0
  358. package/ts/src/hooks/mirrorState.ts +71 -0
  359. package/ts/src/hooks/payload.ts +68 -0
  360. package/ts/src/hooks/runtime.ts +159 -0
  361. package/ts/src/identity/applyConsolidationIdentityUpdate.ts +67 -0
  362. package/ts/src/identity/descriptorGenerate.ts +162 -0
  363. package/ts/src/identity/identityEdit.ts +143 -0
  364. package/ts/src/identity/identityKey.ts +77 -0
  365. package/ts/src/identity/identityRead.ts +69 -0
  366. package/ts/src/identity/identityStore.ts +80 -0
  367. package/ts/src/identity/journeyListing.ts +53 -0
  368. package/ts/src/identity/personaInspect.ts +44 -0
  369. package/ts/src/identity/personaListing.ts +37 -0
  370. package/ts/src/identity/setIdentity.ts +64 -0
  371. package/ts/src/index.ts +231 -0
  372. package/ts/src/init/init.ts +111 -0
  373. package/ts/src/journey/journeyOptions.ts +131 -0
  374. package/ts/src/journey/journeyRemoval.ts +158 -0
  375. package/ts/src/journey/journeyStatus.ts +149 -0
  376. package/ts/src/journey/journeySyncFile.ts +87 -0
  377. package/ts/src/journey/journeyWrite.ts +203 -0
  378. package/ts/src/journey/parentJourney.ts +41 -0
  379. package/ts/src/journey/validateParentJourney.ts +55 -0
  380. package/ts/src/mcp/boundary.ts +151 -0
  381. package/ts/src/mcp/guards.ts +244 -0
  382. package/ts/src/mcp/main.ts +121 -0
  383. package/ts/src/mcp/payload.ts +141 -0
  384. package/ts/src/mcp/protocol.ts +141 -0
  385. package/ts/src/mcp/readModels.ts +241 -0
  386. package/ts/src/mcp/registry.ts +201 -0
  387. package/ts/src/mcp/serve.ts +120 -0
  388. package/ts/src/mcp/tools/deterministic.ts +137 -0
  389. package/ts/src/mcp/tools/providerCrossing.ts +187 -0
  390. package/ts/src/mcp/wire.ts +8 -0
  391. package/ts/src/memory/journal.ts +180 -0
  392. package/ts/src/memory/listing.ts +107 -0
  393. package/ts/src/memory/memoryWrite.ts +88 -0
  394. package/ts/src/memory/reinforcement.ts +35 -0
  395. package/ts/src/mirror/context.ts +161 -0
  396. package/ts/src/mirror/defaultResolution.ts +199 -0
  397. package/ts/src/mirror/orchestration.ts +364 -0
  398. package/ts/src/mirror/reception.ts +194 -0
  399. package/ts/src/mirror/render.ts +75 -0
  400. package/ts/src/mirror/runtimeSession.ts +205 -0
  401. package/ts/src/mode/operatingMode.ts +148 -0
  402. package/ts/src/observability/callOutcome.ts +115 -0
  403. package/ts/src/observability/inspectLedger.ts +329 -0
  404. package/ts/src/observability/ledgerHooks.ts +126 -0
  405. package/ts/src/observability/llmCalls.ts +154 -0
  406. package/ts/src/persona/detectPersona.ts +81 -0
  407. package/ts/src/planning/promptAssembly.ts +111 -0
  408. package/ts/src/planning/weekPending.ts +81 -0
  409. package/ts/src/planning/weekPlan.ts +183 -0
  410. package/ts/src/planning/weekSave.ts +117 -0
  411. package/ts/src/providers/config.ts +189 -0
  412. package/ts/src/providers/cost.ts +64 -0
  413. package/ts/src/providers/credits.ts +204 -0
  414. package/ts/src/providers/embedding.ts +321 -0
  415. package/ts/src/providers/familyProviders.ts +147 -0
  416. package/ts/src/providers/llm.ts +334 -0
  417. package/ts/src/providers/openrouter.ts +244 -0
  418. package/ts/src/providers/redaction.ts +110 -0
  419. package/ts/src/providers/replay.ts +15 -0
  420. package/ts/src/providers/transport.ts +410 -0
  421. package/ts/src/repair/encodingRepair.ts +253 -0
  422. package/ts/src/runtime/backup.ts +151 -0
  423. package/ts/src/runtime/channel.ts +146 -0
  424. package/ts/src/runtime/config.ts +168 -0
  425. package/ts/src/runtime/diagnose.ts +686 -0
  426. package/ts/src/runtime/git.ts +510 -0
  427. package/ts/src/runtime/installKind.ts +220 -0
  428. package/ts/src/runtime/migrate.ts +154 -0
  429. package/ts/src/runtime/packageIdentity.ts +102 -0
  430. package/ts/src/runtime/releaseNotes.ts +244 -0
  431. package/ts/src/runtime/status.ts +557 -0
  432. package/ts/src/runtime/strategies/clone.ts +200 -0
  433. package/ts/src/runtime/strategies/package.ts +328 -0
  434. package/ts/src/runtime/treeRoot.ts +30 -0
  435. package/ts/src/runtime/update.ts +445 -0
  436. package/ts/src/runtime/updateGate.ts +40 -0
  437. package/ts/src/runtime/updatePipeline.ts +159 -0
  438. package/ts/src/runtime/version.ts +36 -0
  439. package/ts/src/runtime/wiring.ts +130 -0
  440. package/ts/src/search/memorySearch.ts +338 -0
  441. package/ts/src/search/ranker.ts +154 -0
  442. package/ts/src/seed/seed.ts +256 -0
  443. package/ts/src/seed/seedYaml.ts +133 -0
  444. package/ts/src/soul/apply.ts +218 -0
  445. package/ts/src/soul/harvest.ts +267 -0
  446. package/ts/src/soul/prompts/soul_beauty_voice.md +103 -0
  447. package/ts/src/soul/prompts/soul_self_voice.md +219 -0
  448. package/ts/src/soul/prompts/soul_wisdom_voice.md +121 -0
  449. package/ts/src/soul/prompts.ts +85 -0
  450. package/ts/src/soul/render.ts +358 -0
  451. package/ts/src/soul/state.ts +212 -0
  452. package/ts/src/soul/transition.ts +37 -0
  453. package/ts/src/tasks/journeyPathParse.ts +113 -0
  454. package/ts/src/tasks/taskImportSync.ts +142 -0
  455. package/ts/src/tasks/taskStore.ts +312 -0
  456. package/ts/src/tasks/weekView.ts +120 -0
  457. package/ts/src/util/clipCodePoints.ts +26 -0
  458. package/ts/src/util/paths.ts +63 -0
  459. package/ts/src/util/program.ts +9 -0
  460. package/ts/src/util/pyGenerators.ts +144 -0
  461. package/ts/src/util/pythonText.ts +317 -0
  462. package/ts/src/util/semver.ts +45 -0
  463. package/ts/src/util/shellWord.ts +25 -0
  464. package/ts/src/util/slug.ts +64 -0
  465. package/ts/src/welcome/card.ts +339 -0
  466. package/ts/src/welcome/statusLine.ts +101 -0
  467. package/ts/src/welcome/updateCache.ts +129 -0
@@ -0,0 +1,350 @@
1
+ // Read side of the extension SQL migration runner (CV22.DS7.TS3 plateau 3a).
2
+ // Port of the read half of `src/memory/extensions/migrations.py`.
3
+ //
4
+ // `runtime status` and `runtime diagnose` need to answer one question per
5
+ // installed command-skill: are this extension's migration files pending,
6
+ // drifted, or clean? That is `inspect_migration_files`, the read-only
7
+ // companion to `run_migrations`. Only the read half is ported here.
8
+ //
9
+ // The WRITE half (`run_migrations`, `_split_statements`,
10
+ // `_extract_table_targets`, `_validate_prefix`) is deliberately absent. It
11
+ // belongs to the extension catalog, which the plan assigns to CV22.DS7.TS4;
12
+ // this module is placed where TS4 extends it rather than moves it.
13
+ //
14
+ // The checksum contract (docs/product/extensions/migrations.md): two files
15
+ // differing only in comments or whitespace hash the same; any structural
16
+ // difference, string literals included, does not. Both cores must agree
17
+ // exactly, or one of them cries drift on a file the other calls clean.
18
+
19
+ import { createHash } from "node:crypto";
20
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
21
+ import { join } from "node:path";
22
+ import type { Database, WritableDatabase } from "#db/database.ts";
23
+ import { PYTHON_WHITESPACE_CLASS, pyStrip, sortByCodePoint } from "#util/pythonText.ts";
24
+ import { ExtensionMigrationError } from "./errors.ts";
25
+
26
+ /**
27
+ * Port of `_FILENAME_RE`. Python's `\d` matches the whole Unicode `Nd`
28
+ * category, not `[0-9]`, so `\p{Nd}` is the faithful class -- a filename
29
+ * numbered with Arabic-Indic digits is accepted by both cores or by neither.
30
+ */
31
+ const FILENAME_RE = /^\p{Nd}{3,}_[a-z0-9_]+\.sql$/u;
32
+
33
+ const STRIP_COMMENTS_RE = /--[^\n]*|\/\*[\s\S]*?\*\//g;
34
+ const COLLAPSE_WHITESPACE_RE = new RegExp(`[${PYTHON_WHITESPACE_CLASS}]+`, "gu");
35
+ const TRIM_AROUND_PUNCT_RE = new RegExp(
36
+ `[${PYTHON_WHITESPACE_CLASS}]*([(),;])[${PYTHON_WHITESPACE_CLASS}]*`,
37
+ "gu",
38
+ );
39
+
40
+ /** Port of `table_prefix_for`: the `ext_<id>_` namespace an extension owns. */
41
+ export function tablePrefixFor(extensionId: string): string {
42
+ return `ext_${extensionId.replaceAll("-", "_")}_`;
43
+ }
44
+
45
+ /**
46
+ * Port of `_normalise_for_checksum`: drop comments, collapse whitespace runs,
47
+ * and close the gaps around `(),;` so the same statement reformatted across
48
+ * indented lines hashes identically. String literals survive -- an `INSERT`
49
+ * value is real SQL content, and editing one must trip the drift guard.
50
+ */
51
+ export function normaliseForChecksum(sql: string): string {
52
+ const withoutComments = sql.replace(STRIP_COMMENTS_RE, "");
53
+ const collapsed = withoutComments.replace(COLLAPSE_WHITESPACE_RE, " ");
54
+ return pyStrip(collapsed.replace(TRIM_AROUND_PUNCT_RE, "$1"));
55
+ }
56
+
57
+ /** Port of `_checksum`: SHA-256 over the normalised SQL. */
58
+ export function checksum(content: string): string {
59
+ return createHash("sha256").update(normaliseForChecksum(content), "utf8").digest("hex");
60
+ }
61
+
62
+ /**
63
+ * Port of `_raw_checksum`: SHA-256 over the bytes as written. Backwards
64
+ * compatibility only -- rows recorded before CV14.E2.S3 carry this hash, and
65
+ * matching it keeps existing installs from reporting drift on a file nobody
66
+ * touched.
67
+ */
68
+ export function rawChecksum(content: string): string {
69
+ return createHash("sha256").update(content, "utf8").digest("hex");
70
+ }
71
+
72
+ /**
73
+ * Port of `_list_migration_files`. Non-`.sql` entries are skipped in silence;
74
+ * a `.sql` file that breaks the naming contract raises, because a migration
75
+ * the runner cannot order is a defect, not a file to ignore.
76
+ */
77
+ export function listMigrationFiles(migrationsDir: string, extensionId: string): string[] {
78
+ if (!existsSync(migrationsDir)) return [];
79
+ const names = sortByCodePoint(readdirSync(migrationsDir));
80
+ const files: string[] = [];
81
+ for (const name of names) {
82
+ const full = join(migrationsDir, name);
83
+ if (!statSync(full).isFile()) continue;
84
+ // `Path.suffix` is empty for a dotfile with no other dot, so `.sql`
85
+ // itself is not a `.sql` file to either core.
86
+ const dot = name.lastIndexOf(".");
87
+ if (dot <= 0 || name.slice(dot) !== ".sql") continue;
88
+ if (!FILENAME_RE.test(name)) {
89
+ throw new ExtensionMigrationError(
90
+ `invalid migration filename: ${name} (expected ^[0-9]{3,}_[a-z0-9_]+\\.sql$)`,
91
+ extensionId,
92
+ );
93
+ }
94
+ files.push(name);
95
+ }
96
+ return files;
97
+ }
98
+
99
+ /** What `inspect_migration_files` returns: two ordered filename lists. */
100
+ export interface MigrationInspection {
101
+ pending: string[];
102
+ drifted: string[];
103
+ }
104
+
105
+ /**
106
+ * Port of `inspect_migration_files`: which files have never been applied, and
107
+ * which were applied and have since changed. Never writes bookkeeping and
108
+ * never runs a statement out of a migration file.
109
+ */
110
+ export function inspectMigrationFiles(
111
+ db: Database,
112
+ extensionId: string,
113
+ migrationsDir: string,
114
+ ): MigrationInspection {
115
+ const files = listMigrationFiles(migrationsDir, extensionId);
116
+ const applied = db.prepare(
117
+ "SELECT checksum FROM _ext_migrations WHERE extension_id = ? AND filename = ?",
118
+ );
119
+ const pending: string[] = [];
120
+ const drifted: string[] = [];
121
+ for (const name of files) {
122
+ const content = readFileSync(join(migrationsDir, name), "utf8");
123
+ const row = applied.get(extensionId, name);
124
+ if (row === undefined) {
125
+ pending.push(name);
126
+ continue;
127
+ }
128
+ const recorded = row.checksum as string;
129
+ if (recorded === checksum(content) || recorded === rawChecksum(content)) continue;
130
+ drifted.push(name);
131
+ }
132
+ return { pending, drifted };
133
+ }
134
+
135
+ // --- the WRITE half (CV22.DS7.TS4 plateau 3) ----------------------------------
136
+ //
137
+ // TS3 ported the read half and said: "The WRITE half (`run_migrations`,
138
+ // `_split_statements`, `_extract_table_targets`, `_validate_prefix`) is
139
+ // deliberately absent. It belongs to the extension catalog, which the plan
140
+ // assigns to CV22.DS7.TS4; this module is placed where TS4 extends it rather
141
+ // than moves it." This is that extension.
142
+
143
+ const DDL_TABLE_RE = new RegExp(
144
+ "\\b(?:" +
145
+ "CREATE\\s+(?:VIRTUAL\\s+)?TABLE|" +
146
+ "ALTER\\s+TABLE|" +
147
+ "DROP\\s+TABLE|" +
148
+ "INSERT\\s+(?:OR\\s+\\w+\\s+)?INTO|" +
149
+ "UPDATE|" +
150
+ "DELETE\\s+FROM" +
151
+ ")" +
152
+ '\\s+(?:IF\\s+(?:NOT\\s+)?EXISTS\\s+)?"?([A-Za-z_][A-Za-z0-9_]*)"?',
153
+ "gi",
154
+ );
155
+
156
+ const INDEX_TABLE_RE = new RegExp(
157
+ "\\bCREATE\\s+(?:UNIQUE\\s+)?INDEX\\s+" +
158
+ "(?:IF\\s+NOT\\s+EXISTS\\s+)?" +
159
+ '(?:"?[A-Za-z_][A-Za-z0-9_]*"?\\s+)?' +
160
+ 'ON\\s+"?([A-Za-z_][A-Za-z0-9_]*)"?',
161
+ "gi",
162
+ );
163
+
164
+ const STRIP_STRINGS_RE = /'(?:[^']|'')*'/g;
165
+
166
+ /** Port of `_strip_noise`: comments out, string literals flattened to `''`. */
167
+ function stripNoise(sql: string): string {
168
+ return sql.replace(STRIP_COMMENTS_RE, "").replace(STRIP_STRINGS_RE, "''");
169
+ }
170
+
171
+ /**
172
+ * Port of `_split_statements`.
173
+ *
174
+ * Written as a character walk, exactly as Python's is, because the shape of
175
+ * the bug it prevents is structural: `executescript` commits any open
176
+ * transaction, so the runner splits the script itself and feeds statements
177
+ * through one explicit savepoint. A regex split on `;` would cut inside a
178
+ * string literal -- the database-architect's plan-stage dissent, and the
179
+ * reason the corpus stages `INSERT ... VALUES ('a;b')` and `'it''s'`.
180
+ *
181
+ * Compound statements (`BEGIN ... END`) are NOT supported, in either core.
182
+ */
183
+ export function splitStatements(sql: string): string[] {
184
+ const characters = Array.from(sql);
185
+ const statements: string[] = [];
186
+ let buffer = "";
187
+ let index = 0;
188
+ while (index < characters.length) {
189
+ const character = characters[index] as string;
190
+ if (character === "-" && characters[index + 1] === "-") {
191
+ const end = characters.indexOf("\n", index);
192
+ index = end === -1 ? characters.length : end;
193
+ continue;
194
+ }
195
+ if (character === "/" && characters[index + 1] === "*") {
196
+ const end = sqlIndexOf(characters, "*/", index + 2);
197
+ index = end === -1 ? characters.length : end + 2;
198
+ continue;
199
+ }
200
+ if (character === "'") {
201
+ buffer += character;
202
+ index += 1;
203
+ while (index < characters.length) {
204
+ const inner = characters[index] as string;
205
+ buffer += inner;
206
+ if (inner === "'") {
207
+ if (characters[index + 1] === "'") {
208
+ buffer += characters[index + 1];
209
+ index += 2;
210
+ continue;
211
+ }
212
+ index += 1;
213
+ break;
214
+ }
215
+ index += 1;
216
+ }
217
+ continue;
218
+ }
219
+ if (character === ";") {
220
+ const statement = pyStrip(buffer);
221
+ if (statement) statements.push(statement);
222
+ buffer = "";
223
+ index += 1;
224
+ continue;
225
+ }
226
+ buffer += character;
227
+ index += 1;
228
+ }
229
+ const tail = pyStrip(buffer);
230
+ if (tail) statements.push(tail);
231
+ return statements;
232
+ }
233
+
234
+ /** `str.find(needle, start)` over a code-point array. */
235
+ function sqlIndexOf(characters: readonly string[], needle: string, start: number): number {
236
+ const parts = Array.from(needle);
237
+ for (let index = start; index + parts.length <= characters.length; index += 1) {
238
+ if (parts.every((part, offset) => characters[index + offset] === part)) return index;
239
+ }
240
+ return -1;
241
+ }
242
+
243
+ /** Port of `_extract_table_targets`: every table a statement reads or writes. */
244
+ export function extractTableTargets(sql: string): string[] {
245
+ const cleaned = stripNoise(sql);
246
+ const tables: string[] = [];
247
+ for (const match of cleaned.matchAll(DDL_TABLE_RE))
248
+ tables.push((match[1] as string).toLowerCase());
249
+ for (const match of cleaned.matchAll(INDEX_TABLE_RE)) {
250
+ tables.push((match[1] as string).toLowerCase());
251
+ }
252
+ return tables;
253
+ }
254
+
255
+ /** Port of `_validate_prefix`: an extension may only touch its own namespace. */
256
+ export function validatePrefix(content: string, extensionId: string, filename: string): void {
257
+ const prefix = tablePrefixFor(extensionId);
258
+ for (const table of extractTableTargets(content)) {
259
+ if (!table.startsWith(prefix)) {
260
+ throw new ExtensionMigrationError(
261
+ `${filename} targets table '${table}' outside the required prefix '${prefix}*'`,
262
+ extensionId,
263
+ );
264
+ }
265
+ }
266
+ }
267
+
268
+ export interface RunMigrationsDeps {
269
+ /** Python's `_now()`: `datetime.now(timezone.utc).isoformat()`. */
270
+ readonly nowIso: () => string;
271
+ }
272
+
273
+ /**
274
+ * Port of `run_migrations`. Returns the number of newly applied files.
275
+ *
276
+ * Atomicity is a SAVEPOINT, not a transaction, and that is deliberate in both
277
+ * cores: SQLite implicitly commits an open deferred transaction before DDL, so
278
+ * `BEGIN`/`ROLLBACK` would not undo a half-applied `CREATE TABLE`. A savepoint
279
+ * does, bookkeeping row included.
280
+ */
281
+ export function runMigrations(
282
+ db: WritableDatabase,
283
+ extensionId: string,
284
+ migrationsDir: string,
285
+ deps: RunMigrationsDeps,
286
+ ): number {
287
+ const files = listMigrationFiles(migrationsDir, extensionId);
288
+ let applied = 0;
289
+ for (const name of files) {
290
+ const content = readFileSync(join(migrationsDir, name), "utf8");
291
+ const current = checksum(content);
292
+ const row = db
293
+ .prepare("SELECT checksum FROM _ext_migrations WHERE extension_id = ? AND filename = ?")
294
+ .get(extensionId, name);
295
+
296
+ if (row !== undefined) {
297
+ const recorded = row.checksum as string;
298
+ if (recorded === current) continue;
299
+ if (recorded === rawChecksum(content)) {
300
+ // A pre-CV14.E2.S3 row that still matches the file as written: upgrade
301
+ // the stored hash so the next run takes the fast path.
302
+ db.prepare(
303
+ "UPDATE _ext_migrations SET checksum = ? WHERE extension_id = ? AND filename = ?",
304
+ ).run(current, extensionId, name);
305
+ continue;
306
+ }
307
+ throw new ExtensionMigrationError(
308
+ `${name} has been edited since it was applied ` +
309
+ `(recorded checksum ${recorded.slice(0, 8)}..., ` +
310
+ `current checksum ${current.slice(0, 8)}...); never edit an ` +
311
+ "applied migration in ways that change its SQL semantics " +
312
+ "— comment and whitespace edits are tolerated, but a " +
313
+ "structural change requires a new migration file",
314
+ extensionId,
315
+ );
316
+ }
317
+
318
+ validatePrefix(content, extensionId, name);
319
+
320
+ const savepoint = `ext_migration_${extensionId.replaceAll("-", "_")}`;
321
+ db.exec(`SAVEPOINT ${savepoint}`);
322
+ try {
323
+ for (const statement of splitStatements(content)) db.exec(statement);
324
+ db.prepare(
325
+ "INSERT INTO _ext_migrations (extension_id, filename, checksum, applied_at) " +
326
+ "VALUES (?, ?, ?, ?)",
327
+ ).run(extensionId, name, current, deps.nowIso());
328
+ } catch (error) {
329
+ db.exec(`ROLLBACK TO SAVEPOINT ${savepoint}`);
330
+ db.exec(`RELEASE SAVEPOINT ${savepoint}`);
331
+ throw new ExtensionMigrationError(
332
+ `${name} failed to apply: ${sqliteMessage(error)}`,
333
+ extensionId,
334
+ );
335
+ }
336
+ db.exec(`RELEASE SAVEPOINT ${savepoint}`);
337
+ applied += 1;
338
+ }
339
+ return applied;
340
+ }
341
+
342
+ /**
343
+ * Python renders `sqlite3.Error` as its message alone. `node:sqlite` prefixes
344
+ * its own errors differently, so the text after `failed to apply:` is a
345
+ * RECORDED DIVERGENCE: the golden pins the stable prefix and the exit code,
346
+ * never the driver's sentence.
347
+ */
348
+ function sqliteMessage(error: unknown): string {
349
+ return error instanceof Error ? error.message : String(error);
350
+ }
@@ -0,0 +1,304 @@
1
+ import { resolveExtractionModel } from "#providers/config.ts";
2
+ import type { LlmProvider, LlmResponse } from "#providers/llm.ts";
3
+ import {
4
+ fenceTranscript,
5
+ MAX_MEMORIES_PER_CONVERSATION,
6
+ MAX_TASKS_PER_CONVERSATION,
7
+ type SanitizeDropped,
8
+ sanitizeExtracted,
9
+ } from "./fencing.ts";
10
+ import { parseJsonResponse } from "./json.ts";
11
+ import { CURATION_PROMPT, EXTRACTION_PROMPT, TASK_EXTRACTION_PROMPT } from "./prompts.ts";
12
+
13
+ export interface ExtractionMessage {
14
+ role: string;
15
+ content: string;
16
+ }
17
+
18
+ export interface ExtractedMemory {
19
+ title: string;
20
+ content: string;
21
+ context: string | null;
22
+ memory_type: string;
23
+ layer: string;
24
+ tags: string[];
25
+ journey: string | null;
26
+ persona: string | null;
27
+ }
28
+
29
+ export interface ExistingMemoryForCuration {
30
+ title: string;
31
+ content: string;
32
+ memory_type: string;
33
+ layer: string;
34
+ }
35
+
36
+ export interface ExtractedTask {
37
+ title: string;
38
+ due_date: string | null;
39
+ journey: string | null;
40
+ stage: string | null;
41
+ context: string | null;
42
+ }
43
+
44
+ export function formatTranscript(
45
+ messages: readonly ExtractionMessage[],
46
+ userName = "User",
47
+ ): string {
48
+ return messages
49
+ .map((message) => {
50
+ const role = message.role === "user" ? userName : "Mirror";
51
+ return `**${role}:** ${message.content}`;
52
+ })
53
+ .join("\n\n");
54
+ }
55
+
56
+ export function naiveSummary(messages: readonly ExtractionMessage[]): string {
57
+ return messages
58
+ .filter((message) => message.role === "user" || message.role === "assistant")
59
+ .map((message) => message.content.slice(0, 500))
60
+ .join(" ")
61
+ .slice(0, 2000);
62
+ }
63
+
64
+ export type ExtractionStatusValue = "parse_failed" | "no_signal" | "ok";
65
+
66
+ export interface ExtractionStatus {
67
+ status: ExtractionStatusValue;
68
+ dropped?: SanitizeDropped;
69
+ }
70
+
71
+ export interface ExtractMemoriesOutcome {
72
+ memories: ExtractedMemory[];
73
+ status: ExtractionStatus;
74
+ }
75
+
76
+ /**
77
+ * Extracts memories and reports why the result is what it is, mirroring
78
+ * Python's `extract_memories(status=...)` (AI-10, CV9.E2.S16). A caller can
79
+ * distinguish unreadable model output (parse_failed) from a genuinely empty
80
+ * result (no_signal) instead of both looking identical to an empty array.
81
+ *
82
+ * `"llm_failed"` is deliberately not part of `ExtractionStatusValue`: this
83
+ * function cannot structurally report it -- that value only exists when the
84
+ * call throws, which is the orchestration layer's (`runConversationExtraction`)
85
+ * concern, not this function's return shape.
86
+ *
87
+ * Documented divergence: Python's status dict is left untouched (no key set
88
+ * at all) for a truly-empty messages call -- a mutate-if-present out-param
89
+ * behavior this return-based design cannot replicate exactly. This returns
90
+ * `{ status: "no_signal" }` instead. Zero observable impact:
91
+ * `runConversationExtraction` already guards `messages.length < 4` before
92
+ * ever calling this, so the path is unreachable from the real orchestration
93
+ * call site.
94
+ */
95
+ /**
96
+ * The ledger seam Python's `on_llm_call` parameter provides: invoked once per
97
+ * successful model call with the response and the assembled prompt, before
98
+ * the response is parsed. Never invoked when the call throws.
99
+ */
100
+ export type OnLlmCall = (response: LlmResponse, prompt: string) => void;
101
+
102
+ export async function extractMemoriesWithStatus(
103
+ provider: LlmProvider,
104
+ messages: readonly ExtractionMessage[],
105
+ options: {
106
+ persona?: string | null;
107
+ journey?: string | null;
108
+ userName?: string;
109
+ onLlmCall?: OnLlmCall;
110
+ } = {},
111
+ ): Promise<ExtractMemoriesOutcome> {
112
+ if (messages.length === 0) return { memories: [], status: { status: "no_signal" } };
113
+ const prompt = buildExtractionPrompt(messages, options.userName ?? "User");
114
+ const response = await provider.complete({
115
+ role: "extraction",
116
+ prompt,
117
+ model: resolveExtractionModel(),
118
+ temperature: 0.3,
119
+ });
120
+ options.onLlmCall?.(response, prompt);
121
+ const data = parseJsonResponse(response.content);
122
+ if (!Array.isArray(data)) {
123
+ return { memories: [], status: { status: "parse_failed" } };
124
+ }
125
+ const memories: ExtractedMemory[] = [];
126
+ for (const item of data) {
127
+ const memory = toExtractedMemory(item, options);
128
+ if (memory) memories.push(memory);
129
+ }
130
+ const { kept, dropped } = sanitizeExtracted(memories, MAX_MEMORIES_PER_CONVERSATION);
131
+ return { memories: kept, status: { status: kept.length > 0 ? "ok" : "no_signal", dropped } };
132
+ }
133
+
134
+ /** Thin, non-breaking wrapper over `extractMemoriesWithStatus`, returning only
135
+ * the memories array for existing callers -- mirrors Python's `extract_memories`
136
+ * default (`status=None`) shape. */
137
+ export async function extractMemories(
138
+ provider: LlmProvider,
139
+ messages: readonly ExtractionMessage[],
140
+ options: { persona?: string | null; journey?: string | null; userName?: string } = {},
141
+ ): Promise<ExtractedMemory[]> {
142
+ return (await extractMemoriesWithStatus(provider, messages, options)).memories;
143
+ }
144
+
145
+ export async function extractTasks(
146
+ provider: LlmProvider,
147
+ messages: readonly ExtractionMessage[],
148
+ options: { journey?: string | null; userName?: string; onLlmCall?: OnLlmCall } = {},
149
+ ): Promise<ExtractedTask[]> {
150
+ if (messages.length === 0) return [];
151
+ const prompt = buildTaskExtractionPrompt(messages, options.userName ?? "User");
152
+ const response = await provider.complete({
153
+ role: "task_extraction",
154
+ prompt,
155
+ model: resolveExtractionModel(),
156
+ temperature: 0.3,
157
+ });
158
+ options.onLlmCall?.(response, prompt);
159
+ const data = parseJsonResponse(response.content);
160
+ if (!Array.isArray(data)) return [];
161
+ const tasks: ExtractedTask[] = [];
162
+ for (const item of data) {
163
+ const task = toExtractedTask(item, options.journey ?? null);
164
+ if (task) tasks.push(task);
165
+ }
166
+ return tasks.length > MAX_TASKS_PER_CONVERSATION
167
+ ? tasks.slice(0, MAX_TASKS_PER_CONVERSATION)
168
+ : tasks;
169
+ }
170
+
171
+ export async function curateAgainstExisting(
172
+ provider: LlmProvider,
173
+ candidates: readonly ExtractedMemory[],
174
+ existing: readonly ExistingMemoryForCuration[],
175
+ options: { onLlmCall?: OnLlmCall } = {},
176
+ ): Promise<ExtractedMemory[]> {
177
+ if (candidates.length === 0) return [];
178
+ if (existing.length === 0) return [...candidates];
179
+ try {
180
+ const prompt = buildCurationPrompt(candidates, existing);
181
+ const response = await provider.complete({
182
+ role: "curation",
183
+ prompt,
184
+ model: resolveExtractionModel(),
185
+ temperature: 0.2,
186
+ });
187
+ options.onLlmCall?.(response, prompt);
188
+ const data = parseJsonResponse(response.content);
189
+ if (!Array.isArray(data)) return [...candidates];
190
+ const curated: ExtractedMemory[] = [];
191
+ for (const item of data) {
192
+ const memory = toExtractedMemory(item, {});
193
+ if (memory) curated.push(memory);
194
+ }
195
+ return sanitizeExtracted(curated, MAX_MEMORIES_PER_CONVERSATION).kept;
196
+ } catch {
197
+ return [...candidates];
198
+ }
199
+ }
200
+
201
+ export function toExtractedMemory(
202
+ item: unknown,
203
+ defaults: { persona?: string | null; journey?: string | null },
204
+ ): ExtractedMemory | null {
205
+ if (!isRecord(item)) return null;
206
+ if (
207
+ typeof item.title !== "string" ||
208
+ typeof item.content !== "string" ||
209
+ typeof item.memory_type !== "string"
210
+ ) {
211
+ return null;
212
+ }
213
+ return {
214
+ title: item.title,
215
+ content: item.content,
216
+ context: typeof item.context === "string" ? item.context : null,
217
+ memory_type: item.memory_type,
218
+ layer: typeof item.layer === "string" ? item.layer : "ego",
219
+ tags: Array.isArray(item.tags) ? item.tags.map((tag) => String(tag)) : [],
220
+ journey: typeof item.journey === "string" ? item.journey : (defaults.journey ?? null),
221
+ persona: typeof item.persona === "string" ? item.persona : (defaults.persona ?? null),
222
+ };
223
+ }
224
+
225
+ function toExtractedTask(item: unknown, journey: string | null): ExtractedTask | null {
226
+ if (!isRecord(item) || typeof item.title !== "string") return null;
227
+ return {
228
+ title: item.title,
229
+ due_date: typeof item.due_date === "string" ? item.due_date : null,
230
+ journey: typeof item.journey === "string" ? item.journey : journey,
231
+ stage: typeof item.stage === "string" ? item.stage : null,
232
+ context: typeof item.context === "string" ? item.context : null,
233
+ };
234
+ }
235
+
236
+ /**
237
+ * Python's `_format_candidates`. Each candidate emits its header line, a
238
+ * `Content:` line, an optional `Context:` line, and a trailing blank entry, all
239
+ * joined with newlines -- so the block ends with a newline. The optional
240
+ * context line and that trailing newline are both part of the prompt bytes.
241
+ */
242
+ function formatCandidates(candidates: readonly ExtractedMemory[]): string {
243
+ const lines: string[] = [];
244
+ candidates.forEach((candidate, index) => {
245
+ lines.push(
246
+ `${index + 1}. **${candidate.title}** (${candidate.memory_type}/${candidate.layer})`,
247
+ );
248
+ lines.push(` Content: ${candidate.content}`);
249
+ if (candidate.context) lines.push(` Context: ${candidate.context}`);
250
+ lines.push("");
251
+ });
252
+ return lines.join("\n");
253
+ }
254
+
255
+ /** Python's `_format_existing`: header line, truncated body, trailing blank. */
256
+ function formatExisting(existing: readonly ExistingMemoryForCuration[]): string {
257
+ const lines: string[] = [];
258
+ for (const memory of existing) {
259
+ lines.push(`- **${memory.title}** (${memory.memory_type}/${memory.layer})`);
260
+ lines.push(` ${memory.content.slice(0, 200)}`);
261
+ lines.push("");
262
+ }
263
+ return lines.join("\n");
264
+ }
265
+
266
+ /**
267
+ * Assemble the exact extraction prompt Python sends.
268
+ *
269
+ * Note the asymmetry with the close-tail surfaces: extraction and task
270
+ * extraction carry no post-fence reminder, while title/tags/summary do. That
271
+ * is Python's shape, reproduced rather than harmonized.
272
+ */
273
+ export function buildExtractionPrompt(
274
+ messages: readonly ExtractionMessage[],
275
+ userName = "User",
276
+ ): string {
277
+ return EXTRACTION_PROMPT + fenceTranscript(formatTranscript(messages, userName));
278
+ }
279
+
280
+ /** Assemble the exact task-extraction prompt Python sends. */
281
+ export function buildTaskExtractionPrompt(
282
+ messages: readonly ExtractionMessage[],
283
+ userName = "User",
284
+ ): string {
285
+ return TASK_EXTRACTION_PROMPT + fenceTranscript(formatTranscript(messages, userName));
286
+ }
287
+
288
+ /** Assemble the exact curation prompt Python sends, section headers included. */
289
+ export function buildCurationPrompt(
290
+ candidates: readonly ExtractedMemory[],
291
+ existing: readonly ExistingMemoryForCuration[],
292
+ ): string {
293
+ return (
294
+ CURATION_PROMPT +
295
+ "## Candidate memories (from this conversation)\n\n" +
296
+ formatCandidates(candidates) +
297
+ "\n## Existing similar memories (already stored)\n\n" +
298
+ formatExisting(existing)
299
+ );
300
+ }
301
+
302
+ function isRecord(value: unknown): value is Record<string, unknown> {
303
+ return typeof value === "object" && value !== null;
304
+ }