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,111 @@
1
+ // `init` — bootstrap a user home from repository identity templates. The port
2
+ // of memory.cli.init. Filesystem-only: no database is opened or touched.
3
+ //
4
+ // Deliberate, verified divergence: Python's `main()` never catches
5
+ // FileExistsError/FileNotFoundError from init_user_home, so those paths exit
6
+ // via an uncaught exception — a raw Python traceback with this machine's
7
+ // absolute file paths on stderr, exit 1. That traceback is an implementation
8
+ // leak, not a designed CLI contract (unlike, say, `identity get`'s deliberate
9
+ // one-line not-found message) — it is not meaningful or possible to replicate
10
+ // byte-for-byte (it depends on Python's own call stack and module paths, which
11
+ // drift on every refactor and reveal nothing product-relevant). This port
12
+ // preserves the observable CONTRACT instead: exit 1, and a stderr message that
13
+ // names the failure. See the CLI wiring in ../frontDoor/cli.ts.
14
+
15
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
16
+ import { homedir } from "node:os";
17
+ import { join } from "node:path";
18
+ import { runningTreeRoot } from "#runtime/treeRoot.ts";
19
+
20
+ /** Port of Python's uncaught `FileNotFoundError` for a missing templates root. */
21
+ export class TemplatesNotFoundError extends Error {}
22
+
23
+ /** Port of Python's uncaught `FileExistsError` for a non-empty identity root. */
24
+ export class IdentityRootExistsError extends Error {
25
+ readonly path: string;
26
+ constructor(path: string) {
27
+ super(`Identity root already exists and is not empty: ${path}`);
28
+ this.path = path;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * `<tree root>/templates/identity`. Python's `find_templates_identity_root`
34
+ * walked up from its own file for the directory; since CV22.DS10.US3 the tree
35
+ * root is one resolver shared with release notes and the welcome card, and
36
+ * the templates ship in the package at the same path as in the checkout.
37
+ */
38
+ export function templatesIdentityRoot(treeRoot: string = runningTreeRoot()): string {
39
+ const candidate = join(treeRoot, "templates", "identity");
40
+ if (existsSync(candidate)) return candidate;
41
+ throw new TemplatesNotFoundError(`Could not find templates/identity under ${treeRoot}.`);
42
+ }
43
+
44
+ /**
45
+ * Where `init` creates a user's home: `<home>/.mirror-minds/<user>`, the path
46
+ * `MIRROR_USER=<user>` resolves to.
47
+ *
48
+ * Python's `default_user_home` hardcoded the legacy `~/.mirror/<user>` and told
49
+ * the person to set MIRROR_HOME by hand. CV22.DS10.US3 (D3) has `init` write
50
+ * `MIRROR_USER` into the config file instead, and a legacy home would then
51
+ * make every later command print the legacy-path warning (D14).
52
+ */
53
+ export function defaultUserHome(user: string, home: string = homedir()): string {
54
+ return join(home, ".mirror-minds", user);
55
+ }
56
+
57
+ /** Every `.yaml` file under `root`, recursively (mirrors `Path.rglob("*.yaml")`). */
58
+ function yamlFilesRecursive(root: string): string[] {
59
+ const out: string[] = [];
60
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
61
+ const full = join(root, entry.name);
62
+ if (entry.isDirectory()) out.push(...yamlFilesRecursive(full));
63
+ else if (entry.isFile() && full.endsWith(".yaml")) out.push(full);
64
+ }
65
+ return out;
66
+ }
67
+
68
+ /**
69
+ * Port of `_substitute_user_name`: replace every `{{user_name}}` token in every
70
+ * `.yaml` file under `identityRoot` with `user`, writing back only files that
71
+ * actually contained the token. Non-`.yaml` files (e.g. `README.md`) are
72
+ * intentionally left untouched, matching the Python glob scope exactly.
73
+ */
74
+ function substituteUserName(identityRoot: string, user: string): void {
75
+ for (const file of yamlFilesRecursive(identityRoot)) {
76
+ const content = readFileSync(file, "utf8");
77
+ if (content.includes("{{user_name}}")) {
78
+ writeFileSync(file, content.replaceAll("{{user_name}}", user), "utf8");
79
+ }
80
+ }
81
+ }
82
+
83
+ export interface InitUserHomeOptions {
84
+ /** Overrides the located templates root (tests only; production auto-locates it). */
85
+ templatesIdentityRoot?: string;
86
+ /** Overrides the destination home (tests only; production uses defaultUserHome). */
87
+ userHome?: string;
88
+ }
89
+
90
+ /**
91
+ * Port of `init_user_home`: locate the identity templates, refuse a non-empty
92
+ * existing destination, copy the template tree, substitute `{{user_name}}` in
93
+ * every `.yaml` file, and return the destination identity root.
94
+ */
95
+ export function initUserHome(user: string, options: InitUserHomeOptions = {}): string {
96
+ const templatesRoot = options.templatesIdentityRoot ?? templatesIdentityRoot();
97
+ if (!existsSync(templatesRoot)) {
98
+ throw new TemplatesNotFoundError(`Identity templates not found: ${templatesRoot}`);
99
+ }
100
+
101
+ const destinationHome = options.userHome ?? defaultUserHome(user);
102
+ const identityRoot = join(destinationHome, "identity");
103
+ if (existsSync(identityRoot) && readdirSync(identityRoot).length > 0) {
104
+ throw new IdentityRootExistsError(identityRoot);
105
+ }
106
+
107
+ mkdirSync(destinationHome, { recursive: true });
108
+ cpSync(templatesRoot, identityRoot, { recursive: true });
109
+ substituteUserName(identityRoot, user);
110
+ return identityRoot;
111
+ }
@@ -0,0 +1,131 @@
1
+ // Journey listing parity port (CV22.DS2.US3).
2
+ //
3
+ // A faithful TypeScript port of Python `JourneyService.list_journey_options` +
4
+ // `_sort_journey_options` (`src/memory/services/journey.py`). The logic is pure
5
+ // over the `journey` identity rows: it derives each journey's display name,
6
+ // status, and parent from row content/metadata, then orders every descendant in
7
+ // bounded depth-first order. Reading the rows out of the SQLite
8
+ // seam belongs to the caller, keeping this the pure decision core.
9
+
10
+ import { resolveParentJourney } from "./parentJourney.ts";
11
+
12
+ /** One `journey`-layer identity row, as read from the DB. */
13
+ export interface JourneyIdentityRow {
14
+ key: string;
15
+ content: string;
16
+ metadata?: string | null;
17
+ /** Migration 017's derived parent projection. Metadata remains semantic
18
+ * authority while Python and TS can both write parentage (CR050). */
19
+ parent_journey?: string | null;
20
+ }
21
+
22
+ /** A journey option DTO with hierarchy metadata, mirroring the Python output. */
23
+ export interface JourneyOption {
24
+ id: string;
25
+ name: string;
26
+ status: string;
27
+ parent_journey: string;
28
+ depth: number;
29
+ lineage: string[];
30
+ }
31
+
32
+ type UnorderedJourneyOption = Omit<JourneyOption, "depth" | "lineage">;
33
+
34
+ /** Build the unordered option fields for a single journey row. */
35
+ function toOption(row: JourneyIdentityRow): UnorderedJourneyOption {
36
+ const content = row.content || "";
37
+ // Python: content.split("\n")[0].strip().lstrip("# ").strip()
38
+ const firstLine = (content.split("\n")[0] ?? "")
39
+ .trim()
40
+ .replace(/^[# ]+/, "")
41
+ .trim();
42
+ const statusMatch = content.match(/\*\*Status:\*\*\s*([^\n]+)/);
43
+ const status = statusMatch ? statusMatch[1].trim() : "unknown";
44
+ return {
45
+ id: row.key,
46
+ name: firstLine || row.key,
47
+ status,
48
+ parent_journey: resolveParentJourney(row),
49
+ };
50
+ }
51
+
52
+ /** A roots-then-children split of journey-like items keyed by `parent_journey`. */
53
+ export interface JourneyHierarchy<T> {
54
+ roots: T[];
55
+ childrenByParent: Map<string, T[]>;
56
+ }
57
+
58
+ /**
59
+ * Split items into roots and children by `parent_journey`, preserving input
60
+ * order within each group. An item whose parent is empty or absent from the set
61
+ * is a root. The depth-first sorter consumes this index; renderers consume the
62
+ * resulting ordered projection and must not reconstruct a shallower hierarchy.
63
+ */
64
+ export function groupJourneysByParent<T extends { id: string; parent_journey: string }>(
65
+ items: readonly T[],
66
+ ): JourneyHierarchy<T> {
67
+ const knownIds = new Set(items.map((item) => item.id));
68
+ const roots: T[] = [];
69
+ const childrenByParent = new Map<string, T[]>();
70
+ for (const item of items) {
71
+ const parent = item.parent_journey || "";
72
+ if (parent && knownIds.has(parent)) {
73
+ const bucket = childrenByParent.get(parent);
74
+ if (bucket) bucket.push(item);
75
+ else childrenByParent.set(parent, [item]);
76
+ } else {
77
+ roots.push(item);
78
+ }
79
+ }
80
+ return { roots, childrenByParent };
81
+ }
82
+
83
+ /**
84
+ * Order every option in bounded depth-first hierarchy order, mirroring the
85
+ * released Python `_sort_journey_options` oracle.
86
+ *
87
+ * Roots (no parent, or a parent absent from the set) and every sibling set are
88
+ * sorted by `(status !== "active", name.toLowerCase())`. A visited set makes
89
+ * malformed legacy cycles bounded; a final sorted pass starts each rootless
90
+ * component so corrupt rows remain visible exactly once.
91
+ */
92
+ function sortJourneyOptions(options: UnorderedJourneyOption[]): JourneyOption[] {
93
+ const { roots, childrenByParent } = groupJourneysByParent(options);
94
+
95
+ const compare = (a: UnorderedJourneyOption, b: UnorderedJourneyOption): number => {
96
+ const aInactive = a.status !== "active" ? 1 : 0;
97
+ const bInactive = b.status !== "active" ? 1 : 0;
98
+ if (aInactive !== bInactive) return aInactive - bInactive;
99
+ const aName = a.name.toLowerCase();
100
+ const bName = b.name.toLowerCase();
101
+ return aName < bName ? -1 : aName > bName ? 1 : 0;
102
+ };
103
+
104
+ const ordered: JourneyOption[] = [];
105
+ const visited = new Set<string>();
106
+
107
+ const appendBranch = (item: UnorderedJourneyOption, lineage: string[]): void => {
108
+ if (visited.has(item.id)) return;
109
+ visited.add(item.id);
110
+ const currentLineage = [...lineage, item.id];
111
+ ordered.push({ ...item, depth: lineage.length, lineage: currentLineage });
112
+ for (const child of [...(childrenByParent.get(item.id) ?? [])].sort(compare)) {
113
+ appendBranch(child, currentLineage);
114
+ }
115
+ };
116
+
117
+ for (const root of [...roots].sort(compare)) appendBranch(root, []);
118
+ for (const unvisited of [...options].sort(compare)) {
119
+ if (!visited.has(unvisited.id)) appendBranch(unvisited, []);
120
+ }
121
+ return ordered;
122
+ }
123
+
124
+ /**
125
+ * Return all journeys as option DTOs with hierarchy metadata, reproducing the
126
+ * Python oracle. `rows` must arrive in the DB's `ORDER BY key` order (as
127
+ * `get_identity_by_layer` returns them) so stable-sort tie-breaks match Python.
128
+ */
129
+ export function listJourneyOptions(rows: readonly JourneyIdentityRow[]): JourneyOption[] {
130
+ return sortJourneyOptions(rows.map(toOption));
131
+ }
@@ -0,0 +1,158 @@
1
+ // Conservative journey removal ported from the v0.31.9 Python domain operation.
2
+ //
3
+ // A journey is deletable only when it exists, has no metadata-authoritative
4
+ // children, and has no durable association in the closed inventory below. The
5
+ // complete check and the single identity DELETE share BEGIN IMMEDIATE so a
6
+ // competing writer that commits first is visible before the decision.
7
+
8
+ import { type WritableDatabase, withTransaction } from "#db/database.ts";
9
+
10
+ export interface JourneyAssociationCounts {
11
+ child_journeys: number;
12
+ journey_paths: number;
13
+ identity_integrations: number;
14
+ conversations: number;
15
+ memories: number;
16
+ tasks: number;
17
+ attachments: number;
18
+ runtime_sessions: number;
19
+ explorer_stories: number;
20
+ refinement_stories: number;
21
+ change_requests: number;
22
+ refinement_cursors: number;
23
+ }
24
+
25
+ export class JourneyRemovalError extends Error {
26
+ readonly slug: string;
27
+ readonly associations?: JourneyAssociationCounts;
28
+
29
+ constructor(slug: string, message: string, associations?: JourneyAssociationCounts) {
30
+ super(message);
31
+ this.slug = slug;
32
+ this.associations = associations;
33
+ }
34
+ }
35
+
36
+ function count(db: WritableDatabase, sql: string, journey: string): number {
37
+ const value = db.prepare(sql).get(journey)?.count;
38
+ if (typeof value === "number") return value;
39
+ if (typeof value === "bigint") return Number(value);
40
+ throw new Error("journey association count did not return a number");
41
+ }
42
+
43
+ /**
44
+ * Count the released closed inventory of records that removal would orphan.
45
+ * Every query is a fixed literal. Child parentage comes only from valid JSON
46
+ * metadata (CR050 semantic authority), never migration 017's projection.
47
+ */
48
+ export function countJourneyAssociations(
49
+ db: WritableDatabase,
50
+ journey: string,
51
+ ): JourneyAssociationCounts {
52
+ return {
53
+ child_journeys: count(
54
+ db,
55
+ `SELECT COUNT(*) AS count FROM identity
56
+ WHERE layer = 'journey'
57
+ AND CASE WHEN json_valid(metadata)
58
+ THEN json_extract(metadata, '$.parent_journey') END = ?`,
59
+ journey,
60
+ ),
61
+ journey_paths: count(
62
+ db,
63
+ "SELECT COUNT(*) AS count FROM identity WHERE layer = 'journey_path' AND key = ?",
64
+ journey,
65
+ ),
66
+ identity_integrations: count(
67
+ db,
68
+ "SELECT COUNT(*) AS count FROM identity_integrations WHERE layer = 'journey' AND key = ?",
69
+ journey,
70
+ ),
71
+ conversations: count(
72
+ db,
73
+ "SELECT COUNT(*) AS count FROM conversations WHERE journey = ?",
74
+ journey,
75
+ ),
76
+ memories: count(db, "SELECT COUNT(*) AS count FROM memories WHERE journey = ?", journey),
77
+ tasks: count(db, "SELECT COUNT(*) AS count FROM tasks WHERE journey = ?", journey),
78
+ attachments: count(
79
+ db,
80
+ "SELECT COUNT(*) AS count FROM attachments WHERE journey_id = ?",
81
+ journey,
82
+ ),
83
+ runtime_sessions: count(
84
+ db,
85
+ "SELECT COUNT(*) AS count FROM runtime_sessions WHERE journey = ?",
86
+ journey,
87
+ ),
88
+ explorer_stories: count(
89
+ db,
90
+ "SELECT COUNT(*) AS count FROM exploratory_stories WHERE journey = ?",
91
+ journey,
92
+ ),
93
+ refinement_stories: count(
94
+ db,
95
+ "SELECT COUNT(*) AS count FROM builder_refinement_stories WHERE journey = ?",
96
+ journey,
97
+ ),
98
+ change_requests: count(
99
+ db,
100
+ "SELECT COUNT(*) AS count FROM builder_change_requests WHERE journey = ?",
101
+ journey,
102
+ ),
103
+ refinement_cursors: count(
104
+ db,
105
+ "SELECT COUNT(*) AS count FROM builder_refinement_cursors WHERE journey = ?",
106
+ journey,
107
+ ),
108
+ };
109
+ }
110
+
111
+ const ASSOCIATED_RECORD_KEYS = [
112
+ "journey_paths",
113
+ "identity_integrations",
114
+ "conversations",
115
+ "memories",
116
+ "tasks",
117
+ "attachments",
118
+ "runtime_sessions",
119
+ "explorer_stories",
120
+ "refinement_stories",
121
+ "change_requests",
122
+ "refinement_cursors",
123
+ ] as const satisfies readonly (keyof JourneyAssociationCounts)[];
124
+
125
+ /** Remove exactly one empty leaf journey, or throw without mutating state. */
126
+ export function removeJourney(db: WritableDatabase, journey: string): true {
127
+ return withTransaction(db, () => {
128
+ const exists = db
129
+ .prepare("SELECT 1 AS present FROM identity WHERE layer = 'journey' AND key = ?")
130
+ .get(journey);
131
+ if (exists === undefined) {
132
+ throw new JourneyRemovalError(journey, `Journey '${journey}' not found`);
133
+ }
134
+
135
+ const associations = countJourneyAssociations(db, journey);
136
+ if (associations.child_journeys > 0) {
137
+ throw new JourneyRemovalError(
138
+ journey,
139
+ `Journey '${journey}' has child journeys; move or remove them first`,
140
+ associations,
141
+ );
142
+ }
143
+
144
+ const details = ASSOCIATED_RECORD_KEYS.filter((key) => associations[key] > 0).map(
145
+ (key) => `${key}=${associations[key]}`,
146
+ );
147
+ if (details.length > 0) {
148
+ throw new JourneyRemovalError(
149
+ journey,
150
+ `Journey '${journey}' has associated records: ${details.join(", ")}`,
151
+ associations,
152
+ );
153
+ }
154
+
155
+ db.prepare("DELETE FROM identity WHERE layer = 'journey' AND key = ?").run(journey);
156
+ return true;
157
+ });
158
+ }
@@ -0,0 +1,149 @@
1
+ // `journey status` read primitives — the port of JourneyService.get_journey_status,
2
+ // get_journey_path, get_sync_file, and the store reads it composes
3
+ // (get_memories_by_journey, get_recent_conversations_by_journey).
4
+
5
+ import { readFileSync } from "node:fs";
6
+ import type { Database } from "#db/database.ts";
7
+ import { optionalString, requireString } from "#db/rowDecode.ts";
8
+ import { getIdentityContent, getIdentityMetadata } from "#identity/identityRead.ts";
9
+ import { expandHome } from "#util/paths.ts";
10
+
11
+ const JOURNEY_LAYER = "journey";
12
+ /** The `journey_path` identity layer, also the write target of `journey update`. */
13
+ export const JOURNEY_PATH_LAYER = "journey_path";
14
+
15
+ export interface JourneyStatusMemoryRow {
16
+ created_at: string;
17
+ title: string;
18
+ }
19
+
20
+ export interface JourneyStatusConversationRow {
21
+ started_at: string;
22
+ title: string | null;
23
+ }
24
+
25
+ export interface JourneyStatusEntry {
26
+ journeyId: string;
27
+ identity: string | null;
28
+ journeyPath: string | null;
29
+ recentMemories: JourneyStatusMemoryRow[];
30
+ recentConversations: JourneyStatusConversationRow[];
31
+ }
32
+
33
+ /** Reads a file as utf8 text; injectable so tests never touch the real filesystem. */
34
+ export type ReadTextFile = (path: string) => string;
35
+
36
+ const defaultReadTextFile: ReadTextFile = (path) => readFileSync(path, "utf8");
37
+
38
+ /** Port of `get_sync_file`: the journey's own metadata `sync_file`, or null. */
39
+ export function getSyncFile(db: Database, journeyKey: string): string | null {
40
+ const metadata = getIdentityMetadata(db, JOURNEY_LAYER, journeyKey);
41
+ if (!metadata) return null;
42
+ try {
43
+ const parsed = JSON.parse(metadata) as unknown;
44
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
45
+ const value = (parsed as Record<string, unknown>).sync_file;
46
+ return typeof value === "string" ? value : null;
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Port of `get_project_path`: the journey's metadata `project_path`, expanded.
54
+ *
55
+ * Python expands `~` defensively on read, because values written before
56
+ * write-side normalization can still be stored in tilde form. A no-op for
57
+ * canonical paths, and load-bearing for the ones it is not.
58
+ */
59
+ export function getProjectPath(db: Database, journeyKey: string): string | null {
60
+ const metadata = getIdentityMetadata(db, JOURNEY_LAYER, journeyKey);
61
+ if (!metadata) return null;
62
+ try {
63
+ const parsed = JSON.parse(metadata) as unknown;
64
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
65
+ const value = (parsed as Record<string, unknown>).project_path;
66
+ return typeof value === "string" ? expandHome(value) : null;
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Port of `get_journey_path`: prefer the external sync file (expanduser'd) when
74
+ * configured and readable; on ANY read failure (missing, permission, or other
75
+ * I/O error — matching Python's broad `OSError` catch), fall back to the
76
+ * database's `journey_path` identity content.
77
+ */
78
+ export function getJourneyPathContent(
79
+ db: Database,
80
+ journeyKey: string,
81
+ readTextFile: ReadTextFile = defaultReadTextFile,
82
+ ): string | null {
83
+ const syncFile = getSyncFile(db, journeyKey);
84
+ if (syncFile) {
85
+ try {
86
+ return readTextFile(expandHome(syncFile));
87
+ } catch {
88
+ // Fall through to the database.
89
+ }
90
+ }
91
+ return getIdentityContent(db, JOURNEY_PATH_LAYER, journeyKey);
92
+ }
93
+
94
+ /** Port of `store.get_memories_by_journey(journey)[:10]`, LIMIT pushed down to SQL. */
95
+ export function getMemoriesByJourney(
96
+ db: Database,
97
+ journeyKey: string,
98
+ limit = 10,
99
+ ): JourneyStatusMemoryRow[] {
100
+ return db
101
+ .prepare(
102
+ "SELECT created_at, title FROM memories WHERE journey = ? ORDER BY created_at DESC LIMIT ?",
103
+ )
104
+ .all(journeyKey, limit)
105
+ .map((row) => ({
106
+ created_at: requireString(row, "created_at"),
107
+ title: requireString(row, "title"),
108
+ }));
109
+ }
110
+
111
+ /** Port of `store.get_recent_conversations_by_journey(journey, limit=5)`. */
112
+ export function getRecentConversationsByJourney(
113
+ db: Database,
114
+ journeyKey: string,
115
+ limit = 5,
116
+ ): JourneyStatusConversationRow[] {
117
+ return db
118
+ .prepare(
119
+ "SELECT started_at, title FROM conversations WHERE journey = ? ORDER BY started_at DESC LIMIT ?",
120
+ )
121
+ .all(journeyKey, limit)
122
+ .map((row) => ({
123
+ started_at: requireString(row, "started_at"),
124
+ title: optionalString(row, "title"),
125
+ }));
126
+ }
127
+
128
+ /** Port of `_get_journey_identities()` keys only: every journey key, ordered. */
129
+ export function allJourneyKeys(db: Database): string[] {
130
+ return db
131
+ .prepare("SELECT key FROM identity WHERE layer = 'journey' ORDER BY key")
132
+ .all()
133
+ .map((row) => requireString(row, "key"));
134
+ }
135
+
136
+ /** Port of `get_journey_status` for an explicit, pre-resolved list of journey keys. */
137
+ export function getJourneyStatusEntries(
138
+ db: Database,
139
+ journeyKeys: readonly string[],
140
+ readTextFile: ReadTextFile = defaultReadTextFile,
141
+ ): JourneyStatusEntry[] {
142
+ return journeyKeys.map((journeyId) => ({
143
+ journeyId,
144
+ identity: getIdentityContent(db, JOURNEY_LAYER, journeyId),
145
+ journeyPath: getJourneyPathContent(db, journeyId, readTextFile),
146
+ recentMemories: getMemoriesByJourney(db, journeyId, 10),
147
+ recentConversations: getRecentConversationsByJourney(db, journeyId, 5),
148
+ }));
149
+ }
@@ -0,0 +1,87 @@
1
+ // Journey sync-file metadata parity port (CV22.DS7.US2 slice 3c).
2
+ //
3
+ // A faithful port of `JourneyService.get_sync_file` / `set_sync_file` /
4
+ // `get_journey_path` (`src/memory/services/journey.py`). `sync_file` is a
5
+ // field inside the "journey" layer identity row's `metadata` JSON — the same
6
+ // metadata object `journeyWrite.ts`'s `journeyMetadata` composes for
7
+ // `project_path`/`icon`/`color`/`parent_journey` — read and written here with
8
+ // its own narrow read-modify-write (Python's `set_sync_file` reads the whole
9
+ // metadata object, sets one key, and writes it back; it does NOT go through
10
+ // the ordered `journeyMetadata` composer).
11
+
12
+ import { readFileSync } from "node:fs";
13
+ import type { Database, WritableDatabase } from "#db/database.ts";
14
+ import { getIdentityContent, getIdentityMetadata } from "#identity/identityRead.ts";
15
+ import { updateIdentityMetadata } from "#identity/identityStore.ts";
16
+ import { expandHome } from "#util/paths.ts";
17
+ import { JOURNEY_PATH_LAYER } from "./journeyStatus.ts";
18
+ import { JourneyNotFoundError } from "./journeyWrite.ts";
19
+
20
+ export const JOURNEY_LAYER = "journey";
21
+
22
+ /**
23
+ * Port of `get_sync_file`: the configured sync file path for a journey, or
24
+ * `null` when the journey has no identity row, no metadata, no `sync_file`
25
+ * key, or the metadata fails to parse (matching Python's
26
+ * `except (JSONDecodeError, TypeError): return None`).
27
+ */
28
+ export function getSyncFile(db: Database, journey: string): string | null {
29
+ const metadataJson = getIdentityMetadata(db, JOURNEY_LAYER, journey);
30
+ if (!metadataJson) return null;
31
+ try {
32
+ const meta = JSON.parse(metadataJson) as Record<string, unknown>;
33
+ return typeof meta.sync_file === "string" ? meta.sync_file : null;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Port of `set_sync_file`: read-modify-write the journey's metadata, setting
41
+ * `sync_file`, preserving every other existing key. Throws `JourneyNotFoundError`
42
+ * when the journey has no "journey"-layer identity row -- Python's own
43
+ * `cmd_sync_config` does not catch this ValueError either (an uncaught
44
+ * exception, exit 1); the front-door route maps it to a clear stderr message
45
+ * instead of fabricating a fake traceback, the same DS7.US1 `init` precedent.
46
+ */
47
+ export function setSyncFile(
48
+ db: WritableDatabase,
49
+ journey: string,
50
+ filePath: string,
51
+ nowIso: string,
52
+ ): void {
53
+ const existingMetadata = getIdentityMetadata(db, JOURNEY_LAYER, journey);
54
+ const exists =
55
+ db
56
+ .prepare("SELECT 1 FROM identity WHERE layer = ? AND key = ? LIMIT 1")
57
+ .get(JOURNEY_LAYER, journey) !== undefined;
58
+ if (!exists) {
59
+ throw new JourneyNotFoundError(journey);
60
+ }
61
+ let meta: Record<string, unknown>;
62
+ try {
63
+ meta = existingMetadata ? (JSON.parse(existingMetadata) as Record<string, unknown>) : {};
64
+ } catch {
65
+ meta = {};
66
+ }
67
+ meta.sync_file = filePath;
68
+ updateIdentityMetadata(db, JOURNEY_LAYER, journey, JSON.stringify(meta), nowIso);
69
+ }
70
+
71
+ /**
72
+ * Port of `get_journey_path`: if a sync file is configured, read its EXTERNAL
73
+ * file content, falling back to the database's `journey_path`-layer content
74
+ * when the file is absent/unreadable (matching Python's
75
+ * `except (FileNotFoundError, PermissionError, OSError): pass`).
76
+ */
77
+ export function getJourneyPath(db: Database, journey: string): string | null {
78
+ const syncFile = getSyncFile(db, journey);
79
+ if (syncFile) {
80
+ try {
81
+ return readFileSync(expandHome(syncFile), "utf8");
82
+ } catch {
83
+ // Fall back to the database.
84
+ }
85
+ }
86
+ return getIdentityContent(db, JOURNEY_PATH_LAYER, journey);
87
+ }