mirror-mind 0.0.1 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (474) hide show
  1. package/.claude/hooks/log-assistant-reply.sh +85 -0
  2. package/.claude/hooks/log-session-end.sh +85 -0
  3. package/.claude/hooks/log-user-prompt.sh +85 -0
  4. package/.claude/hooks/mirror-inject.sh +85 -0
  5. package/.claude/hooks/session-start.sh +85 -0
  6. package/.gemini/hooks/log-assistant.sh +85 -0
  7. package/.gemini/hooks/log-user.sh +85 -0
  8. package/.gemini/hooks/session-end.sh +85 -0
  9. package/.gemini/hooks/session-start.sh +85 -0
  10. package/.pi/extensions/mirror-logger.ts +646 -0
  11. package/.pi/skills/mm-backup/SKILL.md +21 -0
  12. package/.pi/skills/mm-build/SKILL.md +1206 -0
  13. package/.pi/skills/mm-consolidate/SKILL.md +112 -0
  14. package/.pi/skills/mm-consult/SKILL.md +70 -0
  15. package/.pi/skills/mm-conversations/SKILL.md +16 -0
  16. package/.pi/skills/mm-discard/SKILL.md +39 -0
  17. package/.pi/skills/mm-explore/SKILL.md +255 -0
  18. package/.pi/skills/mm-help/SKILL.md +74 -0
  19. package/.pi/skills/mm-identity/SKILL.md +96 -0
  20. package/.pi/skills/mm-journal/SKILL.md +16 -0
  21. package/.pi/skills/mm-journey/SKILL.md +23 -0
  22. package/.pi/skills/mm-journeys/SKILL.md +15 -0
  23. package/.pi/skills/mm-memories/SKILL.md +17 -0
  24. package/.pi/skills/mm-mirror/SKILL.md +98 -0
  25. package/.pi/skills/mm-mute/SKILL.md +18 -0
  26. package/.pi/skills/mm-new/SKILL.md +18 -0
  27. package/.pi/skills/mm-recall/SKILL.md +17 -0
  28. package/.pi/skills/mm-release-notes/SKILL.md +52 -0
  29. package/.pi/skills/mm-seed/SKILL.md +19 -0
  30. package/.pi/skills/mm-shadow/SKILL.md +117 -0
  31. package/.pi/skills/mm-soul/SKILL.md +502 -0
  32. package/.pi/skills/mm-tasks/SKILL.md +24 -0
  33. package/.pi/skills/mm-update/SKILL.md +32 -0
  34. package/.pi/skills/mm-week/SKILL.md +28 -0
  35. package/.pi/skills/mm-welcome/SKILL.md +24 -0
  36. package/AGENTS.md +188 -0
  37. package/README.md +126 -9
  38. package/bin/loader.js +67 -0
  39. package/bin/mirror-hook.js +7 -0
  40. package/bin/mirror.js +7 -0
  41. package/docs/releases/index.md +100 -0
  42. package/docs/releases/pending-cutoffs.md +510 -0
  43. package/docs/releases/v0.10.0.md +123 -0
  44. package/docs/releases/v0.10.1.md +66 -0
  45. package/docs/releases/v0.10.10.md +51 -0
  46. package/docs/releases/v0.10.11.md +39 -0
  47. package/docs/releases/v0.10.12.md +57 -0
  48. package/docs/releases/v0.10.13.md +35 -0
  49. package/docs/releases/v0.10.14.md +42 -0
  50. package/docs/releases/v0.10.15.md +53 -0
  51. package/docs/releases/v0.10.16.md +35 -0
  52. package/docs/releases/v0.10.17.md +43 -0
  53. package/docs/releases/v0.10.2.md +64 -0
  54. package/docs/releases/v0.10.3.md +78 -0
  55. package/docs/releases/v0.10.4.md +42 -0
  56. package/docs/releases/v0.10.5.md +57 -0
  57. package/docs/releases/v0.10.6.md +47 -0
  58. package/docs/releases/v0.10.7.md +53 -0
  59. package/docs/releases/v0.10.8.md +49 -0
  60. package/docs/releases/v0.10.9.md +37 -0
  61. package/docs/releases/v0.11.0.md +86 -0
  62. package/docs/releases/v0.12.0.md +67 -0
  63. package/docs/releases/v0.13.0.md +64 -0
  64. package/docs/releases/v0.14.0.md +68 -0
  65. package/docs/releases/v0.15.0.md +48 -0
  66. package/docs/releases/v0.16.0.md +50 -0
  67. package/docs/releases/v0.16.1.md +55 -0
  68. package/docs/releases/v0.16.2.md +36 -0
  69. package/docs/releases/v0.17.0.md +50 -0
  70. package/docs/releases/v0.17.1.md +58 -0
  71. package/docs/releases/v0.17.2.md +42 -0
  72. package/docs/releases/v0.17.3.md +58 -0
  73. package/docs/releases/v0.18.0.md +84 -0
  74. package/docs/releases/v0.18.1.md +51 -0
  75. package/docs/releases/v0.18.2.md +69 -0
  76. package/docs/releases/v0.19.0.md +85 -0
  77. package/docs/releases/v0.19.1.md +46 -0
  78. package/docs/releases/v0.20.0.md +85 -0
  79. package/docs/releases/v0.21.0.md +61 -0
  80. package/docs/releases/v0.21.1.md +47 -0
  81. package/docs/releases/v0.22.0.md +77 -0
  82. package/docs/releases/v0.22.1.md +42 -0
  83. package/docs/releases/v0.23.0.md +105 -0
  84. package/docs/releases/v0.24.0.md +125 -0
  85. package/docs/releases/v0.25.0.md +107 -0
  86. package/docs/releases/v0.26.0.md +139 -0
  87. package/docs/releases/v0.26.1.md +65 -0
  88. package/docs/releases/v0.26.2.md +43 -0
  89. package/docs/releases/v0.27.0.md +63 -0
  90. package/docs/releases/v0.28.0.md +59 -0
  91. package/docs/releases/v0.29.0.md +61 -0
  92. package/docs/releases/v0.29.1.md +59 -0
  93. package/docs/releases/v0.29.2.md +59 -0
  94. package/docs/releases/v0.29.3.md +93 -0
  95. package/docs/releases/v0.30.0.md +75 -0
  96. package/docs/releases/v0.30.1.md +93 -0
  97. package/docs/releases/v0.30.2.md +66 -0
  98. package/docs/releases/v0.31.0.md +122 -0
  99. package/docs/releases/v0.31.1.md +142 -0
  100. package/docs/releases/v0.31.10.md +118 -0
  101. package/docs/releases/v0.31.11.md +88 -0
  102. package/docs/releases/v0.31.12.md +105 -0
  103. package/docs/releases/v0.31.13.md +99 -0
  104. package/docs/releases/v0.31.14.md +97 -0
  105. package/docs/releases/v0.31.2.md +115 -0
  106. package/docs/releases/v0.31.3.md +142 -0
  107. package/docs/releases/v0.31.4.md +117 -0
  108. package/docs/releases/v0.31.5.md +155 -0
  109. package/docs/releases/v0.31.6.md +132 -0
  110. package/docs/releases/v0.31.7.md +132 -0
  111. package/docs/releases/v0.31.8.md +120 -0
  112. package/docs/releases/v0.31.9.md +120 -0
  113. package/docs/releases/v0.32.0.md +261 -0
  114. package/docs/releases/v0.32.1.md +59 -0
  115. package/docs/releases/v0.33.0.md +164 -0
  116. package/docs/releases/v0.8.0.md +118 -0
  117. package/docs/releases/v0.9.0.md +98 -0
  118. package/docs/releases/v0.9.1.md +83 -0
  119. package/package.json +102 -6
  120. package/plugins/mirror-mind/.claude-plugin/plugin.json +17 -0
  121. package/plugins/mirror-mind/hooks/hooks.json +62 -0
  122. package/plugins/mirror-mind/hooks/log-assistant-reply.sh +98 -0
  123. package/plugins/mirror-mind/hooks/log-session-end.sh +98 -0
  124. package/plugins/mirror-mind/hooks/log-user-prompt.sh +98 -0
  125. package/plugins/mirror-mind/hooks/mirror-inject.sh +98 -0
  126. package/plugins/mirror-mind/hooks/session-start.sh +98 -0
  127. package/plugins/mirror-mind/skills/mm-backup/SKILL.md +27 -0
  128. package/plugins/mirror-mind/skills/mm-build/SKILL.md +1206 -0
  129. package/plugins/mirror-mind/skills/mm-consolidate/SKILL.md +112 -0
  130. package/plugins/mirror-mind/skills/mm-consult/SKILL.md +83 -0
  131. package/plugins/mirror-mind/skills/mm-conversations/SKILL.md +17 -0
  132. package/plugins/mirror-mind/skills/mm-discard/SKILL.md +40 -0
  133. package/plugins/mirror-mind/skills/mm-explore/SKILL.md +241 -0
  134. package/plugins/mirror-mind/skills/mm-help/SKILL.md +104 -0
  135. package/plugins/mirror-mind/skills/mm-identity/SKILL.md +96 -0
  136. package/plugins/mirror-mind/skills/mm-journal/SKILL.md +21 -0
  137. package/plugins/mirror-mind/skills/mm-journey/SKILL.md +37 -0
  138. package/plugins/mirror-mind/skills/mm-journeys/SKILL.md +15 -0
  139. package/plugins/mirror-mind/skills/mm-memories/SKILL.md +20 -0
  140. package/plugins/mirror-mind/skills/mm-mirror/SKILL.md +67 -0
  141. package/plugins/mirror-mind/skills/mm-mute/SKILL.md +18 -0
  142. package/plugins/mirror-mind/skills/mm-new/SKILL.md +18 -0
  143. package/plugins/mirror-mind/skills/mm-recall/SKILL.md +17 -0
  144. package/plugins/mirror-mind/skills/mm-release-notes/SKILL.md +31 -0
  145. package/plugins/mirror-mind/skills/mm-seed/SKILL.md +15 -0
  146. package/plugins/mirror-mind/skills/mm-shadow/SKILL.md +117 -0
  147. package/plugins/mirror-mind/skills/mm-soul/SKILL.md +488 -0
  148. package/plugins/mirror-mind/skills/mm-tasks/SKILL.md +36 -0
  149. package/plugins/mirror-mind/skills/mm-update/SKILL.md +30 -0
  150. package/plugins/mirror-mind/skills/mm-week/SKILL.md +46 -0
  151. package/plugins/mirror-mind/skills/mm-welcome/SKILL.md +22 -0
  152. package/scripts/codex-hooks/session-end.sh +85 -0
  153. package/scripts/codex-hooks/session-start.sh +85 -0
  154. package/scripts/codex-mirror.sh +48 -0
  155. package/templates/identity/README.md +119 -0
  156. package/templates/identity/ego/behavior.yaml +52 -0
  157. package/templates/identity/ego/constraints.yaml +15 -0
  158. package/templates/identity/ego/identity.yaml +43 -0
  159. package/templates/identity/journeys/personal-growth.yaml +39 -0
  160. package/templates/identity/organization/identity.yaml +24 -0
  161. package/templates/identity/organization/principles.yaml +17 -0
  162. package/templates/identity/personas/coach.yaml +94 -0
  163. package/templates/identity/personas/designer.yaml +93 -0
  164. package/templates/identity/personas/doctor.yaml +96 -0
  165. package/templates/identity/personas/engineer.yaml +77 -0
  166. package/templates/identity/personas/financial.yaml +97 -0
  167. package/templates/identity/personas/prompt-engineer.yaml +122 -0
  168. package/templates/identity/personas/researcher.yaml +88 -0
  169. package/templates/identity/personas/strategist.yaml +91 -0
  170. package/templates/identity/personas/teacher.yaml +95 -0
  171. package/templates/identity/personas/therapist.yaml +91 -0
  172. package/templates/identity/personas/thinker.yaml +74 -0
  173. package/templates/identity/personas/writer.yaml +76 -0
  174. package/templates/identity/self/config.yaml +16 -0
  175. package/templates/identity/self/soul.yaml +105 -0
  176. package/templates/identity/user/identity.yaml +30 -0
  177. package/ts/src/backup/staging.ts +62 -0
  178. package/ts/src/backup/zipBackup.ts +267 -0
  179. package/ts/src/backup/zipReader.ts +109 -0
  180. package/ts/src/backup/zipWriter.ts +148 -0
  181. package/ts/src/builder/activeCheckpoint.ts +134 -0
  182. package/ts/src/builder/aggregateStatus.ts +34 -0
  183. package/ts/src/builder/approve.ts +149 -0
  184. package/ts/src/builder/argv.ts +292 -0
  185. package/ts/src/builder/ariadMethod.ts +536 -0
  186. package/ts/src/builder/artifacts/artifactSurfaces.ts +106 -0
  187. package/ts/src/builder/artifacts/artifactWriter.ts +113 -0
  188. package/ts/src/builder/artifacts/closureArtifacts.ts +199 -0
  189. package/ts/src/builder/artifacts/deliveryStoryArtifacts.ts +316 -0
  190. package/ts/src/builder/artifacts/planArtifacts.ts +242 -0
  191. package/ts/src/builder/artifacts/scaffoldSections.ts +273 -0
  192. package/ts/src/builder/artifacts/scaffoldState.ts +227 -0
  193. package/ts/src/builder/artifacts/storyFiles.ts +61 -0
  194. package/ts/src/builder/artifacts/storyIndex.ts +166 -0
  195. package/ts/src/builder/cadence.ts +59 -0
  196. package/ts/src/builder/card.ts +192 -0
  197. package/ts/src/builder/checkpointRefused.ts +66 -0
  198. package/ts/src/builder/closure.ts +964 -0
  199. package/ts/src/builder/commands.ts +2053 -0
  200. package/ts/src/builder/cursorPosition.ts +176 -0
  201. package/ts/src/builder/cursorTransitions.ts +77 -0
  202. package/ts/src/builder/deliveryCursor.ts +577 -0
  203. package/ts/src/builder/deliveryStoryClosure.ts +521 -0
  204. package/ts/src/builder/deliveryStoryPlan.ts +536 -0
  205. package/ts/src/builder/deliveryStoryReady.ts +160 -0
  206. package/ts/src/builder/deliveryStoryRoadmapClosure.ts +151 -0
  207. package/ts/src/builder/expand.ts +336 -0
  208. package/ts/src/builder/flowUnit.ts +339 -0
  209. package/ts/src/builder/homeSurface.ts +122 -0
  210. package/ts/src/builder/implementationGuard.ts +125 -0
  211. package/ts/src/builder/index.ts +11 -0
  212. package/ts/src/builder/lifecycleRefusal.ts +92 -0
  213. package/ts/src/builder/lifecycleRibbon.ts +159 -0
  214. package/ts/src/builder/load.ts +313 -0
  215. package/ts/src/builder/methodAdoption.ts +113 -0
  216. package/ts/src/builder/methodDefinition.ts +429 -0
  217. package/ts/src/builder/methodInspection.ts +371 -0
  218. package/ts/src/builder/plan.ts +408 -0
  219. package/ts/src/builder/planPreauthorization.ts +265 -0
  220. package/ts/src/builder/prepare.ts +225 -0
  221. package/ts/src/builder/projectPaths.ts +64 -0
  222. package/ts/src/builder/pull.ts +356 -0
  223. package/ts/src/builder/pullCandidates.ts +463 -0
  224. package/ts/src/builder/pullCandidatesRender.ts +238 -0
  225. package/ts/src/builder/refinementField.ts +93 -0
  226. package/ts/src/builder/releaseIntent.ts +173 -0
  227. package/ts/src/builder/resumeState.ts +84 -0
  228. package/ts/src/builder/resumeSurface.ts +149 -0
  229. package/ts/src/builder/roadmapGrammar.ts +300 -0
  230. package/ts/src/builder/roadmapScan.ts +97 -0
  231. package/ts/src/builder/roadmapScope.ts +210 -0
  232. package/ts/src/builder/scopePhrases.ts +146 -0
  233. package/ts/src/builder/storyPaths.ts +196 -0
  234. package/ts/src/builder/storyPlanPreauthorization.ts +287 -0
  235. package/ts/src/builder/surfaceProtocol.ts +42 -0
  236. package/ts/src/builder/templateGeneration.ts +148 -0
  237. package/ts/src/builder/transition.ts +178 -0
  238. package/ts/src/consult/args.ts +99 -0
  239. package/ts/src/consult/core.ts +122 -0
  240. package/ts/src/consult/modelCatalog.ts +55 -0
  241. package/ts/src/consult/render.ts +47 -0
  242. package/ts/src/conversation/append.ts +414 -0
  243. package/ts/src/conversation/backfill.ts +387 -0
  244. package/ts/src/conversation/closeTail.ts +467 -0
  245. package/ts/src/conversation/extraction.ts +333 -0
  246. package/ts/src/conversation/extractionDriver.ts +130 -0
  247. package/ts/src/conversation/extractionRun.ts +70 -0
  248. package/ts/src/conversation/hookPayload.ts +51 -0
  249. package/ts/src/conversation/journeyInference.ts +165 -0
  250. package/ts/src/conversation/journeyRepair.ts +114 -0
  251. package/ts/src/conversation/lifecycleFaces.ts +197 -0
  252. package/ts/src/conversation/lifecycleWrites.ts +313 -0
  253. package/ts/src/conversation/listing.ts +77 -0
  254. package/ts/src/conversation/logger.ts +851 -0
  255. package/ts/src/conversation/loggerCli.ts +321 -0
  256. package/ts/src/conversation/loggerRuntime.ts +259 -0
  257. package/ts/src/conversation/metadataLifecycle.ts +443 -0
  258. package/ts/src/conversation/recall.ts +66 -0
  259. package/ts/src/conversation/sessionComposites.ts +258 -0
  260. package/ts/src/conversation/sessionImport.ts +90 -0
  261. package/ts/src/conversation/transcriptBackfill.ts +141 -0
  262. package/ts/src/cultivation/applyActions.ts +241 -0
  263. package/ts/src/cultivation/cluster.ts +86 -0
  264. package/ts/src/cultivation/consolidationStore.ts +281 -0
  265. package/ts/src/cultivation/promptContext.ts +62 -0
  266. package/ts/src/cultivation/propose.ts +305 -0
  267. package/ts/src/cultivation/scan.ts +155 -0
  268. package/ts/src/db/backupGate.ts +47 -0
  269. package/ts/src/db/bootstrap.ts +108 -0
  270. package/ts/src/db/bootstrapLock.ts +223 -0
  271. package/ts/src/db/copyGuard.ts +60 -0
  272. package/ts/src/db/database.ts +397 -0
  273. package/ts/src/db/decode.ts +66 -0
  274. package/ts/src/db/ftsIntegrity.ts +41 -0
  275. package/ts/src/db/migrateOnOpen.ts +214 -0
  276. package/ts/src/db/migrations.ts +576 -0
  277. package/ts/src/db/readyOnOpen.ts +35 -0
  278. package/ts/src/db/rowDecode.ts +42 -0
  279. package/ts/src/db/schema.ts +432 -0
  280. package/ts/src/db/schemaInventory.ts +225 -0
  281. package/ts/src/db/schemaInventorySnapshot.ts +2107 -0
  282. package/ts/src/db/schemaState.ts +91 -0
  283. package/ts/src/db/schemaTsDivergence.ts +114 -0
  284. package/ts/src/descriptor/descriptorRead.ts +49 -0
  285. package/ts/src/docs/docsLint.ts +265 -0
  286. package/ts/src/explorer/handoff.ts +522 -0
  287. package/ts/src/explorer/render.ts +335 -0
  288. package/ts/src/explorer/story.ts +769 -0
  289. package/ts/src/explorer/transition.ts +72 -0
  290. package/ts/src/extensions/bindings.ts +170 -0
  291. package/ts/src/extensions/catalog.ts +249 -0
  292. package/ts/src/extensions/catalogCommands.ts +513 -0
  293. package/ts/src/extensions/catalogWrites.ts +652 -0
  294. package/ts/src/extensions/contextRuntime.ts +325 -0
  295. package/ts/src/extensions/dispatch.ts +374 -0
  296. package/ts/src/extensions/errors.ts +24 -0
  297. package/ts/src/extensions/manifest.ts +356 -0
  298. package/ts/src/extensions/migrations.ts +350 -0
  299. package/ts/src/extraction/conversation.ts +304 -0
  300. package/ts/src/extraction/conversationMetadata.ts +206 -0
  301. package/ts/src/extraction/fencing.ts +106 -0
  302. package/ts/src/extraction/json.ts +17 -0
  303. package/ts/src/extraction/prompts.ts +470 -0
  304. package/ts/src/frontDoor/args.ts +30 -0
  305. package/ts/src/frontDoor/argvShape.ts +106 -0
  306. package/ts/src/frontDoor/buildLoadRuntime.ts +139 -0
  307. package/ts/src/frontDoor/buildRoute.ts +398 -0
  308. package/ts/src/frontDoor/cli.ts +1872 -0
  309. package/ts/src/frontDoor/cloneRoleGuard.ts +98 -0
  310. package/ts/src/frontDoor/consultRoute.ts +50 -0
  311. package/ts/src/frontDoor/contentTailRoute.ts +239 -0
  312. package/ts/src/frontDoor/conversationLoggerRoute.ts +83 -0
  313. package/ts/src/frontDoor/cultivationRoute.ts +210 -0
  314. package/ts/src/frontDoor/dbPath.ts +131 -0
  315. package/ts/src/frontDoor/dbSafetyToolsRoute.ts +240 -0
  316. package/ts/src/frontDoor/exploreRoute.ts +572 -0
  317. package/ts/src/frontDoor/extensionCatalogRoute.ts +252 -0
  318. package/ts/src/frontDoor/frontDoorLog.ts +57 -0
  319. package/ts/src/frontDoor/identityWrite.ts +36 -0
  320. package/ts/src/frontDoor/journeyWriteRoute.ts +26 -0
  321. package/ts/src/frontDoor/lifecycleWriteRoute.ts +110 -0
  322. package/ts/src/frontDoor/liveBackup.ts +100 -0
  323. package/ts/src/frontDoor/mirrorModeRoute.ts +163 -0
  324. package/ts/src/frontDoor/namedRefusal.ts +35 -0
  325. package/ts/src/frontDoor/nodeSupport.ts +24 -0
  326. package/ts/src/frontDoor/render/consolidate.ts +199 -0
  327. package/ts/src/frontDoor/render/conversations.ts +33 -0
  328. package/ts/src/frontDoor/render/cultivationShared.ts +21 -0
  329. package/ts/src/frontDoor/render/descriptor.ts +20 -0
  330. package/ts/src/frontDoor/render/detectPersona.ts +50 -0
  331. package/ts/src/frontDoor/render/icons.ts +13 -0
  332. package/ts/src/frontDoor/render/identity.ts +59 -0
  333. package/ts/src/frontDoor/render/identityRows.ts +17 -0
  334. package/ts/src/frontDoor/render/inspectPersona.ts +53 -0
  335. package/ts/src/frontDoor/render/journeyStatus.ts +78 -0
  336. package/ts/src/frontDoor/render/journeys.ts +79 -0
  337. package/ts/src/frontDoor/render/list.ts +41 -0
  338. package/ts/src/frontDoor/render/memories.ts +67 -0
  339. package/ts/src/frontDoor/render/recall.ts +49 -0
  340. package/ts/src/frontDoor/render/shadow.ts +155 -0
  341. package/ts/src/frontDoor/render/tasks.ts +99 -0
  342. package/ts/src/frontDoor/render/tasksImportSync.ts +63 -0
  343. package/ts/src/frontDoor/render/week.ts +113 -0
  344. package/ts/src/frontDoor/routing.ts +1001 -0
  345. package/ts/src/frontDoor/runtimeRoute.ts +623 -0
  346. package/ts/src/frontDoor/searchRoute.ts +166 -0
  347. package/ts/src/frontDoor/seedPaths.ts +98 -0
  348. package/ts/src/frontDoor/sessionStartWelcomeRoute.ts +166 -0
  349. package/ts/src/frontDoor/soulRoute.ts +433 -0
  350. package/ts/src/frontDoor/stdinSource.ts +37 -0
  351. package/ts/src/frontDoor/tasksImportSyncRoute.ts +110 -0
  352. package/ts/src/frontDoor/tasksWriteRoute.ts +101 -0
  353. package/ts/src/frontDoor/usage.ts +176 -0
  354. package/ts/src/guards/claudePlugin.ts +298 -0
  355. package/ts/src/guards/packContents.ts +306 -0
  356. package/ts/src/guards/piSourcedSkills.ts +70 -0
  357. package/ts/src/guards/retiredRouteShadows.ts +57 -0
  358. package/ts/src/guards/retiredSurfaces.ts +564 -0
  359. package/ts/src/hooks/claude.ts +214 -0
  360. package/ts/src/hooks/codex.ts +45 -0
  361. package/ts/src/hooks/gemini.ts +126 -0
  362. package/ts/src/hooks/main.ts +109 -0
  363. package/ts/src/hooks/mirrorState.ts +71 -0
  364. package/ts/src/hooks/payload.ts +73 -0
  365. package/ts/src/hooks/runtime.ts +179 -0
  366. package/ts/src/hooks/runtimeDetection.ts +34 -0
  367. package/ts/src/identity/applyConsolidationIdentityUpdate.ts +67 -0
  368. package/ts/src/identity/descriptorGenerate.ts +162 -0
  369. package/ts/src/identity/identityEdit.ts +143 -0
  370. package/ts/src/identity/identityKey.ts +77 -0
  371. package/ts/src/identity/identityRead.ts +69 -0
  372. package/ts/src/identity/identityStore.ts +80 -0
  373. package/ts/src/identity/journeyListing.ts +53 -0
  374. package/ts/src/identity/personaInspect.ts +44 -0
  375. package/ts/src/identity/personaListing.ts +37 -0
  376. package/ts/src/identity/setIdentity.ts +64 -0
  377. package/ts/src/index.ts +231 -0
  378. package/ts/src/init/init.ts +111 -0
  379. package/ts/src/journey/journeyOptions.ts +131 -0
  380. package/ts/src/journey/journeyRemoval.ts +158 -0
  381. package/ts/src/journey/journeyStatus.ts +149 -0
  382. package/ts/src/journey/journeySyncFile.ts +87 -0
  383. package/ts/src/journey/journeyWrite.ts +203 -0
  384. package/ts/src/journey/parentJourney.ts +41 -0
  385. package/ts/src/journey/validateParentJourney.ts +55 -0
  386. package/ts/src/mcp/boundary.ts +151 -0
  387. package/ts/src/mcp/guards.ts +244 -0
  388. package/ts/src/mcp/main.ts +121 -0
  389. package/ts/src/mcp/payload.ts +141 -0
  390. package/ts/src/mcp/protocol.ts +141 -0
  391. package/ts/src/mcp/readModels.ts +241 -0
  392. package/ts/src/mcp/registry.ts +201 -0
  393. package/ts/src/mcp/serve.ts +120 -0
  394. package/ts/src/mcp/tools/deterministic.ts +137 -0
  395. package/ts/src/mcp/tools/providerCrossing.ts +187 -0
  396. package/ts/src/mcp/wire.ts +8 -0
  397. package/ts/src/memory/journal.ts +180 -0
  398. package/ts/src/memory/listing.ts +107 -0
  399. package/ts/src/memory/memoryWrite.ts +88 -0
  400. package/ts/src/memory/reinforcement.ts +35 -0
  401. package/ts/src/mirror/context.ts +161 -0
  402. package/ts/src/mirror/defaultResolution.ts +199 -0
  403. package/ts/src/mirror/mirrorForm.ts +43 -0
  404. package/ts/src/mirror/orchestration.ts +364 -0
  405. package/ts/src/mirror/reception.ts +194 -0
  406. package/ts/src/mirror/render.ts +75 -0
  407. package/ts/src/mirror/runtimeSession.ts +205 -0
  408. package/ts/src/mode/operatingMode.ts +148 -0
  409. package/ts/src/observability/callOutcome.ts +115 -0
  410. package/ts/src/observability/inspectLedger.ts +329 -0
  411. package/ts/src/observability/ledgerHooks.ts +126 -0
  412. package/ts/src/observability/llmCalls.ts +154 -0
  413. package/ts/src/persona/detectPersona.ts +81 -0
  414. package/ts/src/planning/promptAssembly.ts +111 -0
  415. package/ts/src/planning/weekPending.ts +81 -0
  416. package/ts/src/planning/weekPlan.ts +183 -0
  417. package/ts/src/planning/weekSave.ts +117 -0
  418. package/ts/src/providers/config.ts +189 -0
  419. package/ts/src/providers/cost.ts +64 -0
  420. package/ts/src/providers/credits.ts +204 -0
  421. package/ts/src/providers/embedding.ts +321 -0
  422. package/ts/src/providers/familyProviders.ts +147 -0
  423. package/ts/src/providers/llm.ts +334 -0
  424. package/ts/src/providers/openrouter.ts +244 -0
  425. package/ts/src/providers/redaction.ts +110 -0
  426. package/ts/src/providers/replay.ts +15 -0
  427. package/ts/src/providers/transport.ts +410 -0
  428. package/ts/src/repair/encodingRepair.ts +253 -0
  429. package/ts/src/runtime/backup.ts +151 -0
  430. package/ts/src/runtime/channel.ts +146 -0
  431. package/ts/src/runtime/config.ts +168 -0
  432. package/ts/src/runtime/diagnose.ts +686 -0
  433. package/ts/src/runtime/git.ts +510 -0
  434. package/ts/src/runtime/installKind.ts +220 -0
  435. package/ts/src/runtime/migrate.ts +154 -0
  436. package/ts/src/runtime/packageIdentity.ts +102 -0
  437. package/ts/src/runtime/releaseNotes.ts +244 -0
  438. package/ts/src/runtime/status.ts +557 -0
  439. package/ts/src/runtime/strategies/clone.ts +200 -0
  440. package/ts/src/runtime/strategies/package.ts +328 -0
  441. package/ts/src/runtime/treeRoot.ts +30 -0
  442. package/ts/src/runtime/update.ts +445 -0
  443. package/ts/src/runtime/updateGate.ts +40 -0
  444. package/ts/src/runtime/updatePipeline.ts +159 -0
  445. package/ts/src/runtime/version.ts +36 -0
  446. package/ts/src/runtime/wiring.ts +130 -0
  447. package/ts/src/search/memorySearch.ts +338 -0
  448. package/ts/src/search/ranker.ts +154 -0
  449. package/ts/src/seed/seed.ts +256 -0
  450. package/ts/src/seed/seedYaml.ts +133 -0
  451. package/ts/src/soul/apply.ts +218 -0
  452. package/ts/src/soul/harvest.ts +267 -0
  453. package/ts/src/soul/prompts/soul_beauty_voice.md +103 -0
  454. package/ts/src/soul/prompts/soul_self_voice.md +219 -0
  455. package/ts/src/soul/prompts/soul_wisdom_voice.md +121 -0
  456. package/ts/src/soul/prompts.ts +85 -0
  457. package/ts/src/soul/render.ts +358 -0
  458. package/ts/src/soul/state.ts +212 -0
  459. package/ts/src/soul/transition.ts +37 -0
  460. package/ts/src/tasks/journeyPathParse.ts +113 -0
  461. package/ts/src/tasks/taskImportSync.ts +142 -0
  462. package/ts/src/tasks/taskStore.ts +312 -0
  463. package/ts/src/tasks/weekView.ts +120 -0
  464. package/ts/src/util/clipCodePoints.ts +26 -0
  465. package/ts/src/util/paths.ts +63 -0
  466. package/ts/src/util/program.ts +9 -0
  467. package/ts/src/util/pyGenerators.ts +144 -0
  468. package/ts/src/util/pythonText.ts +317 -0
  469. package/ts/src/util/semver.ts +45 -0
  470. package/ts/src/util/shellWord.ts +25 -0
  471. package/ts/src/util/slug.ts +64 -0
  472. package/ts/src/welcome/card.ts +339 -0
  473. package/ts/src/welcome/statusLine.ts +101 -0
  474. package/ts/src/welcome/updateCache.ts +129 -0
@@ -0,0 +1,165 @@
1
+ // Journey inference for conversations (CV22.DS7.US10 slice E).
2
+ //
3
+ // Ports `_journey_aliases`, `_first_user_message`, `_activation_text`, and
4
+ // `_infer_journey_for_conversation` from
5
+ // `src/memory/cli/conversation_logger.py`.
6
+ //
7
+ // This is the deterministic half of `diagnose-journeys`/`repair-journeys`: it
8
+ // decides which journeyless conversation has a high-confidence match. The
9
+ // decision matters because `--apply` writes it, so a false positive silently
10
+ // reassigns a user's conversation to the wrong journey.
11
+ //
12
+ // Pure: no storage, no clock, no provider. The caller supplies the alias table
13
+ // and the conversation's title and first user message.
14
+
15
+ import type { WritableDatabase } from "#db/database.ts";
16
+
17
+ /** slug -> alias strings, longest first (Python sorts by length descending). */
18
+ export type JourneyAliases = Record<string, string[]>;
19
+
20
+ export interface JourneyInference {
21
+ journey: string | null;
22
+ reason: string | null;
23
+ }
24
+
25
+ /**
26
+ * Python's `_journey_aliases`: the slug, the slug with hyphens as spaces, and
27
+ * the first Markdown heading in the journey's identity content.
28
+ *
29
+ * Python builds the value set with `{slug.lower(), slug.replace("-", " ").lower()}`,
30
+ * so a slug without hyphens contributes one alias, not two.
31
+ */
32
+ export function journeyAliases(db: WritableDatabase): JourneyAliases {
33
+ const rows = db
34
+ .prepare("SELECT key, content FROM identity WHERE layer = 'journey' ORDER BY key")
35
+ .all() as Record<string, unknown>[];
36
+
37
+ const aliases: JourneyAliases = {};
38
+ for (const row of rows) {
39
+ const slug = String(row.key);
40
+ const content = typeof row.content === "string" ? row.content : "";
41
+ const values = new Set([slug.toLowerCase(), slug.replaceAll("-", " ").toLowerCase()]);
42
+ for (const line of content.split("\n")) {
43
+ const heading = line.trim();
44
+ if (heading.startsWith("#")) {
45
+ // Python breaks at the FIRST heading line, whether or not it yielded a
46
+ // title, so a bare `#` stops the scan instead of falling through.
47
+ const title = heading.replace(/^#+/, "").trim();
48
+ if (title) values.add(title.toLowerCase());
49
+ break;
50
+ }
51
+ }
52
+ aliases[slug] = sortByLengthDescending([...values]);
53
+ }
54
+ return aliases;
55
+ }
56
+
57
+ /**
58
+ * Python's `sorted(values, key=len, reverse=True)`, with equal lengths broken
59
+ * lexicographically.
60
+ *
61
+ * Python sorts a SET and its sort is stable, so aliases of equal length come
62
+ * out in set-iteration order -- which string hash randomization varies between
63
+ * processes. Observed directly while building the golden: `beta-two` alternates
64
+ * between `["beta two", "beta-two"]` and the reverse across runs.
65
+ *
66
+ * The ordering is behaviorally inert for inference: only the matched alias's
67
+ * LENGTH feeds the cross-slug tie-break, and equal-length entries tie by
68
+ * definition. Breaking those ties lexicographically therefore preserves
69
+ * behavior while making the alias table reproducible, which is what lets the
70
+ * golden serve as a determinism gate. The generator normalizes the oracle's
71
+ * output the same way and asserts the two agree on membership and lengths.
72
+ */
73
+ function sortByLengthDescending(values: string[]): string[] {
74
+ return [...values].sort(
75
+ (left, right) => right.length - left.length || (left < right ? -1 : left > right ? 1 : 0),
76
+ );
77
+ }
78
+
79
+ /** Python's `_first_user_message`. */
80
+ export function firstUserMessage(db: WritableDatabase, conversationId: string): string {
81
+ const row = db
82
+ .prepare(
83
+ `SELECT content FROM messages
84
+ WHERE conversation_id = ? AND role = 'user'
85
+ ORDER BY created_at ASC LIMIT 1`,
86
+ )
87
+ .get(conversationId) as Record<string, unknown> | undefined;
88
+ return row && typeof row.content === "string" ? row.content : "";
89
+ }
90
+
91
+ /**
92
+ * Python's `_activation_text`: the title, the text after a closing `</skill>`
93
+ * tag when present, and the first line -- joined with newlines and lowercased.
94
+ */
95
+ export function activationText(title: string | null, firstUser: string): string {
96
+ const parts: string[] = [title ?? ""];
97
+ const stripped = firstUser.trim();
98
+ if (stripped.includes("</skill>")) {
99
+ parts.push(stripped.slice(stripped.lastIndexOf("</skill>") + "</skill>".length));
100
+ }
101
+ const firstLine = stripped ? (stripped.split("\n")[0] as string) : "";
102
+ parts.push(firstLine);
103
+ return parts.join("\n").toLowerCase();
104
+ }
105
+
106
+ function escapeRegExp(value: string): string {
107
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
108
+ }
109
+
110
+ /**
111
+ * Python's `_infer_journey_for_conversation`.
112
+ *
113
+ * Two routes: an explicit `/mm-build <slug>` command, or an alias match that is
114
+ * either standalone on its own line or wrapped in an activation phrase. A tie
115
+ * between the two longest matching aliases returns no journey -- ambiguity is
116
+ * resolved by refusing, never by picking, because the caller may write it.
117
+ */
118
+ export function inferJourneyForConversation(
119
+ title: string | null,
120
+ firstUser: string,
121
+ aliases: JourneyAliases,
122
+ ): JourneyInference {
123
+ const text = activationText(title, firstUser);
124
+ if (!text.trim()) return { journey: null, reason: null };
125
+
126
+ const buildMatch = /(?:\/mm-build|\$mm-build|\/mm:build)\s+([a-z0-9][a-z0-9_-]*)/.exec(text);
127
+ if (buildMatch) {
128
+ const slug = buildMatch[1] as string;
129
+ if (slug in aliases) return { journey: slug, reason: "explicit build command" };
130
+ }
131
+
132
+ const candidates: { slug: string; alias: string; reason: string }[] = [];
133
+ for (const [slug, values] of Object.entries(aliases)) {
134
+ for (const alias of values) {
135
+ const escaped = escapeRegExp(alias);
136
+ // Python's `$` without MULTILINE means end of string, or immediately
137
+ // before a trailing newline -- NOT the end of every line. A JavaScript
138
+ // `m` flag would match an alias sitting mid-text and silently widen the
139
+ // rule that decides what `--apply` rewrites, so the trailing-newline case
140
+ // is spelled out with a lookahead instead.
141
+ if (new RegExp(`(?:^|\\n)\\s*${escaped}\\s*(?=\\n?$)`).test(text)) {
142
+ candidates.push({ slug, alias, reason: "standalone journey slug/name" });
143
+ break;
144
+ }
145
+ const activation =
146
+ `(?:vamos|quero|preciso|let'?s)\\s+` +
147
+ `(?:retomar\\s+)?(?:trabalhar|ativar|continuar|retomar|work)` +
148
+ `(?:\\s+\\w+){0,5}\\s+(?:journey|jornada|travessia|projeto|project)?\\s*` +
149
+ `(?:no|na|em|on|with)?\\s*${escaped}\\b` +
150
+ `(?=$|\\s*[.,;:!?)]|\\s+(?:em|in|journey|jornada|travessia|modo)\\b)`;
151
+ if (new RegExp(activation).test(text)) {
152
+ candidates.push({ slug, alias, reason: "activation phrase" });
153
+ break;
154
+ }
155
+ }
156
+ }
157
+
158
+ if (candidates.length === 0) return { journey: null, reason: null };
159
+ candidates.sort((left, right) => right.alias.length - left.alias.length);
160
+ const best = candidates[0] as { slug: string; alias: string; reason: string };
161
+ if (candidates.length > 1 && best.alias.length === (candidates[1]?.alias.length ?? -1)) {
162
+ return { journey: null, reason: null };
163
+ }
164
+ return { journey: best.slug, reason: best.reason };
165
+ }
@@ -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
+ }