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,252 @@
1
+ // Front-door route for the extension catalog family (CV22.DS7.TS4 plateau 7):
2
+ // `extensions`, `ext`, `list extensions|all`, and `inspect`'s four TS4 targets.
3
+ //
4
+ // The route is thin on purpose. Every byte it prints was decided by
5
+ // `catalogCommands.ts` or `inspectLedger.ts`, both pure and graded by their own
6
+ // corpora. What happens here is the part a corpus cannot cover: choosing a read
7
+ // or a write handle, composing `list all` from three ported renderers,
8
+ // executing a dispatch decision in a real process, and reporting a LEAF NAME to
9
+ // the front-door log.
10
+ //
11
+ // Two rules shape it:
12
+ //
13
+ // * **Redaction.** `detail` carries the leaf and nothing else. An extension
14
+ // subcommand's argv routinely holds account ids, campaign names, and folder
15
+ // paths — the security-engineer's plan-stage requirement — so a dispatch
16
+ // logs `leaf=<id>` and never what followed it.
17
+ // * **No handle while the host runs.** `ext <id> <subcommand>` reaches the
18
+ // compat host, which opens the database itself, exactly as Python's
19
+ // dispatcher does. So the dispatch decision is taken WITHOUT a handle and
20
+ // the host is spawned with none open: two writers on one file is a
21
+ // SQLITE_BUSY this route would be creating for itself.
22
+
23
+ import type { Database, WritableDatabase } from "#db/database.ts";
24
+ import type { BindingDeps } from "#extensions/bindings.ts";
25
+ import type { RenderedCommand } from "#extensions/catalog.ts";
26
+ import {
27
+ type CatalogContext,
28
+ type ExtWriteContext,
29
+ runExtCommand,
30
+ runExtensionsCommand,
31
+ runInspectCommand,
32
+ runListCommand,
33
+ UnsupportedCatalogCommandError,
34
+ } from "#extensions/catalogCommands.ts";
35
+ import {
36
+ type ExtensionDispatch,
37
+ isExtensionDispatch,
38
+ runExtensionSubcommand,
39
+ } from "#extensions/dispatch.ts";
40
+ import { listJourneysForListCommand } from "#identity/journeyListing.ts";
41
+ import { listPersonas } from "#identity/personaListing.ts";
42
+ import { runInspectEmbeddingProvenance, runInspectLlmCalls } from "#observability/inspectLedger.ts";
43
+ import { renderListJourneys, renderListPersonas } from "./render/list.ts";
44
+
45
+ export interface ExtensionRouteOutcome {
46
+ exitCode: number;
47
+ /** Leaf names only. Never an argument value. */
48
+ detail: string;
49
+ }
50
+
51
+ export interface ExtensionRouteDeps {
52
+ mirrorHome: string;
53
+ databasePath: string;
54
+ deps: BindingDeps;
55
+ /**
56
+ * The database seams, narrowed to an EXIT CODE on purpose: every caller here
57
+ * answers a command, and a generic `<T>` would let a future branch smuggle a
58
+ * value out of a handle that is already closed by the time it is read.
59
+ */
60
+ withReadOnlyDatabase: (run: (db: Database) => number) => number;
61
+ withWritableDatabase: (
62
+ run: (db: WritableDatabase) => Promise<number> | number,
63
+ ) => Promise<number>;
64
+ environment?: NodeJS.ProcessEnv;
65
+ }
66
+
67
+ /** The `extensions` verbs that need a writable handle; the rest read. */
68
+ const EXTENSIONS_WRITE_VERBS = new Set([
69
+ "sync",
70
+ "install",
71
+ "uninstall",
72
+ "expose-claude",
73
+ "clean-claude",
74
+ ]);
75
+ /** The `ext` built-in verbs that write. `bindings` reads; the rest dispatch. */
76
+ const EXT_WRITE_VERBS = new Set(["bind", "unbind", "migrate"]);
77
+ const EXT_READ_VERBS = new Set(["bindings"]);
78
+ const EXT_HELP_HEADS = new Set(["list", "--help", "-h", "help"]);
79
+
80
+ /** A failed `register(api)` during install: Python's traceback, one TS line. */
81
+
82
+ function emit(rendered: RenderedCommand): number {
83
+ if (rendered.stdout) process.stdout.write(rendered.stdout);
84
+ if (rendered.stderr) process.stderr.write(rendered.stderr);
85
+ return rendered.exitCode;
86
+ }
87
+
88
+ /** The dispatcher's own `--mirror-home` consumption, so the verb is the verb. */
89
+ function extArgs(argv: readonly string[]): string[] {
90
+ const args: string[] = [];
91
+ for (let index = 1; index < argv.length; index += 1) {
92
+ const token = argv[index] as string;
93
+ if (token === "--mirror-home" && argv[index + 1] !== undefined) {
94
+ index += 1;
95
+ continue;
96
+ }
97
+ args.push(token);
98
+ }
99
+ return args;
100
+ }
101
+
102
+ /**
103
+ * The leaf name for the front-door log.
104
+ *
105
+ * `extensions install ext-google-ads` logs `leaf=install`: the verb is the
106
+ * leaf and the id is an argument. `ext google-ads campaigns --account 1234`
107
+ * logs `leaf=google-ads` — the installed extension that answered — because the
108
+ * subcommand's arguments are where account ids and campaign names live.
109
+ */
110
+ export function leafFor(argv: readonly string[]): string {
111
+ const command = argv[0] ?? "";
112
+ if (command === "extensions") return firstPositional(argv.slice(1)) ?? "list";
113
+ if (command === "inspect") return firstPositional(argv.slice(1)) ?? "(none)";
114
+ if (command === "list") return firstPositional(argv.slice(1)) ?? "all";
115
+ if (command === "ext") {
116
+ const args = extArgs(argv);
117
+ const head = args[0];
118
+ if (head === undefined) return "(none)";
119
+ if (EXT_HELP_HEADS.has(head)) return head;
120
+ const verb = args[1];
121
+ if (verb !== undefined && (EXT_WRITE_VERBS.has(verb) || EXT_READ_VERBS.has(verb))) {
122
+ return `${head}/${verb}`;
123
+ }
124
+ return head;
125
+ }
126
+ return command;
127
+ }
128
+
129
+ function firstPositional(args: readonly string[]): string | null {
130
+ const valued = new Set(["--mirror-home", "--extensions-root", "--runtime", "--target-root"]);
131
+ for (let index = 0; index < args.length; index += 1) {
132
+ const token = args[index] as string;
133
+ if (valued.has(token)) {
134
+ index += 1;
135
+ continue;
136
+ }
137
+ if (!token.startsWith("--")) return token;
138
+ }
139
+ return null;
140
+ }
141
+
142
+ function readContextFor(route: ExtensionRouteDeps): CatalogContext {
143
+ return { mirrorHome: route.mirrorHome };
144
+ }
145
+
146
+ function dispatchOptions(route: ExtensionRouteDeps) {
147
+ return {
148
+ databasePath: route.databasePath,
149
+ ...(route.environment ? { environment: route.environment } : {}),
150
+ };
151
+ }
152
+
153
+ function writeContextFor(route: ExtensionRouteDeps, db: WritableDatabase): ExtWriteContext {
154
+ return { mirrorHome: route.mirrorHome, db, deps: route.deps };
155
+ }
156
+
157
+ export async function runExtensionCatalogRoute(
158
+ argv: readonly string[],
159
+ route: ExtensionRouteDeps,
160
+ ): Promise<ExtensionRouteOutcome> {
161
+ const command = argv[0] ?? "";
162
+ const args = argv.slice(1);
163
+ const detail = `leaf=${leafFor(argv)}`;
164
+
165
+ // No translation layer here any more: `install` no longer imports extension
166
+ // code, so the one failure class this route used to catch -- a `register(api)`
167
+ // raising inside the Python host -- cannot occur. Manifest and migration
168
+ // failures keep the paths they already had.
169
+ if (command === "extensions") {
170
+ const verb = firstPositional(args) ?? "list";
171
+ if (!EXTENSIONS_WRITE_VERBS.has(verb)) {
172
+ return { exitCode: emit(runExtensionsCommand(readContextFor(route), args)), detail };
173
+ }
174
+ const exitCode = await route.withWritableDatabase((db) =>
175
+ emit(runExtensionsCommand(writeContextFor(route, db), args)),
176
+ );
177
+ return { exitCode, detail };
178
+ }
179
+
180
+ if (command === "inspect") return { exitCode: runInspect(args, route), detail };
181
+ if (command === "list") return { exitCode: runList(args, route), detail };
182
+ if (command === "ext") return { exitCode: await runExt(argv, route), detail };
183
+
184
+ throw new UnsupportedCatalogCommandError(`extension catalog route: ${command}`);
185
+ }
186
+
187
+ /**
188
+ * `inspect`'s four TS4 targets, dispatched by target because they do not share
189
+ * a refusal class: the catalog pair prints usage on stdout at exit 1, and the
190
+ * ledger pair is argparse and exits 2 on stderr. One command, two classes.
191
+ */
192
+ function runInspect(args: readonly string[], route: ExtensionRouteDeps): number {
193
+ const target = firstPositional(args);
194
+ if (target === "llm-calls" || target === "embedding-provenance") {
195
+ return route.withReadOnlyDatabase((db) => {
196
+ const result =
197
+ target === "llm-calls"
198
+ ? runInspectLlmCalls(db, args.slice(1))
199
+ : runInspectEmbeddingProvenance(db, args.slice(1));
200
+ return emit(result);
201
+ });
202
+ }
203
+ return emit(runInspectCommand(readContextFor(route), args));
204
+ }
205
+
206
+ /**
207
+ * `list`, composed.
208
+ *
209
+ * `all` prints personas, journeys, and extensions in that order with a blank
210
+ * line between blocks — three renderers from two stories, which is why this
211
+ * composition lives at the front door rather than inside either of them.
212
+ */
213
+ function runList(args: readonly string[], route: ExtensionRouteDeps): number {
214
+ const target = firstPositional(args) ?? "all";
215
+ const verbose = args.includes("--verbose");
216
+ if (target === "extensions") return emit(runListCommand(readContextFor(route), args));
217
+
218
+ return route.withReadOnlyDatabase((db) => {
219
+ process.stdout.write(renderListPersonas(listPersonas(db), verbose));
220
+ process.stdout.write("\n");
221
+ process.stdout.write(renderListJourneys(listJourneysForListCommand(db)));
222
+ process.stdout.write("\n");
223
+ // The extension block of `all` is `list extensions` with the same options.
224
+ return emit(runListCommand(readContextFor(route), ["extensions", ...args.slice(1)]));
225
+ });
226
+ }
227
+
228
+ /**
229
+ * `ext`, in three shapes.
230
+ *
231
+ * The built-in verbs take a handle (write or read). The dispatch takes NONE:
232
+ * the decision is pure, and the host that executes it opens the database
233
+ * itself, exactly as Python's dispatcher does.
234
+ */
235
+ async function runExt(argv: readonly string[], route: ExtensionRouteDeps): Promise<number> {
236
+ const args = extArgs(argv);
237
+ const verb = args[1];
238
+ if (verb !== undefined && EXT_WRITE_VERBS.has(verb)) {
239
+ return route.withWritableDatabase((db) =>
240
+ emit(runExtCommand(writeContextFor(route, db), argv.slice(1)) as RenderedCommand),
241
+ );
242
+ }
243
+ if (verb !== undefined && EXT_READ_VERBS.has(verb)) {
244
+ return route.withWritableDatabase((db) =>
245
+ emit(runExtCommand(writeContextFor(route, db), argv.slice(1)) as RenderedCommand),
246
+ );
247
+ }
248
+
249
+ const answer = runExtCommand(readContextFor(route), argv.slice(1));
250
+ if (!isExtensionDispatch(answer)) return emit(answer);
251
+ return runExtensionSubcommand(answer as ExtensionDispatch, dispatchOptions(route));
252
+ }
@@ -0,0 +1,57 @@
1
+ // Durable, redacted observability for the front door (CR026).
2
+ //
3
+ // The front door is a production write path that previously left no trace of
4
+ // backup failures, guard refusals, SQLITE_BUSY, or resolution problems. This
5
+ // appends a metadata-only line per invocation to `<mirror home>/front-door.log`
6
+ // (beside `mirror-logger.log`), under a fail-quietly contract: logging never
7
+ // throws and never blocks the user command.
8
+ //
9
+ // Redaction is structural (RS005/CR033): a `FrontDoorLogEntry` carries only the
10
+ // command name, the route, the exit code, and a short error CATEGORY — never
11
+ // argv payloads (`--content`) or stdin. Callers must not put argument values in
12
+ // `detail`.
13
+
14
+ import { appendFileSync } from "node:fs";
15
+ import { dirname, join } from "node:path";
16
+
17
+ export interface FrontDoorLogEntry {
18
+ command: string | null;
19
+ /**
20
+ * `retired` (CV22.DS10.TS4) is a route like the other two: the front door
21
+ * answered, and the answer was that the surface no longer exists. It is
22
+ * logged so "my command disappeared" stays answerable from the log after the
23
+ * story that removed it is history.
24
+ *
25
+ * `usage` (CV22.DS10.TS5) is the front door's answer for a name it does not
26
+ * know -- the answer Python owned until TS5 deleted it.
27
+ */
28
+ route: "ts" | "retired" | "usage";
29
+ exitCode: number;
30
+ /** A short, content-free note or error category. Never argument values. */
31
+ detail?: string;
32
+ }
33
+
34
+ /** The log path for a resolved database path (sibling of the DB). */
35
+ export function frontDoorLogPath(dbPath: string): string {
36
+ return join(dirname(dbPath), "front-door.log");
37
+ }
38
+
39
+ /** Append one redacted line. Fail-quietly: any error is swallowed. */
40
+ export function logFrontDoor(logPath: string | null, entry: FrontDoorLogEntry): void {
41
+ if (!logPath) return;
42
+ try {
43
+ const level = entry.exitCode === 0 ? "INFO" : "ERROR";
44
+ const detail = entry.detail ? entry.detail.replace(/[\r\n\t]+/g, " ") : "";
45
+ const line = [
46
+ new Date().toISOString(),
47
+ level,
48
+ entry.command ?? "(none)",
49
+ entry.route,
50
+ `exit=${entry.exitCode}`,
51
+ detail,
52
+ ].join("\t");
53
+ appendFileSync(logPath, `${line}\n`);
54
+ } catch {
55
+ // Logging must never break the user command (fail-quietly contract).
56
+ }
57
+ }
@@ -0,0 +1,36 @@
1
+ // Front-door `identity set` write handler (US4).
2
+ //
3
+ // `applyIdentitySet` is the testable core of routing `identity set` to the TS
4
+ // core: it decides the created/updated verb from the pre-write state and applies
5
+ // the ported `setIdentity` (US3) with a generated id/now. The pre-write backup
6
+ // that `openDatabaseForWrite` requires lives in `liveBackup.ts`. Only
7
+ // `identity set` routes here directly. Since CV22.DS7.TS4 plateau 6,
8
+ // `identity edit` shares this write: it drives $EDITOR, then applies the buffer
9
+ // through `applyIdentitySet` so both commands report the same created/updated
10
+ // verb from the same pre-write check.
11
+
12
+ import type { WritableDatabase } from "#db/database.ts";
13
+ import { setIdentity } from "#identity/setIdentity.ts";
14
+
15
+ export interface IdentitySetOutcome {
16
+ action: "created" | "updated";
17
+ layer: string;
18
+ key: string;
19
+ }
20
+
21
+ /** Apply `identity set`: upsert content (metadata inherited), reporting the verb. */
22
+ export function applyIdentitySet(
23
+ db: WritableDatabase,
24
+ params: { layer: string; key: string; content: string; id: string; nowIso: string },
25
+ ): IdentitySetOutcome {
26
+ const existing =
27
+ db
28
+ .prepare("SELECT id FROM identity WHERE layer = ? AND key = ?")
29
+ .get(params.layer, params.key) !== undefined;
30
+ setIdentity(
31
+ db,
32
+ { id: params.id, layer: params.layer, key: params.key, content: params.content },
33
+ params.nowIso,
34
+ );
35
+ return { action: existing ? "updated" : "created", layer: params.layer, key: params.key };
36
+ }
@@ -0,0 +1,26 @@
1
+ // Front-door `journey set-path` write handler (US5).
2
+ //
3
+ // applyJourneySetPath is the testable core of routing `journey set-path` to the TS
4
+ // core: it normalizes the path the way Python does, then delegates to the ported
5
+ // US2 setProjectPath over the US4 live-write seam. setProjectPath throws when the
6
+ // journey does not exist; the cli wrapper maps that to Python's not-found output.
7
+
8
+ import type { WritableDatabase } from "#db/database.ts";
9
+ import { setProjectPath } from "#journey/journeyWrite.ts";
10
+ import { normalizeProjectPath } from "#util/paths.ts";
11
+
12
+ /**
13
+ * Apply `journey set-path`: normalize `rawPath` (expanduser + resolve, matching
14
+ * Python), write it into the journey's `project_path` metadata via setProjectPath,
15
+ * and return the resolved path. Throws `journey not found: <slug>` when absent.
16
+ */
17
+ export function applyJourneySetPath(
18
+ db: WritableDatabase,
19
+ slug: string,
20
+ rawPath: string,
21
+ nowIso: string,
22
+ ): string {
23
+ const resolved = normalizeProjectPath(rawPath);
24
+ setProjectPath(db, slug, resolved, nowIso);
25
+ return resolved;
26
+ }
@@ -0,0 +1,110 @@
1
+ // The ES-001 metadata-lifecycle WRITE faces at the front door
2
+ // (CV22.DS7.TS4 plateau 7).
3
+ //
4
+ // `conversations --metadata-lifecycle-apply <id> [--title/--summary/--tag]` and
5
+ // `--metadata-lifecycle-demo`. Both print `json.dumps(report, ensure_ascii=
6
+ // False, indent=2)`, so the rendering is `JSON.stringify(report, null, 2)` plus
7
+ // the newline `print` adds, and key ORDER is part of those bytes.
8
+ //
9
+ // The demo builds its own throwaway world. Python's is `sqlite3.connect(
10
+ // ":memory:")` with the schema applied; here it is a bootstrapped temp file
11
+ // removed on the way out, because `node:sqlite`'s in-memory handle cannot be
12
+ // given the migration ledger the bootstrap writes. Either way the claim it
13
+ // prints — `uses_production_data: false` — stays literally true: the caller's
14
+ // database is opened, never written, and never read by the demo.
15
+
16
+ import { mkdtempSync, rmSync } from "node:fs";
17
+ import { tmpdir } from "node:os";
18
+ import { join } from "node:path";
19
+ import { LifecycleFaceError } from "#conversation/lifecycleFaces.ts";
20
+ import {
21
+ applyMetadataLifecycle,
22
+ type DemoSeed,
23
+ metadataLifecycleDemoReport,
24
+ writeTitle,
25
+ } from "#conversation/lifecycleWrites.ts";
26
+ import { bootstrapDatabase } from "#db/bootstrap.ts";
27
+ import type { WritableDatabase } from "#db/database.ts";
28
+ import { newId, nowIso } from "#util/pyGenerators.ts";
29
+
30
+ /** Python's `print(json.dumps(report, ensure_ascii=False, indent=2))`. */
31
+ function printReport(report: unknown): number {
32
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
33
+ return 0;
34
+ }
35
+
36
+ function optionValue(argv: readonly string[], flag: string): string | undefined {
37
+ const index = argv.indexOf(flag);
38
+ return index >= 0 ? argv[index + 1] : undefined;
39
+ }
40
+
41
+ /** `--tag` is singular and REPEATABLE (`dest="tags"`, `action="append"`). */
42
+ function tagValues(argv: readonly string[]): string[] | undefined {
43
+ const tags: string[] = [];
44
+ for (let index = 0; index < argv.length; index += 1) {
45
+ if (argv[index] !== "--tag") continue;
46
+ const value = argv[index + 1];
47
+ if (value !== undefined) tags.push(value);
48
+ }
49
+ return tags.length > 0 ? tags : undefined;
50
+ }
51
+
52
+ export function runLifecycleWriteRoute(db: WritableDatabase, args: readonly string[]): number {
53
+ if (args.includes("--metadata-lifecycle-demo")) return runDemo();
54
+
55
+ const conversationId = optionValue(args, "--metadata-lifecycle-apply");
56
+ if (conversationId === undefined) {
57
+ console.error("Mirror TS front door: --metadata-lifecycle-apply requires a conversation id");
58
+ return 2;
59
+ }
60
+ const title = optionValue(args, "--title");
61
+ const summary = optionValue(args, "--summary");
62
+ const tags = tagValues(args);
63
+ try {
64
+ return printReport(
65
+ applyMetadataLifecycle(db, conversationId, {
66
+ ...(title !== undefined ? { title } : {}),
67
+ ...(summary !== undefined ? { summary } : {}),
68
+ ...(tags !== undefined ? { tags } : {}),
69
+ }),
70
+ );
71
+ } catch (error) {
72
+ if (!(error instanceof LifecycleFaceError)) throw error;
73
+ // Python raises ValueError and the traceback escapes: same exit code, same
74
+ // stream, one line.
75
+ console.error(error.message);
76
+ return 1;
77
+ }
78
+ }
79
+
80
+ function runDemo(): number {
81
+ const root = mkdtempSync(join(tmpdir(), "mirror-lifecycle-demo-"));
82
+ try {
83
+ const db = bootstrapDatabase(join(root, "memory_test.db"));
84
+ try {
85
+ const seed: DemoSeed = {
86
+ startConversation: (interfaceName, title) => {
87
+ const id = newId();
88
+ db.prepare(
89
+ "INSERT INTO conversations (id, interface, title, started_at) VALUES (?, ?, ?, ?)",
90
+ ).run(id, interfaceName, title ?? null, nowIso());
91
+ return id;
92
+ },
93
+ addMessage: (conversationId, role, content) => {
94
+ db.prepare(
95
+ "INSERT INTO messages (id, conversation_id, role, content, created_at) " +
96
+ "VALUES (?, ?, ?, ?, ?)",
97
+ ).run(newId(), conversationId, role, content, nowIso());
98
+ },
99
+ setProvisionalTitle: (conversationId, title) =>
100
+ writeTitle(db, conversationId, title, "provisional"),
101
+ updateTitle: (conversationId, title) => writeTitle(db, conversationId, title, "manual"),
102
+ };
103
+ return printReport(metadataLifecycleDemoReport(db, seed));
104
+ } finally {
105
+ db.close();
106
+ }
107
+ } finally {
108
+ rmSync(root, { recursive: true, force: true });
109
+ }
110
+ }
@@ -0,0 +1,100 @@
1
+ // Pre-write backup and restore for the front door's sanctioned live writes.
2
+ //
3
+ // Every routed live write takes a fresh snapshot first; `openDatabaseForWrite`
4
+ // refuses to open without a hash-verified record of it. The snapshot is taken
5
+ // with `VACUUM INTO` (WAL-correct — see `snapshotDatabaseTo`), stored under the
6
+ // mirror home's `backups/` directory (the same convention the Python backup
7
+ // tooling uses), and permission-tightened to owner-only.
8
+ //
9
+ // Overwrite-window semantics, stated plainly: the backup file has a FIXED name
10
+ // and is replaced before EVERY write, so it always holds the state immediately
11
+ // before the most recent routed write — and nothing older. If a write corrupts
12
+ // data and another routed write runs before anyone notices, the corrupted
13
+ // state becomes the backed-up state. The backup is a last-write undo, not an
14
+ // archive; scheduled archives remain `mm-backup`'s job.
15
+ //
16
+ // Concurrent writers (CV22.DS10.TS5, finding F21). Until F21 every writer
17
+ // removed the fixed file and recreated it in place with `VACUUM INTO`, with no
18
+ // coordination. Two writers at the same instant broke each other: the second
19
+ // `VACUUM INTO` found the tables the first was still creating ("table
20
+ // conversations already exists"), or one removed the file the other was still
21
+ // writing ("disk I/O error"), and the losing write was aborted. Claude Code
22
+ // runs its two prompt hooks at once and both write, so the Navigator walk lost
23
+ // two injections in three prompts; Pi had been losing turns in daily use.
24
+ //
25
+ // So each writer now snapshots into a staging file of its own, the gate
26
+ // verifies THAT file -- which no other writer can touch -- and only then is it
27
+ // promoted to the fixed name by an atomic rename. The fixed name keeps its
28
+ // meaning (the state before the most recently promoted write) and can no
29
+ // longer be observed half-written. Under true concurrency "the most recent
30
+ // write" is whichever promoted last; nothing stronger was ever true.
31
+ //
32
+ // One residue remains possible: a writer KILLED between its snapshot and its
33
+ // rename leaves its staging file, the size of the database. Each write sweeps
34
+ // those before taking its own -- a staging file whose process no longer runs
35
+ // and which is older than any snapshot takes (TS5 handoff review, finding N4).
36
+
37
+ import { chmodSync, copyFileSync, mkdirSync, renameSync, rmSync } from "node:fs";
38
+ import { dirname, join } from "node:path";
39
+ import { stagingToken, sweepAbandoned } from "#backup/staging.ts";
40
+ import { type BackupRecord, requireBackup, sha256File } from "#db/backupGate.ts";
41
+ import { openDatabaseForWrite, snapshotDatabaseTo, type WritableDatabase } from "#db/database.ts";
42
+
43
+ const BACKUP_DIR_NAME = "backups";
44
+ const BACKUP_FILE_NAME = "frontdoor-pre-write-backup.db";
45
+
46
+ /** Where the pre-write backup for a given live database lives. */
47
+ export function backupPathFor(dbPath: string): string {
48
+ return join(dirname(dbPath), BACKUP_DIR_NAME, BACKUP_FILE_NAME);
49
+ }
50
+
51
+ /** The name a writer gives its staging file: `<fixed name>.<pid>-<12 hex>.staging`. */
52
+ const STAGING_FILE = /^frontdoor-pre-write-backup\.db\.(\d+)-[0-9a-f]{12}\.staging$/;
53
+
54
+ /**
55
+ * Open the live database for ONE routed write, behind a verified pre-write
56
+ * snapshot. The only sanctioned way to open a live `memory.db` for writing.
57
+ *
58
+ * 1. Snapshot into a staging file unique to this process and call
59
+ * (WAL-correct via `VACUUM INTO`; owner-only: 0700 dir, 0600 file).
60
+ * 2. The backup gate verifies the staging file's hash, and the database opens.
61
+ * 3. The staging file is promoted to the fixed name by an atomic rename.
62
+ *
63
+ * A failure at any step aborts the write and removes the staging file, so a
64
+ * failed snapshot never leaves a half-written file under the fixed name. A
65
+ * staging file a KILLED writer left behind is swept first (finding N4).
66
+ */
67
+ export function openLiveWriteDatabase(dbPath: string): WritableDatabase {
68
+ const backupPath = backupPathFor(dbPath);
69
+ mkdirSync(dirname(backupPath), { recursive: true, mode: 0o700 });
70
+ sweepAbandoned(dirname(backupPath), STAGING_FILE);
71
+ const staging = `${backupPath}.${stagingToken()}.staging`;
72
+ let db: WritableDatabase | null = null;
73
+ try {
74
+ snapshotDatabaseTo(dbPath, staging);
75
+ chmodSync(staging, 0o600);
76
+ db = openDatabaseForWrite(dbPath, { path: staging, sha256: sha256File(staging) });
77
+ renameSync(staging, backupPath);
78
+ return db;
79
+ } catch (error) {
80
+ db?.close();
81
+ throw error;
82
+ } finally {
83
+ // A no-op after the rename; the cleanup when anything before it failed.
84
+ rmSync(staging, { force: true });
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Restore the live database from a verified pre-write backup: re-verify the
90
+ * record's hash, copy the snapshot over the live file, and remove stale
91
+ * `-wal`/`-shm` sidecars so SQLite cannot replay post-backup pages over the
92
+ * restored state. Callers must ensure nothing holds the database open.
93
+ */
94
+ export function restoreFromBackup(backup: BackupRecord, dbPath: string): void {
95
+ requireBackup(backup);
96
+ copyFileSync(backup.path, dbPath);
97
+ rmSync(`${dbPath}-wal`, { force: true });
98
+ rmSync(`${dbPath}-shm`, { force: true });
99
+ chmodSync(dbPath, 0o600);
100
+ }