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,114 @@
1
+ // Journey association diagnosis and repair (CV22.DS7.US10 slice E).
2
+ //
3
+ // Ports `diagnose_journey_associations` and
4
+ // `_print_journey_association_findings` from
5
+ // `src/memory/cli/conversation_logger.py`.
6
+ //
7
+ // `diagnose-journeys` is read-only. `repair-journeys --apply` is the one
8
+ // genuinely MUTATING repair in this story: it rewrites the `journey` column of
9
+ // conversations the user never explicitly assigned. Python gates it behind a
10
+ // database backup and refuses to proceed if the backup fails, which is a safety
11
+ // property, not an implementation detail -- it is ported as a hard refusal.
12
+
13
+ import {
14
+ firstUserMessage,
15
+ inferJourneyForConversation,
16
+ journeyAliases,
17
+ } from "#conversation/journeyInference.ts";
18
+ import type { WritableDatabase } from "#db/database.ts";
19
+
20
+ export interface JourneyFinding {
21
+ conversation_id: string;
22
+ journey: string;
23
+ reason: string;
24
+ title: string;
25
+ started_at: string;
26
+ message_count: number;
27
+ }
28
+
29
+ export interface DiagnoseOptions {
30
+ limit?: number | null;
31
+ apply?: boolean;
32
+ /**
33
+ * Create a database backup and return its path, or null on failure. Required
34
+ * whenever `apply` is set; a null return refuses the repair.
35
+ */
36
+ backup?: () => string | null;
37
+ }
38
+
39
+ /**
40
+ * Find, and optionally repair, journeyless conversations with a high-confidence
41
+ * match.
42
+ *
43
+ * The findings list is identical in both modes: `apply` changes whether they are
44
+ * written, never which conversations qualify. That is what makes the dry run a
45
+ * trustworthy preview of the mutation.
46
+ */
47
+ export function diagnoseJourneyAssociations(
48
+ db: WritableDatabase,
49
+ options: DiagnoseOptions = {},
50
+ ): JourneyFinding[] {
51
+ const aliases = journeyAliases(db);
52
+ const limit = options.limit ?? null;
53
+ const baseSql =
54
+ "SELECT id, title, started_at FROM conversations WHERE journey IS NULL ORDER BY started_at DESC";
55
+ const rows = (
56
+ limit === null ? db.prepare(baseSql).all() : db.prepare(`${baseSql} LIMIT ?`).all(limit)
57
+ ) as Record<string, unknown>[];
58
+
59
+ const findings: JourneyFinding[] = [];
60
+ for (const row of rows) {
61
+ const conversationId = String(row.id);
62
+ const title = typeof row.title === "string" ? row.title : null;
63
+ const { journey, reason } = inferJourneyForConversation(
64
+ title,
65
+ firstUserMessage(db, conversationId),
66
+ aliases,
67
+ );
68
+ if (!journey) continue;
69
+
70
+ const countRow = db
71
+ .prepare("SELECT COUNT(*) AS count FROM messages WHERE conversation_id = ?")
72
+ .get(conversationId) as Record<string, unknown>;
73
+ findings.push({
74
+ conversation_id: conversationId,
75
+ journey,
76
+ reason: reason ?? "high-confidence match",
77
+ title: title ?? "",
78
+ started_at: String(row.started_at),
79
+ message_count: Number(countRow.count ?? 0),
80
+ });
81
+ }
82
+
83
+ if (options.apply && findings.length > 0) {
84
+ if (!options.backup) {
85
+ throw new Error("Database backup failed; refusing to repair conversations.");
86
+ }
87
+ const backupPath = options.backup();
88
+ if (backupPath === null) {
89
+ throw new Error("Database backup failed; refusing to repair conversations.");
90
+ }
91
+ for (const finding of findings) {
92
+ db.prepare("UPDATE conversations SET journey = ? WHERE id = ?").run(
93
+ finding.journey,
94
+ finding.conversation_id,
95
+ );
96
+ }
97
+ }
98
+
99
+ return findings;
100
+ }
101
+
102
+ /** Python's `_print_journey_association_findings`. */
103
+ export function renderJourneyFindings(findings: JourneyFinding[], applied: boolean): string {
104
+ const label = applied ? "Repaired" : "Repair candidates";
105
+ const lines = [`${label}: ${findings.length}`];
106
+ for (const finding of findings) {
107
+ lines.push(
108
+ `- ${finding.conversation_id} -> ${finding.journey} ` +
109
+ `(${finding.reason}; ${finding.message_count} messages; ` +
110
+ `${finding.started_at}; ${finding.title})`,
111
+ );
112
+ }
113
+ return `${lines.join("\n")}\n`;
114
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * The ES-001 metadata-lifecycle READ faces — CV22.DS7.US11 plateau 5b.
3
+ *
4
+ * `conversations --metadata-lifecycle-dry-run <conversation-id>` and
5
+ * `--metadata-lifecycle-preview-at-message <message-id>`: two pure reads over
6
+ * the engine DS7.US10 already ported. CR068 found the whole ES-001 flag family
7
+ * unowned — US1 called it "own slice", no slice claimed it.
8
+ *
9
+ * **Scope boundary, Navigator decision 2026-09-09 (option B).** The sibling
10
+ * faces `--metadata-lifecycle-apply` and `--metadata-lifecycle-demo` are NOT
11
+ * ported here and are refused BY NAME in `routing.ts`. Both need
12
+ * `apply_metadata_lifecycle`: ~80 lines of unported decision logic with a write
13
+ * path, which `closeTail.ts` already refuses for the same reason, and `demo`
14
+ * calls `apply`. The US11 plan recorded all four as "wiring over the ported
15
+ * engine", which was true of the reads and false of the writes. DS7.TS4 owns
16
+ * them; the two historical metadata-backfill flags were retired outright by
17
+ * CV22.DS10.TS4.
18
+ */
19
+
20
+ import {
21
+ type ConversationLike,
22
+ dryRunMetadataLifecycle,
23
+ type MessageLike,
24
+ type MetadataLifecycleReport,
25
+ titleNeedsImprovement,
26
+ } from "#conversation/metadataLifecycle.ts";
27
+ import type { Database } from "#db/database.ts";
28
+
29
+ /** Python raises `ValueError`; the CLI renders its text. */
30
+ export class LifecycleFaceError extends Error {}
31
+
32
+ /**
33
+ * Every column the ENGINE reads, not just the ones the title decision reads.
34
+ *
35
+ * The first version of this face selected `id, title, metadata`, and the
36
+ * summary and tags decisions silently answered as if the conversation had
37
+ * neither: a conversation with a stored summary reported `summary: defer` where
38
+ * Python reports `keep`, and the title decision moved with it, because
39
+ * refinement evidence is drawn from the summary. Measured at CV22.DS7.TS4
40
+ * plateau 6 against the real CLI; the US11 corpus could not see it because no
41
+ * seeded conversation had a summary or tags to lose.
42
+ */
43
+ interface ConversationRow {
44
+ id: string;
45
+ title: string | null;
46
+ summary: string | null;
47
+ tags: string | null;
48
+ metadata: string | null;
49
+ }
50
+
51
+ /**
52
+ * Python `_get_conversation_for_title_operation`: exact id, then an id-prefix
53
+ * lookup, then a ValueError naming the value the caller passed.
54
+ */
55
+ export function resolveConversation(db: Database, conversationId: string): ConversationRow {
56
+ if (typeof conversationId !== "string" || conversationId.trim() === "") {
57
+ throw new LifecycleFaceError("conversationId is required");
58
+ }
59
+ const exact = db
60
+ .prepare("SELECT id, title, summary, tags, metadata FROM conversations WHERE id = ?")
61
+ .get(conversationId) as unknown as ConversationRow | undefined;
62
+ if (exact) return exact;
63
+ const prefixed = db
64
+ .prepare(
65
+ "SELECT id, title, summary, tags, metadata FROM conversations WHERE id LIKE ? " +
66
+ "ORDER BY started_at LIMIT 1",
67
+ )
68
+ .get(`${conversationId}%`) as unknown as ConversationRow | undefined;
69
+ if (prefixed) return prefixed;
70
+ throw new LifecycleFaceError(`Conversation '${conversationId}' not found`);
71
+ }
72
+
73
+ /**
74
+ * The engine's `MessageLike` declares only what the policy READS (role,
75
+ * content). The boundary walk needs the id as well, so it is declared here
76
+ * rather than widening the engine's interface for a caller's convenience.
77
+ */
78
+ interface MessageRow extends MessageLike {
79
+ id: string;
80
+ }
81
+
82
+ function loadMessages(db: Database, conversationId: string): MessageRow[] {
83
+ return db
84
+ .prepare(
85
+ "SELECT id, role, content, created_at FROM messages WHERE conversation_id = ? ORDER BY created_at",
86
+ )
87
+ .all(conversationId) as unknown as MessageRow[];
88
+ }
89
+
90
+ /** Python `_metadata_dict`: a non-object or malformed metadata becomes `{}`. */
91
+ export function metadataDict(conversation: ConversationRow): Record<string, unknown> {
92
+ if (!conversation.metadata) return {};
93
+ try {
94
+ const parsed: unknown = JSON.parse(conversation.metadata);
95
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {};
96
+ return parsed as Record<string, unknown>;
97
+ } catch {
98
+ return {};
99
+ }
100
+ }
101
+
102
+ /**
103
+ * `titleNeedsImprovement` reads `metadata` — `title_status: "provisional"` is
104
+ * what makes a title repairable — so the WHOLE row crosses, not just id and
105
+ * title. An earlier draft passed `{ id, title }` behind an `as ConversationLike`
106
+ * cast, which compiled fine and silently turned every `repair` into `keep`.
107
+ * The cast is gone: the row already satisfies the interface structurally.
108
+ */
109
+ function asConversationLike(row: ConversationRow): ConversationLike {
110
+ return {
111
+ id: row.id,
112
+ title: row.title,
113
+ summary: row.summary,
114
+ tags: row.tags,
115
+ metadata: row.metadata,
116
+ };
117
+ }
118
+
119
+ /** `conversations --metadata-lifecycle-dry-run <conversation-id>`. */
120
+ export function runLifecycleDryRun(db: Database, conversationId: string): MetadataLifecycleReport {
121
+ const conversation = resolveConversation(db, conversationId);
122
+ const messages = loadMessages(db, conversation.id);
123
+ return dryRunMetadataLifecycle(
124
+ asConversationLike(conversation),
125
+ messages,
126
+ metadataDict(conversation),
127
+ {
128
+ titleNeedsImprovement,
129
+ },
130
+ );
131
+ }
132
+
133
+ export interface PreviewAtMessageReport {
134
+ conversation_id: string;
135
+ message_id: string;
136
+ mode: "debug_preview_at_message";
137
+ mutated: false;
138
+ included_message_count: number;
139
+ excluded_message_count: number;
140
+ dry_run: MetadataLifecycleReport;
141
+ }
142
+
143
+ /**
144
+ * `conversations --metadata-lifecycle-preview-at-message <message-id>`.
145
+ *
146
+ * Python resolves the boundary message by exact id OR id prefix, taking the
147
+ * MOST RECENT match (`ORDER BY created_at DESC LIMIT 1`) — the opposite
148
+ * direction from the conversation lookup beside it, reproduced as written.
149
+ */
150
+ export function runLifecyclePreviewAtMessage(
151
+ db: Database,
152
+ messageId: string,
153
+ ): PreviewAtMessageReport {
154
+ if (typeof messageId !== "string" || messageId.trim() === "") {
155
+ throw new LifecycleFaceError("messageId is required");
156
+ }
157
+ const boundary = db
158
+ .prepare(
159
+ "SELECT id, conversation_id FROM messages WHERE id = ? OR id LIKE ? ORDER BY created_at DESC LIMIT 1",
160
+ )
161
+ .get(messageId, `${messageId}%`) as unknown as
162
+ | { id: string; conversation_id: string }
163
+ | undefined;
164
+ if (!boundary) throw new LifecycleFaceError(`Message '${messageId}' not found`);
165
+
166
+ const conversation = resolveConversation(db, boundary.conversation_id);
167
+ const allMessages = loadMessages(db, conversation.id);
168
+
169
+ const selected: MessageRow[] = [];
170
+ let foundBoundary = false;
171
+ for (const message of allMessages) {
172
+ selected.push(message);
173
+ if (message.id === boundary.id) {
174
+ foundBoundary = true;
175
+ break;
176
+ }
177
+ }
178
+ if (!foundBoundary) {
179
+ throw new LifecycleFaceError(`Message '${messageId}' not found in conversation`);
180
+ }
181
+
182
+ const dryRun = dryRunMetadataLifecycle(
183
+ asConversationLike(conversation),
184
+ selected,
185
+ metadataDict(conversation),
186
+ { titleNeedsImprovement },
187
+ );
188
+ return {
189
+ conversation_id: conversation.id,
190
+ message_id: boundary.id,
191
+ mode: "debug_preview_at_message",
192
+ mutated: false,
193
+ included_message_count: selected.length,
194
+ excluded_message_count: Math.max(0, allMessages.length - selected.length),
195
+ dry_run: dryRun,
196
+ };
197
+ }
@@ -0,0 +1,313 @@
1
+ /**
2
+ * The ES-001 metadata-lifecycle WRITE faces — CV22.DS7.TS4 plateau 6.
3
+ *
4
+ * `conversations --metadata-lifecycle-apply <id> [--title/--summary/--tag]` and
5
+ * `--metadata-lifecycle-demo`, the two faces US11 refused by name and handed
6
+ * here (Navigator decision 2026-09-09, option B).
7
+ *
8
+ * This is the WRITE half of an engine already ported: every decision comes from
9
+ * `dryRunMetadataLifecycle`, and what lands here is the bounded application of
10
+ * those decisions. Three rules the corpus pinned and a reading of the code
11
+ * would not give:
12
+ *
13
+ * 1. **A decision is not a permission.** `create` and `repair` apply a title;
14
+ * `preserve` and `refine_candidate` each skip with their OWN reason, and
15
+ * any other decision skips as `decision_<name>_not_applied` — but only
16
+ * when a value was actually offered. With no value the reason is
17
+ * `no_value_provided`, which is a different fact and is reported as one.
18
+ * 2. **Tags ride the summary.** A `defer` tags decision still applies when a
19
+ * summary changed in the SAME call, because the deferral was waiting for
20
+ * exactly that substance. Alone, the same call skips.
21
+ * 3. **The flag is `--tag`, singular and repeatable.** There is no `--tags`;
22
+ * the parser appends into `dest="tags"`. A port written from the parameter
23
+ * name would expose an option Python refuses.
24
+ */
25
+
26
+ import type { ConversationLike, MetadataLifecycleReport } from "#conversation/metadataLifecycle.ts";
27
+ import type { WritableDatabase } from "#db/database.ts";
28
+ import { pythonJsonDumps } from "#util/pyGenerators.ts";
29
+ import { sliceCodePoints } from "#util/pythonText.ts";
30
+ import {
31
+ LifecycleFaceError,
32
+ metadataDict,
33
+ resolveConversation,
34
+ runLifecycleDryRun,
35
+ } from "./lifecycleFaces.ts";
36
+
37
+ export interface ApplyMetadataLifecycleInput {
38
+ title?: string | null;
39
+ summary?: string | null;
40
+ /** The repeated `--tag` values, or a pre-encoded string. */
41
+ tags?: readonly string[] | string | null;
42
+ source?: string;
43
+ }
44
+
45
+ export interface ApplyMetadataLifecycleReport {
46
+ conversation_id: string;
47
+ mode: "apply";
48
+ mutated: boolean;
49
+ changed: Record<string, unknown>;
50
+ skipped: Record<string, string>;
51
+ dry_run: MetadataLifecycleReport;
52
+ }
53
+
54
+ /** Port of `ConversationService._clean_title`. */
55
+ export function cleanTitle(title: string): string {
56
+ const clean = title.trim().split(/\s+/u).filter(Boolean).join(" ");
57
+ if (!clean) throw new LifecycleFaceError("title is required");
58
+ if (clean.length > 160) throw new LifecycleFaceError("title must be at most 160 characters");
59
+ return clean;
60
+ }
61
+
62
+ /**
63
+ * Port of `ConversationService._title_metadata`.
64
+ *
65
+ * `previous_title` is recorded only when it differs from the one already
66
+ * stored, so repeated applies do not overwrite the ORIGINAL title with the one
67
+ * the previous apply wrote.
68
+ */
69
+ export function titleMetadata(
70
+ metadata: Record<string, unknown>,
71
+ options: { source: string; status: string; previousTitle: string | null },
72
+ ): Record<string, unknown> {
73
+ const next = { ...metadata };
74
+ if (options.previousTitle && options.previousTitle !== next.previous_title) {
75
+ next.previous_title = options.previousTitle;
76
+ }
77
+ next.title_source = options.source;
78
+ next.title_status = options.status;
79
+ return next;
80
+ }
81
+
82
+ /** Port of `ConversationService.apply_metadata_lifecycle`. */
83
+ export function applyMetadataLifecycle(
84
+ db: WritableDatabase,
85
+ conversationId: string,
86
+ input: ApplyMetadataLifecycleInput = {},
87
+ ): ApplyMetadataLifecycleReport {
88
+ const source = input.source ?? "metadata_lifecycle_apply";
89
+ const conversation = resolveConversation(db, conversationId);
90
+ const dryRun = runLifecycleDryRun(db, conversation.id);
91
+ let metadata = metadataDict(conversation);
92
+ const updates: Record<string, string> = {};
93
+ const changed: Record<string, unknown> = {};
94
+ const skipped: Record<string, string> = {};
95
+
96
+ const title = input.title ?? null;
97
+ const titleDecision = String(dryRun.fields.title.decision);
98
+ if (titleDecision === "preserve") {
99
+ skipped.title = "manual_lock_preserved";
100
+ } else if (titleDecision === "refine_candidate") {
101
+ skipped.title = "candidate_decision_requires_explicit_review";
102
+ } else if ((titleDecision === "create" || titleDecision === "repair") && title !== null) {
103
+ const clean = cleanTitle(title);
104
+ updates.title = clean;
105
+ metadata = titleMetadata(metadata, {
106
+ source,
107
+ status: "generated",
108
+ previousTitle: conversation.title,
109
+ });
110
+ changed.title = clean;
111
+ } else if (title !== null) {
112
+ skipped.title = `decision_${titleDecision}_not_applied`;
113
+ } else {
114
+ skipped.title = "no_value_provided";
115
+ }
116
+
117
+ const summary = input.summary ?? null;
118
+ const summaryDecision = String(dryRun.fields.summary.decision);
119
+ if (summaryDecision === "create" && summary !== null) {
120
+ const clean = summary.trim();
121
+ if (clean) {
122
+ // Python truncates with a slice, which counts CODE POINTS. A UTF-16
123
+ // slice would cut an astral character in half at the boundary.
124
+ updates.summary = sliceCodePoints(clean, 1000);
125
+ metadata.summary_status = "generated";
126
+ changed.summary = updates.summary;
127
+ } else {
128
+ skipped.summary = "blank_value";
129
+ }
130
+ } else if (summary !== null) {
131
+ skipped.summary = `decision_${summaryDecision}_not_applied`;
132
+ } else {
133
+ skipped.summary = "no_value_provided";
134
+ }
135
+
136
+ const tags = input.tags ?? null;
137
+ const tagsDecision = String(dryRun.fields.tags.decision);
138
+ // Rule 2: a deferral waits for substance, and a summary landing in this same
139
+ // call IS that substance.
140
+ const tagsReadyAfterSummary = tagsDecision === "defer" && "summary" in changed;
141
+ if ((tagsDecision === "create" || tagsReadyAfterSummary) && tags !== null) {
142
+ updates.tags = typeof tags === "string" ? tags : pythonJsonDumps([...tags]);
143
+ metadata.tags_status = "generated";
144
+ changed.tags = tags;
145
+ } else if (tags !== null) {
146
+ skipped.tags = `decision_${tagsDecision}_not_applied`;
147
+ } else {
148
+ skipped.tags = "no_value_provided";
149
+ }
150
+
151
+ if (Object.keys(changed).length > 0) {
152
+ metadata.metadata_lifecycle_version = 1;
153
+ metadata.last_metadata_update_source = source;
154
+ updates.metadata = pythonJsonDumps(metadata);
155
+ updateConversation(db, conversation.id, updates);
156
+ }
157
+
158
+ return {
159
+ conversation_id: conversation.id,
160
+ mode: "apply",
161
+ mutated: Object.keys(changed).length > 0,
162
+ changed,
163
+ skipped,
164
+ dry_run: dryRun,
165
+ };
166
+ }
167
+
168
+ /** Port of `ConversationStore.update_conversation`: the given columns, nothing else. */
169
+ export function updateConversation(
170
+ db: WritableDatabase,
171
+ conversationId: string,
172
+ updates: Record<string, string>,
173
+ ): void {
174
+ const columns = Object.keys(updates);
175
+ if (columns.length === 0) return;
176
+ const assignments = columns.map((column) => `${column} = ?`).join(", ");
177
+ db.prepare(`UPDATE conversations SET ${assignments} WHERE id = ?`).run(
178
+ ...columns.map((column) => updates[column] as string),
179
+ conversationId,
180
+ );
181
+ }
182
+
183
+ /** The conversation columns the demo reports after applying. */
184
+ export interface ConversationAfter {
185
+ title: string | null;
186
+ summary: string | null;
187
+ tags: string | null;
188
+ }
189
+
190
+ export interface MetadataLifecycleDemoReport {
191
+ mode: "metadata_lifecycle_demo";
192
+ uses_production_data: false;
193
+ passed: boolean;
194
+ checks: Record<string, boolean>;
195
+ scenarios: Record<string, unknown>;
196
+ }
197
+
198
+ /**
199
+ * Port of `_metadata_lifecycle_demo_report`.
200
+ *
201
+ * A controlled scenario over a throwaway database: it proves the three
202
+ * decisions a reader most needs to trust — a provisional title is repaired, a
203
+ * manual lock is preserved, and a refine candidate is refused — and it touches
204
+ * no production data, which is the claim `uses_production_data: false` makes.
205
+ *
206
+ * The caller supplies the empty database, so the demo never decides where to
207
+ * write. Python's is `sqlite3.connect(":memory:")`.
208
+ */
209
+ export function metadataLifecycleDemoReport(
210
+ db: WritableDatabase,
211
+ seed: DemoSeed,
212
+ ): MetadataLifecycleDemoReport {
213
+ const repair = seed.startConversation("cli");
214
+ seed.setProvisionalTitle(repair, "vamos trabalhar no maestro");
215
+ seed.addMessage(repair, "user", "Vamos validar checkpoint visibility");
216
+ seed.addMessage(repair, "assistant", "Vamos revisar o handoff");
217
+ const preview = runLifecycleDryRun(db, repair);
218
+ const apply = applyMetadataLifecycle(db, repair, {
219
+ title: "Maestro checkpoint visibility validation",
220
+ summary: "Conversation metadata lifecycle validation.",
221
+ tags: ["metadata", "conversation"],
222
+ });
223
+ const afterApply = readConversation(db, repair);
224
+
225
+ const manual = seed.startConversation("cli", "Initial title");
226
+ seed.addMessage(manual, "user", "Quero corrigir títulos");
227
+ seed.addMessage(manual, "assistant", "Vamos desenhar a correção");
228
+ seed.updateTitle(manual, "Manual conversation title");
229
+ const manualApply = applyMetadataLifecycle(db, manual, {
230
+ title: "Generated replacement title",
231
+ });
232
+
233
+ const refine = seed.startConversation("cli", "Initial editorial session");
234
+ seed.addMessage(refine, "user", "Let's begin");
235
+ seed.addMessage(refine, "assistant", "Ready");
236
+ updateConversation(db, refine, {
237
+ summary:
238
+ "Editorial workflow for Raphael Albino manuscript. Scrivener import, " +
239
+ "cover briefing, Kindle export, EPUB validation, chapter cleanup, raw text hygiene.",
240
+ });
241
+ const refineApply = applyMetadataLifecycle(db, refine, {
242
+ title: "Better editorial workflow title",
243
+ });
244
+
245
+ const checks = {
246
+ preview_non_mutating: preview.mutated === false,
247
+ apply_changed_title: apply.changed.title === "Maestro checkpoint visibility validation",
248
+ manual_lock_preserved: manualApply.skipped.title === "manual_lock_preserved",
249
+ refine_candidate_skipped:
250
+ refineApply.skipped.title === "candidate_decision_requires_explicit_review",
251
+ };
252
+ return {
253
+ mode: "metadata_lifecycle_demo",
254
+ uses_production_data: false,
255
+ passed: Object.values(checks).every(Boolean),
256
+ checks,
257
+ scenarios: {
258
+ preview,
259
+ apply: {
260
+ report: apply,
261
+ after: {
262
+ title: afterApply?.title ?? null,
263
+ summary: afterApply?.summary ?? null,
264
+ tags: afterApply?.tags ?? null,
265
+ },
266
+ },
267
+ manual_lock: manualApply,
268
+ refine_candidate: refineApply,
269
+ },
270
+ };
271
+ }
272
+
273
+ /** What the demo needs from the conversation writer, injected rather than reached for. */
274
+ export interface DemoSeed {
275
+ startConversation: (interfaceName: string, title?: string) => string;
276
+ addMessage: (conversationId: string, role: string, content: string) => void;
277
+ setProvisionalTitle: (conversationId: string, title: string) => void;
278
+ updateTitle: (conversationId: string, title: string) => void;
279
+ }
280
+
281
+ function readConversation(db: WritableDatabase, conversationId: string): ConversationAfter | null {
282
+ const row = db
283
+ .prepare("SELECT title, summary, tags FROM conversations WHERE id = ?")
284
+ .get(conversationId) as unknown as ConversationAfter | undefined;
285
+ return row ?? null;
286
+ }
287
+
288
+ /**
289
+ * The two title writes the demo scripts, ported from `set_provisional_title`
290
+ * and `update_title`. Both go through `_title_metadata`, which is why a
291
+ * provisional title is later REPAIRABLE and a manual one is locked: the only
292
+ * difference on disk is `title_status`.
293
+ */
294
+ export function writeTitle(
295
+ db: WritableDatabase,
296
+ conversationId: string,
297
+ title: string,
298
+ kind: "provisional" | "manual",
299
+ ): void {
300
+ const conversation = resolveConversation(db, conversationId);
301
+ const clean = cleanTitle(title);
302
+ const metadata = titleMetadata(metadataDict(conversation), {
303
+ source: kind === "manual" ? "manual" : "first_user",
304
+ status: kind === "manual" ? "manual" : "provisional",
305
+ previousTitle: conversation.title,
306
+ });
307
+ updateConversation(db, conversation.id, {
308
+ title: clean,
309
+ metadata: pythonJsonDumps(metadata),
310
+ });
311
+ }
312
+
313
+ export type { ConversationLike };
@@ -0,0 +1,77 @@
1
+ // `conversations` listing read — the port of Store.list_recent_conversation_summaries
2
+ // (memory.cli.conversations, the plain-listing path only). The metadata-lifecycle
3
+ // and backfill preview/apply flags on the same Python CLI are stateful writes
4
+ // (ES-001); routing.ts falls back to Python whenever any of those flags are
5
+ // present, so this module only ever serves the plain listing.
6
+ //
7
+ // Query-builder + row-mapper, ordering pushed down to SQLite, per the same
8
+ // database-seam philosophy as memory/listing.ts.
9
+
10
+ import type { Database, SqlValue } from "#db/database.ts";
11
+ import { optionalString, requireString } from "#db/rowDecode.ts";
12
+
13
+ export interface ConversationSummary {
14
+ id: string;
15
+ title: string | null;
16
+ started_at: string | null;
17
+ persona: string | null;
18
+ journey: string | null;
19
+ message_count: number;
20
+ }
21
+
22
+ export interface ListRecentConversationsFilters {
23
+ limit?: number;
24
+ journey?: string | null;
25
+ persona?: string | null;
26
+ }
27
+
28
+ /**
29
+ * Build the parameterized recent-conversations query, mirroring the Python
30
+ * builder exactly: a `1=1` seed, `journey = ?` then `persona = ?` when
31
+ * provided (in that order), `ORDER BY started_at DESC`, trailing `LIMIT ?`.
32
+ */
33
+ export function buildListRecentConversationsQuery(filters: ListRecentConversationsFilters = {}): {
34
+ sql: string;
35
+ params: SqlValue[];
36
+ } {
37
+ const { limit = 20, journey = null, persona = null } = filters;
38
+ const conditions = ["1=1"];
39
+ const params: SqlValue[] = [];
40
+ if (journey) {
41
+ conditions.push("journey = ?");
42
+ params.push(journey);
43
+ }
44
+ if (persona) {
45
+ conditions.push("persona = ?");
46
+ params.push(persona);
47
+ }
48
+ params.push(limit);
49
+ const sql =
50
+ "SELECT id, title, started_at, persona, journey, " +
51
+ "(SELECT COUNT(*) FROM messages WHERE conversation_id = c.id) as message_count " +
52
+ `FROM conversations c WHERE ${conditions.join(" AND ")} ORDER BY started_at DESC LIMIT ?`;
53
+ return { sql, params };
54
+ }
55
+
56
+ function toSummary(row: Record<string, unknown>): ConversationSummary {
57
+ return {
58
+ id: requireString(row, "id"),
59
+ title: optionalString(row, "title"),
60
+ started_at: optionalString(row, "started_at"),
61
+ persona: optionalString(row, "persona"),
62
+ journey: optionalString(row, "journey"),
63
+ message_count: Number(row.message_count),
64
+ };
65
+ }
66
+
67
+ /** Return recent conversation summaries with optional equality filters, newest first. */
68
+ export function listRecentConversationSummaries(
69
+ db: Database,
70
+ filters: ListRecentConversationsFilters = {},
71
+ ): ConversationSummary[] {
72
+ const { sql, params } = buildListRecentConversationsQuery(filters);
73
+ return db
74
+ .prepare(sql)
75
+ .all(...params)
76
+ .map(toSummary);
77
+ }