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,686 @@
1
+ // `runtime diagnose` (CV22.DS7.TS3 plateau 3b). Port of the drift-diagnosis
2
+ // half of `src/memory/cli/runtime.py`.
3
+ //
4
+ // Diagnose is the read-only "what is wrong with this install?" pass: filesystem
5
+ // posture, recent front-door errors, FTS index health, and everything the
6
+ // status readers already found, flattened into ordered findings. It must never
7
+ // repair, migrate, or write -- a diagnosis that changes the thing it is
8
+ // diagnosing reports a state it created.
9
+ //
10
+ // ONE RECORDED DIVERGENCE (D3), the same class the Navigator settled for the
11
+ // manifest notes in plateau 3a: `fts_corrupt` embeds the DRIVER's error text,
12
+ // and the two drivers describe the same corruption differently --
13
+ //
14
+ // Python: database disk image is malformed
15
+ // Node: fts5: corruption found reading blob 10 from table "memories_fts"
16
+ //
17
+ // -- because they link different SQLite builds. Mirror's own words around it
18
+ // are byte-identical, and the golden pins those and records both tails. TS is
19
+ // not normalized to Python's string: the message is driver-dependent rather
20
+ // than a fixed mapping, so translating it would invent an answer, and Node's
21
+ // is the more actionable of the two.
22
+ //
23
+ // NOT ported: a live model catalog call. `probeModelPins` runs against the DS5
24
+ // replay substrate or reports inconclusive, exactly as the oracle reports
25
+ // inconclusive without a key; the live fetch belongs to DS8.
26
+
27
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
28
+ import { dirname, join } from "node:path";
29
+ import { openDatabaseReadOnly } from "#db/database.ts";
30
+ import { identityKeyBreaksGrammar, SLUG_KEYED_LAYERS } from "#identity/identityKey.ts";
31
+ import { pyRepr, sortByCodePoint } from "#util/pythonText.ts";
32
+ import { configFilePath } from "./config.ts";
33
+ import type { GitWorktreeEntry } from "./git.ts";
34
+ import type { RuntimeStatusReport } from "./status.ts";
35
+
36
+ export interface DriftFinding {
37
+ code: string;
38
+ severity: string;
39
+ subject: string;
40
+ detail: string;
41
+ recommendation: string;
42
+ repair_route: string;
43
+ }
44
+
45
+ /**
46
+ * CV9.E2.S6 — the homes root (`~/.mirror-minds`) holds user-home directories
47
+ * and nothing else. A known runtime directory sitting directly in the root is
48
+ * a legacy artifact of the pre-containment resolution rule.
49
+ */
50
+ const ROOT_RUNTIME_DIR_NAMES = new Set(["backups"]);
51
+
52
+ const FRONT_DOOR_WINDOW_MS = 24 * 60 * 60 * 1000;
53
+ const FRONT_DOOR_TAIL_LINES = 1000;
54
+ const FTS_PROBE_TERM = "mirror_fts_integrity_probe";
55
+
56
+ /** Port of `_git_dirty_finding`. */
57
+ export function gitDirtyFinding(entry: GitWorktreeEntry): DriftFinding {
58
+ const isSessionHtml = entry.path.startsWith("pi-session-") && entry.path.endsWith(".html");
59
+ let recommendation: string;
60
+ let repairRoute: string;
61
+ if (entry.status === "??" && isSessionHtml) {
62
+ recommendation = "archive or ignore generated session HTML before update planning";
63
+ repairRoute = "manual review";
64
+ } else if (entry.status === "??") {
65
+ recommendation = "review untracked file before update planning";
66
+ repairRoute = "manual review";
67
+ } else {
68
+ recommendation = "commit, stash, or discard intentional work before update planning";
69
+ repairRoute = "commit or manual review";
70
+ }
71
+ return {
72
+ code: "git_dirty",
73
+ severity: "attention",
74
+ subject: "repository",
75
+ detail: `${entry.status} ${entry.path}`,
76
+ recommendation,
77
+ repair_route: repairRoute,
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Port of `scan_homes_root_state`: files and known runtime directories sitting
83
+ * directly in the homes root. User homes and hidden entries (`.DS_Store`) are
84
+ * not offenders.
85
+ */
86
+ export function scanHomesRootState(homesRoot: string): string[] {
87
+ let names: string[];
88
+ try {
89
+ names = sortByCodePoint(readdirSync(homesRoot));
90
+ } catch {
91
+ return [];
92
+ }
93
+ const offenders: string[] = [];
94
+ for (const name of names) {
95
+ if (name.startsWith(".")) continue;
96
+ const full = join(homesRoot, name);
97
+ let isFile: boolean;
98
+ try {
99
+ isFile = statSync(full).isFile();
100
+ } catch {
101
+ continue;
102
+ }
103
+ if (isFile || ROOT_RUNTIME_DIR_NAMES.has(name)) offenders.push(full);
104
+ }
105
+ return offenders;
106
+ }
107
+
108
+ /** Port of `root_state_findings`. */
109
+ export function rootStateFindings(homesRoot: string): DriftFinding[] {
110
+ return scanHomesRootState(homesRoot).map((path) => ({
111
+ code: "legacy_root_runtime_state",
112
+ severity: "attention",
113
+ subject: `homes root ${homesRoot}`,
114
+ detail: path,
115
+ recommendation:
116
+ "relocate into the owning mirror home " +
117
+ "(see REFERENCE.md \u2014 Relocating legacy root runtime state)",
118
+ repair_route: "manual relocation",
119
+ }));
120
+ }
121
+
122
+ /**
123
+ * Port of `_loose_permission_findings`. The mirror home holds a person's
124
+ * identity, memories, and conversations; the expected posture is owner-only.
125
+ * Creation points enforce it -- this only reports drift on installs that
126
+ * predate the rule. POSIX only, as in the oracle: Windows ACLs are out of scope.
127
+ *
128
+ * Since CV22.DS10.US3 (D3) the user's config file is a target too: it is where
129
+ * `OPENROUTER_API_KEY` lives. `init` creates it 0600; a hand-made one may not be.
130
+ */
131
+ export function loosePermissionFindings(
132
+ report: RuntimeStatusReport,
133
+ configFile: string | null = configFilePath(),
134
+ ): DriftFinding[] {
135
+ if (process.platform === "win32") return [];
136
+ const targets: [string, string][] = [];
137
+ if (report.mirror_home !== null && report.mirror_home_error === null) {
138
+ targets.push(["mirror home", report.mirror_home]);
139
+ }
140
+ if (report.db_path !== null && report.db_exists) targets.push(["database", report.db_path]);
141
+ if (configFile !== null && existsSync(configFile)) targets.push(["config file", configFile]);
142
+
143
+ const findings: DriftFinding[] = [];
144
+ for (const [area, path] of targets) {
145
+ let stats: ReturnType<typeof statSync>;
146
+ try {
147
+ stats = statSync(path);
148
+ } catch {
149
+ continue;
150
+ }
151
+ const mode = stats.mode & 0o777;
152
+ if ((mode & 0o077) === 0) continue;
153
+ const wanted = stats.isDirectory() ? "700" : "600";
154
+ findings.push({
155
+ code: "loose_permissions",
156
+ severity: "attention",
157
+ subject: area,
158
+ detail: `${path} is group/other-accessible (mode ${mode.toString(8)})`,
159
+ recommendation:
160
+ "mirror data should be owner-only; restrict directories to 700 and files to 600",
161
+ repair_route: `chmod ${wanted} ${path}`,
162
+ });
163
+ }
164
+ return findings;
165
+ }
166
+
167
+ /**
168
+ * Python `datetime.fromisoformat(value.replace("Z", "+00:00"))`, narrowed to
169
+ * what the front door actually writes (`Date.toISOString()`).
170
+ *
171
+ * Returns null for anything unparseable AND for a timestamp with no offset.
172
+ * The oracle parses a naive timestamp successfully and then raises TypeError
173
+ * comparing it to an aware cutoff -- an uncaught crash in a read-only
174
+ * diagnostic. Unreachable with the current writer, which always emits `Z`, so
175
+ * this is not a behavioral divergence on any log the front door produces; it
176
+ * is a crash not worth porting. Captured for Debt Review.
177
+ */
178
+ function parseLogTimestamp(value: string): number | null {
179
+ if (!/(?:Z|[+-]\d{2}:\d{2})$/.test(value)) return null;
180
+ const parsed = Date.parse(value);
181
+ return Number.isNaN(parsed) ? null : parsed;
182
+ }
183
+
184
+ /**
185
+ * Port of `_front_door_error_findings` (CR026). The front door appends
186
+ * redacted, metadata-only lines; a non-green summary when ERROR lines appear
187
+ * in the last 24h keeps backup, guard, and routing failures from being
188
+ * invisible.
189
+ */
190
+ export function frontDoorErrorFindings(
191
+ report: RuntimeStatusReport,
192
+ now: Date = new Date(),
193
+ ): DriftFinding[] {
194
+ if (report.db_path === null) return [];
195
+ const logPath = join(dirname(report.db_path), "front-door.log");
196
+ if (!existsSync(logPath)) return [];
197
+ const cutoff = now.getTime() - FRONT_DOOR_WINDOW_MS;
198
+
199
+ let lines: string[];
200
+ try {
201
+ // `errors="replace"` in the oracle; Node's utf8 decode substitutes U+FFFD
202
+ // for the same malformed bytes, so a corrupt line is skipped, not fatal.
203
+ lines = readFileSync(logPath, "utf8").split("\n");
204
+ } catch {
205
+ return [];
206
+ }
207
+ // Python's `splitlines()` drops a trailing empty segment; `split` does not.
208
+ if (lines.at(-1) === "") lines.pop();
209
+
210
+ let recentErrors = 0;
211
+ for (const line of lines.slice(-FRONT_DOOR_TAIL_LINES)) {
212
+ const parts = line.split("\t");
213
+ if (parts.length < 2 || parts[1] !== "ERROR") continue;
214
+ const stamp = parseLogTimestamp(parts[0] as string);
215
+ if (stamp === null) continue;
216
+ if (stamp >= cutoff) recentErrors += 1;
217
+ }
218
+ if (recentErrors === 0) return [];
219
+ return [
220
+ {
221
+ code: "front_door_errors",
222
+ severity: "attention",
223
+ subject: "front door",
224
+ detail: `${recentErrors} front-door error(s) in the last 24h (${logPath})`,
225
+ recommendation:
226
+ "inspect the front-door log; rule out routing, schema-guard, and backup failures",
227
+ repair_route: "review front-door.log",
228
+ },
229
+ ];
230
+ }
231
+
232
+ /**
233
+ * CR104: one finding per journey slug or persona id outside the grammar
234
+ * (`#identity/identityKey.ts`). A new key cannot break it any more, so each one
235
+ * found here predates the rule. It keeps working, and nothing rewrites it: renaming
236
+ * a key that other tables copy is not a repair to make silently. What quoting
237
+ * cannot cover is named instead. Mirror quotes the key in every command it prints,
238
+ * but an agent may put it into a command of its own.
239
+ *
240
+ * The key is printed escaped, so a newline or a terminal escape in it cannot forge
241
+ * the report. A database that cannot be read, or has no identity table, reports
242
+ * nothing; other findings say why it cannot be read.
243
+ */
244
+ export function identityKeyFindings(
245
+ report: Pick<RuntimeStatusReport, "db_path" | "db_exists">,
246
+ ): DriftFinding[] {
247
+ if (report.db_path === null || !report.db_exists) return [];
248
+ const layers = SLUG_KEYED_LAYERS.map(() => "?").join(", ");
249
+ let rows: { layer: string; key: string }[];
250
+ try {
251
+ const db = openDatabaseReadOnly(report.db_path);
252
+ try {
253
+ rows = db
254
+ .prepare(`SELECT layer, key FROM identity WHERE layer IN (${layers}) ORDER BY layer, key`)
255
+ .all(...SLUG_KEYED_LAYERS)
256
+ .map((row) => ({ layer: String(row.layer), key: String(row.key) }));
257
+ } finally {
258
+ db.close();
259
+ }
260
+ } catch {
261
+ return [];
262
+ }
263
+ return rows
264
+ .filter((row) => identityKeyBreaksGrammar(row.layer, row.key))
265
+ .map((row) => ({
266
+ code: "identity_key_outside_grammar",
267
+ severity: "attention",
268
+ subject: row.layer,
269
+ detail: pyRepr(row.key),
270
+ recommendation:
271
+ "an agent may put this key into a shell command unquoted; recreate it under a " +
272
+ "kebab-case key (lowercase letters, digits, and single hyphens)",
273
+ repair_route: "manual review",
274
+ }));
275
+ }
276
+
277
+ /**
278
+ * Port of `_fts_consistency_findings` (CR021). `memories_fts` is an
279
+ * external-content FTS5 index kept in sync by triggers, so a row-count
280
+ * comparison cannot detect desync -- the count always mirrors the content
281
+ * table. A corrupt index, however, raises on any MATCH, which makes a probe
282
+ * query a genuine read-only detector. The authoritative FTS5
283
+ * `integrity-check` needs a writable handle and lives in the write path.
284
+ *
285
+ * See D3 in the module header for the driver-text divergence this carries.
286
+ */
287
+ export function ftsConsistencyFindings(report: RuntimeStatusReport): DriftFinding[] {
288
+ if (report.db_path === null || !report.db_exists) return [];
289
+ let db: ReturnType<typeof openDatabaseReadOnly>;
290
+ try {
291
+ db = openDatabaseReadOnly(report.db_path);
292
+ } catch {
293
+ return [];
294
+ }
295
+ try {
296
+ const tables = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all() as {
297
+ name: string;
298
+ }[];
299
+ if (!tables.some((row) => row.name === "memories_fts")) return [];
300
+ db.prepare("SELECT rowid FROM memories_fts WHERE memories_fts MATCH ? LIMIT 1").get(
301
+ FTS_PROBE_TERM,
302
+ );
303
+ } catch (error) {
304
+ const detail = error instanceof Error ? error.message : String(error);
305
+ return [
306
+ {
307
+ code: "fts_corrupt",
308
+ severity: "attention",
309
+ subject: "memories_fts",
310
+ detail: `FTS query failed (${detail}); the index may be corrupt or desynced`,
311
+ recommendation:
312
+ "rebuild the FTS index: INSERT INTO memories_fts(memories_fts) VALUES('rebuild')",
313
+ repair_route: "rebuild memories_fts",
314
+ },
315
+ ];
316
+ } finally {
317
+ db.close();
318
+ }
319
+ return [];
320
+ }
321
+
322
+ export interface DiagnoseOptions {
323
+ /** Injected only so the 24h boundary is testable; production passes none. */
324
+ now?: Date;
325
+ /** The user's config file; `null` skips it. Tests pin it so a developer's own file never enters a golden. */
326
+ configFile?: string | null;
327
+ }
328
+
329
+ /**
330
+ * Port of `diagnose_runtime`. Finding ORDER is part of the rendered output, so
331
+ * the sequence below mirrors the oracle statement for statement.
332
+ */
333
+ export function diagnoseRuntime(
334
+ report: RuntimeStatusReport,
335
+ worktreeEntries: readonly GitWorktreeEntry[] = [],
336
+ options: DiagnoseOptions = {},
337
+ ): DriftFinding[] {
338
+ const findings: DriftFinding[] = [];
339
+ findings.push(...loosePermissionFindings(report, options.configFile));
340
+ findings.push(...frontDoorErrorFindings(report, options.now));
341
+ findings.push(...ftsConsistencyFindings(report));
342
+ findings.push(...identityKeyFindings(report));
343
+
344
+ if (report.mirror_home_error) {
345
+ findings.push({
346
+ code: "mirror_home_missing",
347
+ severity: "blocker",
348
+ subject: "mirror home",
349
+ detail: report.mirror_home_error,
350
+ recommendation: "configure MIRROR_HOME or MIRROR_USER before update planning",
351
+ repair_route: "configuration",
352
+ });
353
+ }
354
+ if (report.db_exists === false) {
355
+ findings.push({
356
+ code: "database_missing",
357
+ severity: "blocker",
358
+ subject: "database",
359
+ detail: report.db_path ?? "unknown",
360
+ recommendation: "restore or initialize the memory database before update planning",
361
+ repair_route: "restore or initialize",
362
+ });
363
+ }
364
+
365
+ findings.push(...worktreeEntries.map(gitDirtyFinding));
366
+
367
+ for (const migrationId of report.core_migrations.missing) {
368
+ findings.push({
369
+ code: "core_migration_pending",
370
+ severity: "blocker",
371
+ subject: "database _migrations",
372
+ detail: migrationId,
373
+ recommendation: "run pending core migrations only after backup",
374
+ repair_route: "backup then migrate",
375
+ });
376
+ }
377
+ for (const migrationId of report.core_migrations.unknown) {
378
+ findings.push({
379
+ code: "core_migration_unknown",
380
+ severity: "attention",
381
+ subject: "database _migrations",
382
+ detail: migrationId,
383
+ recommendation: "classify as legacy core row, extension-owned row, or experimental drift",
384
+ repair_route: "manual review",
385
+ });
386
+ }
387
+ if (report.core_migrations.note) {
388
+ findings.push({
389
+ code: "core_migration_unreadable",
390
+ severity: "blocker",
391
+ subject: "database _migrations",
392
+ detail: report.core_migrations.note,
393
+ recommendation: "restore readable migration tracking before update planning",
394
+ repair_route: "database repair",
395
+ });
396
+ }
397
+
398
+ for (const extension of report.extension_health) {
399
+ // Only the missing-field shape is a blocker in the oracle: the other
400
+ // manifest failures are reported by `status` but do not gate an update.
401
+ if (extension.note?.includes("missing required field")) {
402
+ findings.push({
403
+ code: "extension_manifest_invalid",
404
+ severity: "blocker",
405
+ subject: `extension/${extension.extension_id}`,
406
+ detail: extension.note,
407
+ recommendation: "repair or reinstall the extension manifest before update planning",
408
+ repair_route: "extension repair",
409
+ });
410
+ }
411
+ for (const filename of extension.pending_migrations) {
412
+ findings.push({
413
+ code: "extension_migration_pending",
414
+ severity: "blocker",
415
+ subject: `extension/${extension.extension_id}`,
416
+ detail: filename,
417
+ recommendation: "run pending extension migrations only after backup",
418
+ repair_route: "backup then extension migrate",
419
+ });
420
+ }
421
+ for (const filename of extension.drifted_migrations) {
422
+ findings.push({
423
+ code: "extension_migration_checksum_drift",
424
+ severity: "blocker",
425
+ subject: `extension/${extension.extension_id}`,
426
+ detail: filename,
427
+ recommendation: "restore the original migration file or create a new migration",
428
+ repair_route: "extension repair",
429
+ });
430
+ }
431
+ for (const filename of extension.unknown_migrations) {
432
+ findings.push({
433
+ code: "extension_migration_unknown",
434
+ severity: "attention",
435
+ subject: `extension/${extension.extension_id}`,
436
+ detail: filename,
437
+ recommendation: "compare installed extension with canonical source and classify history",
438
+ repair_route: "manual review or reinstall",
439
+ });
440
+ }
441
+ }
442
+ return findings;
443
+ }
444
+
445
+ /** The catalog read `probeModelPins` needs. DS5 replay only until DS8. */
446
+ export interface ModelCatalogProvider {
447
+ listAvailableModels(): Promise<Set<string>>;
448
+ }
449
+
450
+ /** A DS5-substrate replay catalog: the fixture IS the answer. */
451
+ export class ReplayModelCatalogProvider implements ModelCatalogProvider {
452
+ private readonly models: Set<string>;
453
+
454
+ constructor(models: Iterable<string>) {
455
+ this.models = new Set(models);
456
+ }
457
+
458
+ async listAvailableModels(): Promise<Set<string>> {
459
+ return this.models;
460
+ }
461
+ }
462
+
463
+ /**
464
+ * Port of `probe_model_pins`. OpenRouter's `/models` lists completion models
465
+ * only, so the extraction pin is catalog-verifiable while the embedding pin is
466
+ * not -- checking the embedding pin against this catalog would be a false
467
+ * positive, and an embedding failure surfaces through degraded search instead.
468
+ *
469
+ * Any inability to read the catalog is INCONCLUSIVE and yields no findings, so
470
+ * diagnose stays green offline and only a confirmed-missing pin warns. With no
471
+ * provider this is always the case: TS3 has no live catalog client, matching
472
+ * the oracle's behavior without a key (DS8 adds the live fetch).
473
+ */
474
+ export async function probeModelPins(
475
+ provider: ModelCatalogProvider | null,
476
+ extractionModel: string,
477
+ ): Promise<DriftFinding[]> {
478
+ if (provider === null) return [];
479
+ let available: Set<string>;
480
+ try {
481
+ available = await provider.listAvailableModels();
482
+ } catch {
483
+ return [];
484
+ }
485
+ if (available.has(extractionModel)) return [];
486
+ return [
487
+ {
488
+ code: "model_pin_unresolved",
489
+ severity: "attention",
490
+ subject: "model pin",
491
+ detail: `${extractionModel} is not available on OpenRouter`,
492
+ recommendation:
493
+ "set MEMORY_EXTRACTION_MODEL to a current model id (see https://openrouter.ai/models)",
494
+ repair_route: "repoint model pin",
495
+ },
496
+ ];
497
+ }
498
+
499
+ /** Port of `render_runtime_diagnosis`. */
500
+ export function renderRuntimeDiagnosis(findings: readonly DriftFinding[]): string {
501
+ const lines: string[] = ["Mirror runtime drift diagnosis", ""];
502
+ if (findings.length === 0) {
503
+ lines.push("Findings: 0", "", "Status: ready");
504
+ return `${lines.join("\n")}\n`;
505
+ }
506
+ lines.push(`Findings: ${findings.length} attention needed`);
507
+ for (const finding of findings) {
508
+ lines.push("");
509
+ lines.push(`[${finding.severity}] ${finding.code}: ${finding.detail}`);
510
+ lines.push(`Subject: ${finding.subject}`);
511
+ lines.push(`Recommendation: ${finding.recommendation}`);
512
+ lines.push(`Repair route: ${finding.repair_route}`);
513
+ }
514
+ lines.push("", "Status: attention needed");
515
+ return `${lines.join("\n")}\n`;
516
+ }
517
+
518
+ // --- CV22.DS10.TS5: the transition's own leftovers -------------------------
519
+
520
+ /**
521
+ * The variables that used to send a route to Python, deleted by CV22.DS10.TS5
522
+ * (D3): the twenty-one family revert gates, the MCP launcher's engine switch
523
+ * (plateau 1), and DS5's replay opt-in, inert since DS8.US3.
524
+ *
525
+ * NAMED, not matched by prefix. The first version of this check reported every
526
+ * non-`_REPLAY` `MIRROR_TS_*` variable, which told users to delete two that
527
+ * still do something -- `MIRROR_TS_MCP_GUARDS` (the MCP wallet guards) and
528
+ * `MIRROR_TS_CONSULT_CONTEXT` (consult's context input). A prefix is a claim
529
+ * about names nobody has written yet (inventory F8).
530
+ */
531
+ export const RETIRED_REVERT_GATES: readonly string[] = [
532
+ "MIRROR_TS_BACKUP",
533
+ "MIRROR_TS_BUILD",
534
+ "MIRROR_TS_CONSULT",
535
+ "MIRROR_TS_CONVERSATION_APPEND",
536
+ "MIRROR_TS_CONVERSATION_LLM_TAIL",
537
+ "MIRROR_TS_CONVERSATION_LOGGER",
538
+ "MIRROR_TS_CONVERSATIONS_LIFECYCLE",
539
+ "MIRROR_TS_CULTIVATION",
540
+ "MIRROR_TS_DESCRIPTOR",
541
+ "MIRROR_TS_EXPLORE",
542
+ "MIRROR_TS_EXTENSIONS",
543
+ "MIRROR_TS_EXTERNAL_ROUTES",
544
+ "MIRROR_TS_IDENTITY_EDIT",
545
+ "MIRROR_TS_JOURNAL",
546
+ "MIRROR_TS_MCP",
547
+ "MIRROR_TS_MIRROR_QUERY",
548
+ "MIRROR_TS_REPAIR_ENCODING",
549
+ "MIRROR_TS_RUNTIME_READS",
550
+ "MIRROR_TS_RUNTIME_UPDATE",
551
+ "MIRROR_TS_SEARCH",
552
+ "MIRROR_TS_SOUL",
553
+ "MIRROR_TS_WEEK",
554
+ "MIRROR_TS_WELCOME",
555
+ ];
556
+
557
+ /**
558
+ * A retired gate still set in the environment.
559
+ *
560
+ * A value left in someone's `.env` changes nothing -- which is the right
561
+ * behavior and an invisible one, so diagnose says it out loud rather than
562
+ * letting a user believe a revert is armed.
563
+ */
564
+ export function staleRevertGateFindings(env: NodeJS.ProcessEnv): DriftFinding[] {
565
+ const stale = RETIRED_REVERT_GATES.filter((name) => (env[name] ?? "") !== "");
566
+ if (stale.length === 0) return [];
567
+ return [
568
+ {
569
+ code: "stale_revert_gate",
570
+ severity: "info",
571
+ subject: stale.join(", "),
572
+ detail:
573
+ "set, but inert since CV22.DS10.TS5: the Python engine these gates reverted to no longer exists",
574
+ recommendation: "remove from .env and the environment; routing ignores them",
575
+ repair_route: "edit .env",
576
+ },
577
+ ];
578
+ }
579
+
580
+ /**
581
+ * Where a hook wrapper looks for Node after `MIRROR_NODE` and `PATH`, in order.
582
+ * The generated wrappers carry the same list (`for candidate in ...`), and
583
+ * `hooks.test.ts` fails if the two differ: they had, by `/usr/bin/node`, which
584
+ * diagnose counted and no wrapper searched (TS5 handoff review, finding N1).
585
+ */
586
+ export const HOOK_NODE_CANDIDATES: readonly string[] = [
587
+ "$HOME/.nvm/current/bin/node",
588
+ "/opt/homebrew/bin/node",
589
+ "/usr/local/bin/node",
590
+ ];
591
+
592
+ /** How far back `hookFailureFindings` looks. Longer than the front door's day,
593
+ * because a hook failure is the kind a user notices weeks later. */
594
+ const HOOK_FAILURE_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
595
+
596
+ /**
597
+ * Hook failures recorded in `<mirror home>/hooks.log` in the last 7 days.
598
+ *
599
+ * `hookNodeFindings` can only ask from diagnose's own environment. The hooks
600
+ * write this file from theirs -- a Node that is missing or cannot run them,
601
+ * a front-door call that failed -- so it is the evidence of what a runtime
602
+ * actually met. The finding counts and names the latest hook; the reasons
603
+ * stay in the file, because a diagnosis gets pasted into issues.
604
+ */
605
+ export function hookFailureFindings(
606
+ report: RuntimeStatusReport,
607
+ now: Date = new Date(),
608
+ ): DriftFinding[] {
609
+ if (report.db_path === null) return [];
610
+ const logPath = join(dirname(report.db_path), "hooks.log");
611
+ if (!existsSync(logPath)) return [];
612
+ let lines: string[];
613
+ try {
614
+ lines = readFileSync(logPath, "utf8").split("\n");
615
+ } catch {
616
+ return [];
617
+ }
618
+ const cutoff = now.getTime() - HOOK_FAILURE_WINDOW_MS;
619
+ let count = 0;
620
+ let latest: { stamp: string; hook: string } | null = null;
621
+ for (const line of lines) {
622
+ const match = /^(\S+) ([^\s:]+:[^\s:]+): /.exec(line);
623
+ if (!match) continue;
624
+ const [, stamp = "", hook = ""] = match;
625
+ const time = parseLogTimestamp(stamp);
626
+ if (time === null || time < cutoff) continue;
627
+ count += 1;
628
+ latest = { stamp, hook };
629
+ }
630
+ if (latest === null) return [];
631
+ return [
632
+ {
633
+ code: "hook_failures_recorded",
634
+ severity: "warning",
635
+ subject: "hooks",
636
+ detail: `${count} hook failure(s) in the last 7 days, latest ${latest.stamp}, ${latest.hook} (${logPath})`,
637
+ recommendation:
638
+ "read the log: a missing or too-old Node, or a failing front-door call, each has its own line",
639
+ repair_route: "review hooks.log",
640
+ },
641
+ ];
642
+ }
643
+
644
+ /**
645
+ * Can a hook find Node?
646
+ *
647
+ * The failure this exists for: `/usr/bin/python3` is on every macOS, so the
648
+ * Python hooks always found their interpreter and could end in `|| true`
649
+ * safely. `node` usually lives under nvm or Homebrew and is NOT on the PATH a
650
+ * GUI-launched runtime inherits. A hook that cannot resolve it skips silently,
651
+ * and the user meets it weeks later as "Mirror stopped remembering".
652
+ *
653
+ * The wrappers write to `<mirror-home>/hooks.log` when this happens. This
654
+ * finding is the other half: a place to ASK, before anything is lost.
655
+ *
656
+ * It asks from the CALLER's environment, which is a terminal's, not a GUI
657
+ * runtime's -- so it can say Node is missing, never that a hook found it.
658
+ * `hookFailureFindings` reads the evidence hooks write from their own context.
659
+ */
660
+ export function hookNodeFindings(
661
+ env: NodeJS.ProcessEnv,
662
+ isExecutable: (path: string) => boolean,
663
+ ): DriftFinding[] {
664
+ const explicit = env.MIRROR_NODE ?? "";
665
+ if (explicit && isExecutable(explicit)) return [];
666
+ const candidates = HOOK_NODE_CANDIDATES.map((candidate) =>
667
+ candidate.replace("$HOME", env.HOME ?? ""),
668
+ );
669
+ const onPath = (env.PATH ?? "")
670
+ .split(":")
671
+ .filter(Boolean)
672
+ .some((entry) => isExecutable(`${entry}/node`));
673
+ if (onPath || candidates.some((candidate) => isExecutable(candidate))) return [];
674
+ return [
675
+ {
676
+ code: "hook_node_unresolvable",
677
+ severity: "warning",
678
+ subject: "node",
679
+ detail:
680
+ "not found on PATH or in the usual install locations; runtime hooks will skip and log to hooks.log",
681
+ recommendation:
682
+ "set MIRROR_NODE to the node binary, or install node where the runtime can see it",
683
+ repair_route: "export MIRROR_NODE=/path/to/node",
684
+ },
685
+ ];
686
+ }