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,339 @@
1
+ // The state-aware welcome card (CV22.DS7.TS3 plateau 4). Port of
2
+ // `compose_welcome` from `src/memory/cli/welcome.py`.
3
+ //
4
+ // Unlike the status line, the card MAY reach the network: on the stable
5
+ // channel it performs a bounded remote update check so a newly published
6
+ // release becomes visible without manual cache repair. That check is
7
+ // TTL-guarded, refresh-guarded, and switched off entirely by
8
+ // `MIRROR_WELCOME_REMOTE_UPDATE_CHECK=off|0|false|no`.
9
+ //
10
+ // Date rendering is done by hand from `_MONTH_ABBR`, never through
11
+ // `toLocaleDateString`: the oracle formats the month itself, so a locale-aware
12
+ // TypeScript formatter would render "since ago. 2025" on a machine whose
13
+ // locale is not English and call it parity.
14
+
15
+ import { existsSync, readFileSync } from "node:fs";
16
+ import { basename, join } from "node:path";
17
+ import { type Database, openDatabaseReadOnly } from "#db/database.ts";
18
+ import { dbNameForEnv } from "#frontDoor/dbPath.ts";
19
+ import { listJourneysForListCommand } from "#identity/journeyListing.ts";
20
+ import {
21
+ checkUpdateAvailability,
22
+ type GitStatus,
23
+ inspectGit,
24
+ inspectGitUpdatePlan,
25
+ type MarkerValue,
26
+ runGit,
27
+ } from "#runtime/git.ts";
28
+ import { compareSemver } from "#util/semver.ts";
29
+ import { SEPARATOR } from "./statusLine.ts";
30
+ import {
31
+ cacheIsStale,
32
+ cacheShouldRefresh,
33
+ readUpdateCache,
34
+ type UpdateAwareness,
35
+ writeUpdateCache,
36
+ } from "./updateCache.ts";
37
+
38
+ export const INVITATION = "→ Where shall we begin?";
39
+
40
+ /** Port of `_MONTH_ABBR`. Hand-formatted, deliberately: see the module header. */
41
+ const MONTH_ABBR = [
42
+ "Jan",
43
+ "Feb",
44
+ "Mar",
45
+ "Apr",
46
+ "May",
47
+ "Jun",
48
+ "Jul",
49
+ "Aug",
50
+ "Sep",
51
+ "Oct",
52
+ "Nov",
53
+ "Dec",
54
+ ] as const;
55
+
56
+ export interface WelcomeStats {
57
+ journeys: number;
58
+ personas: number;
59
+ memories: number;
60
+ conversations: number;
61
+ since: string | null;
62
+ }
63
+
64
+ /** Port of `_fmt_count`: a thousands separator only from 1000 up. */
65
+ function fmtCount(value: number): string {
66
+ return value >= 1000 ? value.toLocaleString("en-US") : String(value);
67
+ }
68
+
69
+ /** Port of `_since_label`. An unparseable or absent timestamp reads "today". */
70
+ export function sinceLabel(firstStarted: string | null): string {
71
+ if (!firstStarted) return "since today";
72
+ const parsed = Date.parse(firstStarted.replace("Z", "+00:00"));
73
+ if (Number.isNaN(parsed)) return "since today";
74
+ // The oracle reads the naive/offset timestamp's OWN fields, so the month and
75
+ // year come from the stored local wall clock, not from a UTC conversion.
76
+ const match = firstStarted.match(/^(\d{4})-(\d{2})/);
77
+ if (match) return `since ${MONTH_ABBR[Number(match[2]) - 1]} ${Number(match[1])}`;
78
+ const date = new Date(parsed);
79
+ return `since ${MONTH_ABBR[date.getUTCMonth()]} ${date.getUTCFullYear()}`;
80
+ }
81
+
82
+ /** Port of `_format_stats`. */
83
+ export function formatStats(stats: WelcomeStats): string {
84
+ return [
85
+ `${fmtCount(stats.journeys)} journeys`,
86
+ `${fmtCount(stats.personas)} personas`,
87
+ `${fmtCount(stats.memories)} memories`,
88
+ `${fmtCount(stats.conversations)} conversations`,
89
+ sinceLabel(stats.since),
90
+ ].join(SEPARATOR);
91
+ }
92
+
93
+ function scalar(db: Database, sql: string, ...params: string[]): number {
94
+ const row = db.prepare(sql).get(...params);
95
+ const value = row === undefined ? null : Object.values(row)[0];
96
+ if (value === null || value === undefined) return 0;
97
+ return typeof value === "number" ? value : Number(value);
98
+ }
99
+
100
+ /** Port of `_stats_line`: five numbers, one read-only connection. */
101
+ export function readWelcomeStats(dbPath: string | null): WelcomeStats {
102
+ if (dbPath === null || !existsSync(dbPath)) {
103
+ return { journeys: 0, personas: 0, memories: 0, conversations: 0, since: null };
104
+ }
105
+ const db = openDatabaseReadOnly(dbPath);
106
+ try {
107
+ const activeJourneys = listJourneysForListCommand(db).filter(
108
+ (journey) => journey.status === "active",
109
+ ).length;
110
+ const startedRow = db.prepare("SELECT MIN(started_at) AS value FROM conversations").get();
111
+ const started = startedRow?.value;
112
+ return {
113
+ journeys: activeJourneys,
114
+ personas: scalar(db, "SELECT COUNT(*) AS value FROM identity WHERE layer = ?", "persona"),
115
+ memories: scalar(db, "SELECT COUNT(*) AS value FROM memories"),
116
+ conversations: scalar(db, "SELECT COUNT(*) AS value FROM conversations"),
117
+ since: typeof started === "string" && started ? started : null,
118
+ };
119
+ } finally {
120
+ db.close();
121
+ }
122
+ }
123
+
124
+ /** Port of `_render`. */
125
+ export function renderWelcome(
126
+ user: string,
127
+ stats: string,
128
+ version: string,
129
+ update: string | null,
130
+ ): string {
131
+ const lines = [`◇ Mirror · ${user}`, `Version ${version}`, stats];
132
+ if (update) lines.push(update);
133
+ lines.push("", INVITATION);
134
+ return lines.join("\n");
135
+ }
136
+
137
+ /** Port of `_remote_update_check_disabled`. */
138
+ export function remoteUpdateCheckDisabled(env: NodeJS.ProcessEnv = process.env): boolean {
139
+ const value = (env.MIRROR_WELCOME_REMOTE_UPDATE_CHECK ?? "").trim().toLowerCase();
140
+ return value === "off" || value === "0" || value === "false" || value === "no";
141
+ }
142
+
143
+ /** Port of `_welcome_disabled`. */
144
+ export function welcomeDisabled(env: NodeJS.ProcessEnv = process.env): boolean {
145
+ const value = (env.MIRROR_WELCOME ?? "").trim().toLowerCase();
146
+ return value === "off" || value === "0" || value === "false" || value === "no";
147
+ }
148
+
149
+ /** Port of `_commits_match`: either id may be the abbreviated form. */
150
+ function commitsMatch(candidate: string, target: string): boolean {
151
+ return candidate === target || candidate.startsWith(target) || target.startsWith(candidate);
152
+ }
153
+
154
+ // The oracle's `_semver_key` was `_parse_semver` under a second name; both
155
+ // are `#util/semver.ts` now, and an unparseable tag still sorts below every
156
+ // real one.
157
+
158
+ /**
159
+ * Port of `_remote_tag_for_commit`: which published tag names the commit the
160
+ * remote is at. `^{}` refs are the dereferenced peel of an annotated tag and
161
+ * are skipped, so a tag is not counted twice.
162
+ */
163
+ export function remoteTagForCommit(
164
+ upstream: string,
165
+ remoteCommit: string,
166
+ repository: string | null,
167
+ ): string | null {
168
+ if (!upstream.includes("/")) return null;
169
+ const remote = upstream.split("/", 1)[0] as string;
170
+ if (repository === null) return null;
171
+ const result = runGit(["ls-remote", "--tags", remote, "refs/tags/v*"], repository);
172
+ if (result.code !== 0 || !result.stdout) return null;
173
+
174
+ const matches: string[] = [];
175
+ for (const line of result.stdout.split("\n")) {
176
+ const parts = line.split(/\s+/).filter((part) => part.length > 0);
177
+ if (parts.length < 2) continue;
178
+ const [commit, ref] = parts as [string, string];
179
+ if (!commitsMatch(commit, remoteCommit) || ref.endsWith("^{}")) continue;
180
+ const tag = ref.split("/").pop() as string;
181
+ if (tag.startsWith("v")) matches.push(tag);
182
+ }
183
+ if (matches.length === 0) return null;
184
+ return matches.sort((a, b) => compareSemver(b, a))[0] as string;
185
+ }
186
+
187
+ /**
188
+ * Port of `_local_release_title`: the headline of a note already on disk,
189
+ * under `<treeRoot>/docs/releases`. The oracle read `<cwd>/docs/releases`;
190
+ * since CV22.DS10.US3 the caller passes the tree that holds the running front
191
+ * door, so the title is the installed package's own and not whatever
192
+ * directory the person is in.
193
+ */
194
+ export function localReleaseTitle(version: string | null, treeRoot: string): string | null {
195
+ if (!version) return null;
196
+ let text: string;
197
+ try {
198
+ text = readFileSync(join(treeRoot, "docs", "releases", `${version}.md`), "utf8");
199
+ } catch {
200
+ return null;
201
+ }
202
+ const prefix = `# ${version} — `;
203
+ for (const line of text.split("\n")) {
204
+ if (line.startsWith(prefix)) return line.slice(prefix.length).trim();
205
+ }
206
+ return null;
207
+ }
208
+
209
+ export interface WelcomeOptions {
210
+ /** The resolved mirror home. Null renders "", as in the oracle. */
211
+ mirrorHome: string | null;
212
+ /** Where git is inspected for the update check (a clone's working tree). */
213
+ cwd?: string;
214
+ /** The tree holding the running program, where `docs/releases` is read. Defaults to `cwd`. */
215
+ treeRoot?: string;
216
+ env?: NodeJS.ProcessEnv;
217
+ version: string;
218
+ updateChannel: MarkerValue;
219
+ /** Injected only so the written cache is byte-comparable; production omits it. */
220
+ now?: () => string;
221
+ }
222
+
223
+ /** Port of `_iso_now`. */
224
+ function isoNow(): string {
225
+ return new Date().toISOString().replace(/\.(\d{3})Z$/, ".$1000+00:00");
226
+ }
227
+
228
+ /** Port of `_refresh_update_cache`. Any failure leaves the cache untouched. */
229
+ function refreshUpdateCache(
230
+ homePath: string,
231
+ channel: MarkerValue,
232
+ cwd: string,
233
+ treeRoot: string,
234
+ version: string,
235
+ now: () => string,
236
+ ): UpdateAwareness | null {
237
+ let report: ReturnType<typeof checkUpdateAvailability>;
238
+ try {
239
+ report = checkUpdateAvailability(cwd, channel.value, version);
240
+ } catch {
241
+ return null;
242
+ }
243
+ let tag: string | null = null;
244
+ let title: string | null = null;
245
+ if (report.status === "update_available" && report.remote_commit && report.upstream) {
246
+ tag = remoteTagForCommit(report.upstream, report.remote_commit, inspectGit(cwd).repository);
247
+ title = localReleaseTitle(tag, treeRoot);
248
+ }
249
+ const awareness: UpdateAwareness = {
250
+ availability: report.status,
251
+ checked_at: now(),
252
+ channel: channel.value,
253
+ current_commit: report.local_commit,
254
+ remote_commit: report.remote_commit,
255
+ version: tag,
256
+ title,
257
+ note: report.note,
258
+ };
259
+ if (report.status === "up_to_date" || report.status === "update_available") {
260
+ writeUpdateCache(homePath, awareness);
261
+ }
262
+ return awareness;
263
+ }
264
+
265
+ /** Port of `_update_awareness`: cache first, remote only when it must. */
266
+ export function updateAwareness(
267
+ homePath: string,
268
+ channel: MarkerValue,
269
+ cwd: string,
270
+ version: string,
271
+ env: NodeJS.ProcessEnv,
272
+ now: () => string,
273
+ treeRoot: string = cwd,
274
+ ): UpdateAwareness | null {
275
+ const cached = readUpdateCache(homePath);
276
+ if (cached && !cacheIsStale(cached) && !cacheShouldRefresh(cached, channel)) return cached;
277
+ if (remoteUpdateCheckDisabled(env) || channel.value !== "stable") return cached;
278
+ return refreshUpdateCache(homePath, channel, cwd, treeRoot, version, now) ?? cached;
279
+ }
280
+
281
+ /** Port of `_update_line`. */
282
+ export function updateLine(
283
+ channel: MarkerValue,
284
+ awareness: UpdateAwareness | null,
285
+ git: GitStatus,
286
+ ): string | null {
287
+ if (awareness && awareness.availability === "update_available") {
288
+ let label = "✨ New Version Available";
289
+ if (awareness.version && awareness.title) {
290
+ label = `${label}: ${awareness.version} — ${awareness.title}`;
291
+ } else if (awareness.version) {
292
+ label = `${label}: ${awareness.version}`;
293
+ } else {
294
+ label = `${label} on ${awareness.channel}`;
295
+ }
296
+ return `${label}\nAsk: "what's new in this update?" or "update my Mirror"`;
297
+ }
298
+
299
+ if (git.repository === null) return null;
300
+ const plan = inspectGitUpdatePlan(git, channel);
301
+ if (plan.ready && plan.action === "pull" && plan.behind) {
302
+ const plural = plan.behind !== 1 ? "s" : "";
303
+ const upstream = plan.upstream || "upstream";
304
+ return (
305
+ `New Version Available: ${plan.behind} commit${plural} behind ${upstream} · ` +
306
+ "run runtime update\n" +
307
+ 'Ask Mirror: "What\'s new in the latest Mirror Mind release?"'
308
+ );
309
+ }
310
+ return null;
311
+ }
312
+
313
+ /**
314
+ * Port of `compose_welcome`. The ONLY path to empty output is an unresolvable
315
+ * mirror home: a fresh database with nothing in it still renders the full card
316
+ * with zeroes.
317
+ */
318
+ export function composeWelcome(options: WelcomeOptions): string {
319
+ const homePath = options.mirrorHome;
320
+ if (homePath === null) return "";
321
+ const cwd = options.cwd ?? process.cwd();
322
+ const env = options.env ?? process.env;
323
+ const now = options.now ?? isoNow;
324
+
325
+ const dbPath = join(homePath, dbNameForEnv(env.MEMORY_ENV || "production"));
326
+ const stats = formatStats(readWelcomeStats(existsSync(dbPath) ? dbPath : null));
327
+ const versionLine = `${options.version} · channel ${options.updateChannel.value}`;
328
+ const awareness = updateAwareness(
329
+ homePath,
330
+ options.updateChannel,
331
+ cwd,
332
+ options.version,
333
+ env,
334
+ now,
335
+ options.treeRoot ?? cwd,
336
+ );
337
+ const update = updateLine(options.updateChannel, awareness, inspectGit(cwd));
338
+ return renderWelcome(basename(homePath), stats, versionLine, update);
339
+ }
@@ -0,0 +1,101 @@
1
+ // The per-turn Mirror status line (CV22.DS7.TS3 plateau 4). Port of
2
+ // `compose_status_line` from `src/memory/cli/welcome.py`.
3
+ //
4
+ // This is the most-executed surface in the product: it renders after every
5
+ // turn, and CR059 made it a front-door call, so until it answers from
6
+ // TypeScript every turn pays a Node start plus a Python start.
7
+ //
8
+ // THE HOT-PATH CONTRACT, enforced by a test that puts a recording `git` shim
9
+ // first on PATH: this module spawns NOTHING. No git, no network, no provider.
10
+ // It reads the update CACHE, never the remote -- refreshing the cache is the
11
+ // full card's job, and the one time that budget was allowed to leak into the
12
+ // per-turn path it cost a 120s network timeout on a status bar.
13
+
14
+ import { existsSync } from "node:fs";
15
+ import { basename, join } from "node:path";
16
+ import { openDatabaseReadOnly } from "#db/database.ts";
17
+ import { dbNameForEnv } from "#frontDoor/dbPath.ts";
18
+ import { getIdentityContent } from "#identity/identityRead.ts";
19
+ import { GLOBAL_STICKY_DEFAULTS_SESSION_ID, getRuntimeSession } from "#mirror/runtimeSession.ts";
20
+ import { getActiveOperatingMode, modeLabel } from "#mode/operatingMode.ts";
21
+ import { readUpdateCache } from "./updateCache.ts";
22
+
23
+ export const SEPARATOR = " · ";
24
+
25
+ export interface StatusLineOptions {
26
+ /** The resolved mirror home. Null renders nothing, as in the oracle. */
27
+ mirrorHome: string | null;
28
+ sessionId?: string | null;
29
+ env?: NodeJS.ProcessEnv;
30
+ }
31
+
32
+ /** Port of `_runtime_environment_segment`: production is unmarked. */
33
+ export function runtimeEnvironmentSegment(env: NodeJS.ProcessEnv = process.env): string | null {
34
+ const memoryEnv = env.MEMORY_ENV || "production";
35
+ if (memoryEnv === "development") return "🛠 development";
36
+ if (memoryEnv === "test") return "🧪 test";
37
+ return null;
38
+ }
39
+
40
+ /** Port of `_journey_display_name`: the first line, stripped of its `# `. */
41
+ export function journeyDisplayName(content: string | null): string | null {
42
+ if (content === null) return null;
43
+ let firstLine = (content.split("\n", 1)[0] ?? "").trim();
44
+ if (firstLine.startsWith("# ")) firstLine = firstLine.slice(2).trim();
45
+ return firstLine || null;
46
+ }
47
+
48
+ /**
49
+ * Port of `_mode_status_segment`. One read-only connection, opened only when
50
+ * the database actually exists.
51
+ */
52
+ function modeStatusSegment(
53
+ homePath: string,
54
+ sessionId: string | null,
55
+ env: NodeJS.ProcessEnv,
56
+ ): string | null {
57
+ const dbPath = join(homePath, dbNameForEnv(env.MEMORY_ENV || "production"));
58
+ if (!existsSync(dbPath)) return null;
59
+
60
+ let db: ReturnType<typeof openDatabaseReadOnly>;
61
+ try {
62
+ db = openDatabaseReadOnly(dbPath);
63
+ } catch {
64
+ return null;
65
+ }
66
+ try {
67
+ let state = getActiveOperatingMode(db, sessionId ?? null);
68
+ if (state === null) {
69
+ // No active mode: fall back to Mirror Mode carrying the sticky journey,
70
+ // so the line still says WHERE the user is working.
71
+ const sticky = getRuntimeSession(db, GLOBAL_STICKY_DEFAULTS_SESSION_ID);
72
+ state = { mode: "Mirror Mode", journey: sticky?.journey ?? null };
73
+ }
74
+ if (!state.journey) return modeLabel(state);
75
+ const display = journeyDisplayName(getIdentityContent(db, "journey", state.journey));
76
+ return `${display ?? state.journey} on ${modeLabel(state)}`;
77
+ } finally {
78
+ db.close();
79
+ }
80
+ }
81
+
82
+ /** Port of `compose_status_line`. Returns "" when no home can be resolved. */
83
+ export function composeStatusLine(options: StatusLineOptions): string {
84
+ const homePath = options.mirrorHome;
85
+ if (homePath === null) return "";
86
+ const env = options.env ?? process.env;
87
+
88
+ const parts = [`◇ ${basename(homePath)}`];
89
+ const environment = runtimeEnvironmentSegment(env);
90
+ if (environment) parts.push(environment);
91
+ const mode = modeStatusSegment(homePath, options.sessionId ?? null, env);
92
+ if (mode) parts.push(mode);
93
+
94
+ const awareness = readUpdateCache(homePath);
95
+ if (awareness && awareness.availability === "update_available") {
96
+ parts.push(`⬆${awareness.version ? ` ${awareness.version}` : ""}`);
97
+ } else {
98
+ parts.push("✓");
99
+ }
100
+ return parts.join(SEPARATOR);
101
+ }
@@ -0,0 +1,129 @@
1
+ // The welcome's update-awareness cache (CV22.DS7.TS3 plateau 4). Port of the
2
+ // cache half of `src/memory/cli/welcome.py`.
3
+ //
4
+ // `<mirror home>/runtime/update-check.json` is READ by the per-turn status line
5
+ // and WRITTEN by the full card's refresh. During the transition both cores read
6
+ // and write it, so byte compatibility is a hard requirement rather than a
7
+ // nicety: two cores that serialize the same state differently would rewrite the
8
+ // file on every alternating invocation and thrash the 6h TTL, turning a bounded
9
+ // remote check into one on every turn.
10
+
11
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
12
+ import { dirname, join } from "node:path";
13
+ import type { MarkerValue } from "#runtime/git.ts";
14
+ import { pythonJsonDumpsIndented } from "#util/pyGenerators.ts";
15
+
16
+ export const UPDATE_CHECK_CACHE = "runtime/update-check.json";
17
+ export const UPDATE_CHECK_TTL_MS = 6 * 60 * 60 * 1000;
18
+
19
+ export interface UpdateAwareness {
20
+ availability: string;
21
+ checked_at: string;
22
+ channel: string;
23
+ current_commit: string | null;
24
+ remote_commit: string | null;
25
+ version: string | null;
26
+ title: string | null;
27
+ note: string | null;
28
+ }
29
+
30
+ export function cachePath(homePath: string): string {
31
+ return join(homePath, UPDATE_CHECK_CACHE);
32
+ }
33
+
34
+ /** Port of `_optional_str`: a non-empty string, or null. */
35
+ function optionalStr(value: unknown): string | null {
36
+ return typeof value === "string" && value.length > 0 ? value : null;
37
+ }
38
+
39
+ /**
40
+ * Port of `_read_update_cache`. Every failure mode -- unreadable, malformed,
41
+ * not an object, or missing one of the three required fields -- yields null,
42
+ * because a cache that cannot be trusted is the same as no cache.
43
+ */
44
+ export function readUpdateCache(homePath: string): UpdateAwareness | null {
45
+ let raw: unknown;
46
+ try {
47
+ raw = JSON.parse(readFileSync(cachePath(homePath), "utf8"));
48
+ } catch {
49
+ return null;
50
+ }
51
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
52
+ const record = raw as Record<string, unknown>;
53
+
54
+ const availability = optionalStr(record.availability);
55
+ const checkedAt = optionalStr(record.checked_at);
56
+ const channel = optionalStr(record.channel);
57
+ if (availability === null || checkedAt === null || channel === null) return null;
58
+
59
+ return {
60
+ availability,
61
+ checked_at: checkedAt,
62
+ channel,
63
+ current_commit: optionalStr(record.current_commit),
64
+ remote_commit: optionalStr(record.remote_commit),
65
+ version: optionalStr(record.version),
66
+ title: optionalStr(record.title),
67
+ note: optionalStr(record.note),
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Port of `_write_update_cache`. Fail-quietly, like the oracle: a cache that
73
+ * cannot be written must never break the welcome it decorates.
74
+ */
75
+ export function writeUpdateCache(homePath: string, awareness: UpdateAwareness): void {
76
+ const path = cachePath(homePath);
77
+ const payload = {
78
+ checked_at: awareness.checked_at,
79
+ channel: awareness.channel,
80
+ availability: awareness.availability,
81
+ current_commit: awareness.current_commit,
82
+ remote_commit: awareness.remote_commit,
83
+ version: awareness.version,
84
+ title: awareness.title,
85
+ note: awareness.note,
86
+ };
87
+ try {
88
+ if (!existsSync(dirname(path))) mkdirSync(dirname(path), { recursive: true });
89
+ writeFileSync(path, `${pythonJsonDumpsIndented(payload)}\n`, "utf8");
90
+ } catch {
91
+ // Fail-quietly contract.
92
+ }
93
+ }
94
+
95
+ /** Port of `_parse_iso`, narrowed to the timestamps this cache carries. */
96
+ export function parseIso(value: string | null): Date | null {
97
+ if (!value) return null;
98
+ const parsed = Date.parse(value.replace("Z", "+00:00"));
99
+ return Number.isNaN(parsed) ? null : new Date(parsed);
100
+ }
101
+
102
+ /**
103
+ * Port of `_cache_is_stale`. A timestamp that cannot be parsed is stale, not
104
+ * fresh -- the safe direction for a cache that gates a network call.
105
+ *
106
+ * A NAIVE timestamp (no offset) is read as UTC, matching the oracle's explicit
107
+ * `replace(tzinfo=timezone.utc)`. JavaScript would otherwise read it as local
108
+ * time and shift the TTL by the machine's offset.
109
+ */
110
+ export function cacheIsStale(awareness: UpdateAwareness, now: Date = new Date()): boolean {
111
+ const hasOffset = /(?:Z|[+-]\d{2}:\d{2})$/.test(awareness.checked_at);
112
+ const checkedAt = parseIso(hasOffset ? awareness.checked_at : `${awareness.checked_at}Z`);
113
+ if (checkedAt === null) return true;
114
+ return now.getTime() - checkedAt.getTime() > UPDATE_CHECK_TTL_MS;
115
+ }
116
+
117
+ /**
118
+ * Port of `_cache_should_refresh`: stable-channel awareness is refreshed even
119
+ * inside the TTL. A cached notice may point at an intermediate release, and a
120
+ * cached up-to-date answer goes wrong the moment stable advances -- opening
121
+ * Mirror should show the newly published version without manually fetching
122
+ * refs or deleting this file.
123
+ */
124
+ export function cacheShouldRefresh(awareness: UpdateAwareness, channel: MarkerValue): boolean {
125
+ return (
126
+ (awareness.availability === "up_to_date" || awareness.availability === "update_available") &&
127
+ channel.value === "stable"
128
+ );
129
+ }