mirror-mind 0.0.0-stage → 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 (468) 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/LICENSE +21 -0
  37. package/README.md +128 -2
  38. package/bin/loader.js +67 -0
  39. package/bin/mirror-hook.js +7 -0
  40. package/bin/mirror.js +7 -0
  41. package/docs/releases/index.md +99 -0
  42. package/docs/releases/pending-cutoffs.md +510 -0
  43. package/docs/releases/v0.10.0.md +123 -0
  44. package/docs/releases/v0.10.1.md +66 -0
  45. package/docs/releases/v0.10.10.md +51 -0
  46. package/docs/releases/v0.10.11.md +39 -0
  47. package/docs/releases/v0.10.12.md +57 -0
  48. package/docs/releases/v0.10.13.md +35 -0
  49. package/docs/releases/v0.10.14.md +42 -0
  50. package/docs/releases/v0.10.15.md +53 -0
  51. package/docs/releases/v0.10.16.md +35 -0
  52. package/docs/releases/v0.10.17.md +43 -0
  53. package/docs/releases/v0.10.2.md +64 -0
  54. package/docs/releases/v0.10.3.md +78 -0
  55. package/docs/releases/v0.10.4.md +42 -0
  56. package/docs/releases/v0.10.5.md +57 -0
  57. package/docs/releases/v0.10.6.md +47 -0
  58. package/docs/releases/v0.10.7.md +53 -0
  59. package/docs/releases/v0.10.8.md +49 -0
  60. package/docs/releases/v0.10.9.md +37 -0
  61. package/docs/releases/v0.11.0.md +86 -0
  62. package/docs/releases/v0.12.0.md +67 -0
  63. package/docs/releases/v0.13.0.md +64 -0
  64. package/docs/releases/v0.14.0.md +68 -0
  65. package/docs/releases/v0.15.0.md +48 -0
  66. package/docs/releases/v0.16.0.md +50 -0
  67. package/docs/releases/v0.16.1.md +55 -0
  68. package/docs/releases/v0.16.2.md +36 -0
  69. package/docs/releases/v0.17.0.md +50 -0
  70. package/docs/releases/v0.17.1.md +58 -0
  71. package/docs/releases/v0.17.2.md +42 -0
  72. package/docs/releases/v0.17.3.md +58 -0
  73. package/docs/releases/v0.18.0.md +84 -0
  74. package/docs/releases/v0.18.1.md +51 -0
  75. package/docs/releases/v0.18.2.md +69 -0
  76. package/docs/releases/v0.19.0.md +85 -0
  77. package/docs/releases/v0.19.1.md +46 -0
  78. package/docs/releases/v0.20.0.md +85 -0
  79. package/docs/releases/v0.21.0.md +61 -0
  80. package/docs/releases/v0.21.1.md +47 -0
  81. package/docs/releases/v0.22.0.md +77 -0
  82. package/docs/releases/v0.22.1.md +42 -0
  83. package/docs/releases/v0.23.0.md +105 -0
  84. package/docs/releases/v0.24.0.md +125 -0
  85. package/docs/releases/v0.25.0.md +107 -0
  86. package/docs/releases/v0.26.0.md +139 -0
  87. package/docs/releases/v0.26.1.md +65 -0
  88. package/docs/releases/v0.26.2.md +43 -0
  89. package/docs/releases/v0.27.0.md +63 -0
  90. package/docs/releases/v0.28.0.md +59 -0
  91. package/docs/releases/v0.29.0.md +61 -0
  92. package/docs/releases/v0.29.1.md +59 -0
  93. package/docs/releases/v0.29.2.md +59 -0
  94. package/docs/releases/v0.29.3.md +93 -0
  95. package/docs/releases/v0.30.0.md +75 -0
  96. package/docs/releases/v0.30.1.md +93 -0
  97. package/docs/releases/v0.30.2.md +66 -0
  98. package/docs/releases/v0.31.0.md +122 -0
  99. package/docs/releases/v0.31.1.md +142 -0
  100. package/docs/releases/v0.31.10.md +118 -0
  101. package/docs/releases/v0.31.11.md +88 -0
  102. package/docs/releases/v0.31.12.md +105 -0
  103. package/docs/releases/v0.31.13.md +99 -0
  104. package/docs/releases/v0.31.14.md +97 -0
  105. package/docs/releases/v0.31.2.md +115 -0
  106. package/docs/releases/v0.31.3.md +142 -0
  107. package/docs/releases/v0.31.4.md +117 -0
  108. package/docs/releases/v0.31.5.md +155 -0
  109. package/docs/releases/v0.31.6.md +132 -0
  110. package/docs/releases/v0.31.7.md +132 -0
  111. package/docs/releases/v0.31.8.md +120 -0
  112. package/docs/releases/v0.31.9.md +120 -0
  113. package/docs/releases/v0.32.0.md +261 -0
  114. package/docs/releases/v0.32.1.md +59 -0
  115. package/docs/releases/v0.8.0.md +118 -0
  116. package/docs/releases/v0.9.0.md +98 -0
  117. package/docs/releases/v0.9.1.md +83 -0
  118. package/package.json +110 -4
  119. package/plugins/mirror-mind/.claude-plugin/plugin.json +13 -0
  120. package/plugins/mirror-mind/hooks/hooks.json +50 -0
  121. package/plugins/mirror-mind/hooks/log-session-end.sh +98 -0
  122. package/plugins/mirror-mind/hooks/log-user-prompt.sh +98 -0
  123. package/plugins/mirror-mind/hooks/mirror-inject.sh +98 -0
  124. package/plugins/mirror-mind/hooks/session-start.sh +98 -0
  125. package/plugins/mirror-mind/mcp/launch.sh +52 -0
  126. package/plugins/mirror-mind/skills/mm-backup/SKILL.md +27 -0
  127. package/plugins/mirror-mind/skills/mm-build/SKILL.md +1206 -0
  128. package/plugins/mirror-mind/skills/mm-consolidate/SKILL.md +112 -0
  129. package/plugins/mirror-mind/skills/mm-consult/SKILL.md +83 -0
  130. package/plugins/mirror-mind/skills/mm-conversations/SKILL.md +17 -0
  131. package/plugins/mirror-mind/skills/mm-discard/SKILL.md +40 -0
  132. package/plugins/mirror-mind/skills/mm-explore/SKILL.md +241 -0
  133. package/plugins/mirror-mind/skills/mm-help/SKILL.md +104 -0
  134. package/plugins/mirror-mind/skills/mm-identity/SKILL.md +96 -0
  135. package/plugins/mirror-mind/skills/mm-journal/SKILL.md +21 -0
  136. package/plugins/mirror-mind/skills/mm-journey/SKILL.md +37 -0
  137. package/plugins/mirror-mind/skills/mm-journeys/SKILL.md +15 -0
  138. package/plugins/mirror-mind/skills/mm-memories/SKILL.md +20 -0
  139. package/plugins/mirror-mind/skills/mm-mirror/SKILL.md +65 -0
  140. package/plugins/mirror-mind/skills/mm-mute/SKILL.md +18 -0
  141. package/plugins/mirror-mind/skills/mm-new/SKILL.md +18 -0
  142. package/plugins/mirror-mind/skills/mm-recall/SKILL.md +17 -0
  143. package/plugins/mirror-mind/skills/mm-release-notes/SKILL.md +31 -0
  144. package/plugins/mirror-mind/skills/mm-seed/SKILL.md +15 -0
  145. package/plugins/mirror-mind/skills/mm-shadow/SKILL.md +117 -0
  146. package/plugins/mirror-mind/skills/mm-soul/SKILL.md +488 -0
  147. package/plugins/mirror-mind/skills/mm-tasks/SKILL.md +36 -0
  148. package/plugins/mirror-mind/skills/mm-update/SKILL.md +30 -0
  149. package/plugins/mirror-mind/skills/mm-week/SKILL.md +46 -0
  150. package/plugins/mirror-mind/skills/mm-welcome/SKILL.md +22 -0
  151. package/scripts/codex-hooks/session-end.sh +85 -0
  152. package/scripts/codex-hooks/session-start.sh +85 -0
  153. package/scripts/codex-mirror.sh +48 -0
  154. package/templates/identity/README.md +119 -0
  155. package/templates/identity/ego/behavior.yaml +52 -0
  156. package/templates/identity/ego/constraints.yaml +15 -0
  157. package/templates/identity/ego/identity.yaml +43 -0
  158. package/templates/identity/journeys/personal-growth.yaml +39 -0
  159. package/templates/identity/organization/identity.yaml +24 -0
  160. package/templates/identity/organization/principles.yaml +17 -0
  161. package/templates/identity/personas/coach.yaml +94 -0
  162. package/templates/identity/personas/designer.yaml +93 -0
  163. package/templates/identity/personas/doctor.yaml +96 -0
  164. package/templates/identity/personas/engineer.yaml +77 -0
  165. package/templates/identity/personas/financial.yaml +97 -0
  166. package/templates/identity/personas/prompt-engineer.yaml +122 -0
  167. package/templates/identity/personas/researcher.yaml +88 -0
  168. package/templates/identity/personas/strategist.yaml +91 -0
  169. package/templates/identity/personas/teacher.yaml +95 -0
  170. package/templates/identity/personas/therapist.yaml +91 -0
  171. package/templates/identity/personas/thinker.yaml +74 -0
  172. package/templates/identity/personas/writer.yaml +76 -0
  173. package/templates/identity/self/config.yaml +16 -0
  174. package/templates/identity/self/soul.yaml +105 -0
  175. package/templates/identity/user/identity.yaml +30 -0
  176. package/ts/src/backup/staging.ts +62 -0
  177. package/ts/src/backup/zipBackup.ts +267 -0
  178. package/ts/src/backup/zipReader.ts +109 -0
  179. package/ts/src/backup/zipWriter.ts +148 -0
  180. package/ts/src/builder/activeCheckpoint.ts +134 -0
  181. package/ts/src/builder/aggregateStatus.ts +34 -0
  182. package/ts/src/builder/approve.ts +149 -0
  183. package/ts/src/builder/argv.ts +292 -0
  184. package/ts/src/builder/ariadMethod.ts +536 -0
  185. package/ts/src/builder/artifacts/artifactSurfaces.ts +106 -0
  186. package/ts/src/builder/artifacts/artifactWriter.ts +113 -0
  187. package/ts/src/builder/artifacts/closureArtifacts.ts +199 -0
  188. package/ts/src/builder/artifacts/deliveryStoryArtifacts.ts +316 -0
  189. package/ts/src/builder/artifacts/planArtifacts.ts +242 -0
  190. package/ts/src/builder/artifacts/scaffoldSections.ts +273 -0
  191. package/ts/src/builder/artifacts/scaffoldState.ts +227 -0
  192. package/ts/src/builder/artifacts/storyFiles.ts +61 -0
  193. package/ts/src/builder/artifacts/storyIndex.ts +166 -0
  194. package/ts/src/builder/cadence.ts +59 -0
  195. package/ts/src/builder/card.ts +192 -0
  196. package/ts/src/builder/checkpointRefused.ts +66 -0
  197. package/ts/src/builder/closure.ts +964 -0
  198. package/ts/src/builder/commands.ts +2053 -0
  199. package/ts/src/builder/cursorPosition.ts +176 -0
  200. package/ts/src/builder/cursorTransitions.ts +77 -0
  201. package/ts/src/builder/deliveryCursor.ts +577 -0
  202. package/ts/src/builder/deliveryStoryClosure.ts +521 -0
  203. package/ts/src/builder/deliveryStoryPlan.ts +536 -0
  204. package/ts/src/builder/deliveryStoryReady.ts +160 -0
  205. package/ts/src/builder/deliveryStoryRoadmapClosure.ts +151 -0
  206. package/ts/src/builder/expand.ts +336 -0
  207. package/ts/src/builder/flowUnit.ts +339 -0
  208. package/ts/src/builder/homeSurface.ts +122 -0
  209. package/ts/src/builder/implementationGuard.ts +125 -0
  210. package/ts/src/builder/index.ts +11 -0
  211. package/ts/src/builder/lifecycleRefusal.ts +92 -0
  212. package/ts/src/builder/lifecycleRibbon.ts +159 -0
  213. package/ts/src/builder/load.ts +313 -0
  214. package/ts/src/builder/methodAdoption.ts +113 -0
  215. package/ts/src/builder/methodDefinition.ts +429 -0
  216. package/ts/src/builder/methodInspection.ts +371 -0
  217. package/ts/src/builder/plan.ts +408 -0
  218. package/ts/src/builder/planPreauthorization.ts +265 -0
  219. package/ts/src/builder/prepare.ts +225 -0
  220. package/ts/src/builder/projectPaths.ts +64 -0
  221. package/ts/src/builder/pull.ts +356 -0
  222. package/ts/src/builder/pullCandidates.ts +463 -0
  223. package/ts/src/builder/pullCandidatesRender.ts +238 -0
  224. package/ts/src/builder/refinementField.ts +93 -0
  225. package/ts/src/builder/releaseIntent.ts +173 -0
  226. package/ts/src/builder/resumeState.ts +84 -0
  227. package/ts/src/builder/resumeSurface.ts +149 -0
  228. package/ts/src/builder/roadmapGrammar.ts +300 -0
  229. package/ts/src/builder/roadmapScan.ts +97 -0
  230. package/ts/src/builder/roadmapScope.ts +210 -0
  231. package/ts/src/builder/scopePhrases.ts +146 -0
  232. package/ts/src/builder/storyPaths.ts +196 -0
  233. package/ts/src/builder/storyPlanPreauthorization.ts +287 -0
  234. package/ts/src/builder/surfaceProtocol.ts +42 -0
  235. package/ts/src/builder/templateGeneration.ts +148 -0
  236. package/ts/src/builder/transition.ts +178 -0
  237. package/ts/src/consult/args.ts +99 -0
  238. package/ts/src/consult/core.ts +122 -0
  239. package/ts/src/consult/modelCatalog.ts +55 -0
  240. package/ts/src/consult/render.ts +47 -0
  241. package/ts/src/conversation/append.ts +414 -0
  242. package/ts/src/conversation/backfill.ts +387 -0
  243. package/ts/src/conversation/closeTail.ts +467 -0
  244. package/ts/src/conversation/extraction.ts +333 -0
  245. package/ts/src/conversation/extractionDriver.ts +130 -0
  246. package/ts/src/conversation/extractionRun.ts +70 -0
  247. package/ts/src/conversation/journeyInference.ts +165 -0
  248. package/ts/src/conversation/journeyRepair.ts +114 -0
  249. package/ts/src/conversation/lifecycleFaces.ts +197 -0
  250. package/ts/src/conversation/lifecycleWrites.ts +313 -0
  251. package/ts/src/conversation/listing.ts +77 -0
  252. package/ts/src/conversation/logger.ts +576 -0
  253. package/ts/src/conversation/loggerCli.ts +282 -0
  254. package/ts/src/conversation/loggerRuntime.ts +259 -0
  255. package/ts/src/conversation/metadataLifecycle.ts +443 -0
  256. package/ts/src/conversation/recall.ts +66 -0
  257. package/ts/src/conversation/sessionComposites.ts +258 -0
  258. package/ts/src/conversation/sessionImport.ts +90 -0
  259. package/ts/src/conversation/transcriptBackfill.ts +141 -0
  260. package/ts/src/cultivation/applyActions.ts +241 -0
  261. package/ts/src/cultivation/cluster.ts +86 -0
  262. package/ts/src/cultivation/consolidationStore.ts +281 -0
  263. package/ts/src/cultivation/promptContext.ts +62 -0
  264. package/ts/src/cultivation/propose.ts +305 -0
  265. package/ts/src/cultivation/scan.ts +155 -0
  266. package/ts/src/db/backupGate.ts +47 -0
  267. package/ts/src/db/bootstrap.ts +108 -0
  268. package/ts/src/db/bootstrapLock.ts +223 -0
  269. package/ts/src/db/copyGuard.ts +60 -0
  270. package/ts/src/db/database.ts +397 -0
  271. package/ts/src/db/decode.ts +66 -0
  272. package/ts/src/db/ftsIntegrity.ts +41 -0
  273. package/ts/src/db/migrateOnOpen.ts +214 -0
  274. package/ts/src/db/migrations.ts +576 -0
  275. package/ts/src/db/readyOnOpen.ts +35 -0
  276. package/ts/src/db/rowDecode.ts +42 -0
  277. package/ts/src/db/schema.ts +432 -0
  278. package/ts/src/db/schemaInventory.ts +225 -0
  279. package/ts/src/db/schemaInventorySnapshot.ts +2107 -0
  280. package/ts/src/db/schemaState.ts +91 -0
  281. package/ts/src/db/schemaTsDivergence.ts +114 -0
  282. package/ts/src/descriptor/descriptorRead.ts +49 -0
  283. package/ts/src/docs/docsLint.ts +265 -0
  284. package/ts/src/explorer/handoff.ts +522 -0
  285. package/ts/src/explorer/render.ts +335 -0
  286. package/ts/src/explorer/story.ts +769 -0
  287. package/ts/src/explorer/transition.ts +72 -0
  288. package/ts/src/extensions/bindings.ts +170 -0
  289. package/ts/src/extensions/catalog.ts +249 -0
  290. package/ts/src/extensions/catalogCommands.ts +513 -0
  291. package/ts/src/extensions/catalogWrites.ts +652 -0
  292. package/ts/src/extensions/contextRuntime.ts +325 -0
  293. package/ts/src/extensions/dispatch.ts +374 -0
  294. package/ts/src/extensions/errors.ts +24 -0
  295. package/ts/src/extensions/manifest.ts +356 -0
  296. package/ts/src/extensions/migrations.ts +350 -0
  297. package/ts/src/extraction/conversation.ts +304 -0
  298. package/ts/src/extraction/conversationMetadata.ts +206 -0
  299. package/ts/src/extraction/fencing.ts +106 -0
  300. package/ts/src/extraction/json.ts +17 -0
  301. package/ts/src/extraction/prompts.ts +470 -0
  302. package/ts/src/frontDoor/args.ts +30 -0
  303. package/ts/src/frontDoor/argvShape.ts +106 -0
  304. package/ts/src/frontDoor/buildLoadRuntime.ts +139 -0
  305. package/ts/src/frontDoor/buildRoute.ts +398 -0
  306. package/ts/src/frontDoor/cli.ts +1865 -0
  307. package/ts/src/frontDoor/cloneRoleGuard.ts +98 -0
  308. package/ts/src/frontDoor/consultRoute.ts +50 -0
  309. package/ts/src/frontDoor/contentTailRoute.ts +239 -0
  310. package/ts/src/frontDoor/conversationLoggerRoute.ts +73 -0
  311. package/ts/src/frontDoor/cultivationRoute.ts +210 -0
  312. package/ts/src/frontDoor/dbPath.ts +131 -0
  313. package/ts/src/frontDoor/dbSafetyToolsRoute.ts +240 -0
  314. package/ts/src/frontDoor/exploreRoute.ts +572 -0
  315. package/ts/src/frontDoor/extensionCatalogRoute.ts +252 -0
  316. package/ts/src/frontDoor/frontDoorLog.ts +57 -0
  317. package/ts/src/frontDoor/identityWrite.ts +36 -0
  318. package/ts/src/frontDoor/journeyWriteRoute.ts +26 -0
  319. package/ts/src/frontDoor/lifecycleWriteRoute.ts +110 -0
  320. package/ts/src/frontDoor/liveBackup.ts +100 -0
  321. package/ts/src/frontDoor/mirrorModeRoute.ts +163 -0
  322. package/ts/src/frontDoor/namedRefusal.ts +35 -0
  323. package/ts/src/frontDoor/nodeSupport.ts +24 -0
  324. package/ts/src/frontDoor/render/consolidate.ts +199 -0
  325. package/ts/src/frontDoor/render/conversations.ts +33 -0
  326. package/ts/src/frontDoor/render/cultivationShared.ts +21 -0
  327. package/ts/src/frontDoor/render/descriptor.ts +20 -0
  328. package/ts/src/frontDoor/render/detectPersona.ts +50 -0
  329. package/ts/src/frontDoor/render/icons.ts +13 -0
  330. package/ts/src/frontDoor/render/identity.ts +59 -0
  331. package/ts/src/frontDoor/render/identityRows.ts +17 -0
  332. package/ts/src/frontDoor/render/inspectPersona.ts +53 -0
  333. package/ts/src/frontDoor/render/journeyStatus.ts +78 -0
  334. package/ts/src/frontDoor/render/journeys.ts +79 -0
  335. package/ts/src/frontDoor/render/list.ts +41 -0
  336. package/ts/src/frontDoor/render/memories.ts +67 -0
  337. package/ts/src/frontDoor/render/recall.ts +49 -0
  338. package/ts/src/frontDoor/render/shadow.ts +155 -0
  339. package/ts/src/frontDoor/render/tasks.ts +99 -0
  340. package/ts/src/frontDoor/render/tasksImportSync.ts +63 -0
  341. package/ts/src/frontDoor/render/week.ts +113 -0
  342. package/ts/src/frontDoor/routing.ts +999 -0
  343. package/ts/src/frontDoor/runtimeRoute.ts +623 -0
  344. package/ts/src/frontDoor/searchRoute.ts +166 -0
  345. package/ts/src/frontDoor/seedPaths.ts +98 -0
  346. package/ts/src/frontDoor/soulRoute.ts +433 -0
  347. package/ts/src/frontDoor/tasksImportSyncRoute.ts +110 -0
  348. package/ts/src/frontDoor/tasksWriteRoute.ts +101 -0
  349. package/ts/src/frontDoor/usage.ts +176 -0
  350. package/ts/src/guards/claudePlugin.ts +252 -0
  351. package/ts/src/guards/packContents.ts +306 -0
  352. package/ts/src/guards/piSourcedSkills.ts +70 -0
  353. package/ts/src/guards/retiredRouteShadows.ts +57 -0
  354. package/ts/src/guards/retiredSurfaces.ts +564 -0
  355. package/ts/src/hooks/claude.ts +176 -0
  356. package/ts/src/hooks/codex.ts +45 -0
  357. package/ts/src/hooks/gemini.ts +126 -0
  358. package/ts/src/hooks/main.ts +109 -0
  359. package/ts/src/hooks/mirrorState.ts +71 -0
  360. package/ts/src/hooks/payload.ts +68 -0
  361. package/ts/src/hooks/runtime.ts +159 -0
  362. package/ts/src/identity/applyConsolidationIdentityUpdate.ts +67 -0
  363. package/ts/src/identity/descriptorGenerate.ts +162 -0
  364. package/ts/src/identity/identityEdit.ts +143 -0
  365. package/ts/src/identity/identityKey.ts +77 -0
  366. package/ts/src/identity/identityRead.ts +69 -0
  367. package/ts/src/identity/identityStore.ts +80 -0
  368. package/ts/src/identity/journeyListing.ts +53 -0
  369. package/ts/src/identity/personaInspect.ts +44 -0
  370. package/ts/src/identity/personaListing.ts +37 -0
  371. package/ts/src/identity/setIdentity.ts +64 -0
  372. package/ts/src/index.ts +231 -0
  373. package/ts/src/init/init.ts +111 -0
  374. package/ts/src/journey/journeyOptions.ts +131 -0
  375. package/ts/src/journey/journeyRemoval.ts +158 -0
  376. package/ts/src/journey/journeyStatus.ts +149 -0
  377. package/ts/src/journey/journeySyncFile.ts +87 -0
  378. package/ts/src/journey/journeyWrite.ts +203 -0
  379. package/ts/src/journey/parentJourney.ts +41 -0
  380. package/ts/src/journey/validateParentJourney.ts +55 -0
  381. package/ts/src/mcp/boundary.ts +151 -0
  382. package/ts/src/mcp/guards.ts +244 -0
  383. package/ts/src/mcp/main.ts +121 -0
  384. package/ts/src/mcp/payload.ts +141 -0
  385. package/ts/src/mcp/protocol.ts +141 -0
  386. package/ts/src/mcp/readModels.ts +241 -0
  387. package/ts/src/mcp/registry.ts +201 -0
  388. package/ts/src/mcp/serve.ts +120 -0
  389. package/ts/src/mcp/tools/deterministic.ts +137 -0
  390. package/ts/src/mcp/tools/providerCrossing.ts +187 -0
  391. package/ts/src/mcp/wire.ts +8 -0
  392. package/ts/src/memory/journal.ts +180 -0
  393. package/ts/src/memory/listing.ts +107 -0
  394. package/ts/src/memory/memoryWrite.ts +88 -0
  395. package/ts/src/memory/reinforcement.ts +35 -0
  396. package/ts/src/mirror/context.ts +161 -0
  397. package/ts/src/mirror/defaultResolution.ts +199 -0
  398. package/ts/src/mirror/orchestration.ts +364 -0
  399. package/ts/src/mirror/reception.ts +194 -0
  400. package/ts/src/mirror/render.ts +75 -0
  401. package/ts/src/mirror/runtimeSession.ts +205 -0
  402. package/ts/src/mode/operatingMode.ts +148 -0
  403. package/ts/src/observability/callOutcome.ts +115 -0
  404. package/ts/src/observability/inspectLedger.ts +329 -0
  405. package/ts/src/observability/ledgerHooks.ts +126 -0
  406. package/ts/src/observability/llmCalls.ts +154 -0
  407. package/ts/src/persona/detectPersona.ts +81 -0
  408. package/ts/src/planning/promptAssembly.ts +111 -0
  409. package/ts/src/planning/weekPending.ts +81 -0
  410. package/ts/src/planning/weekPlan.ts +183 -0
  411. package/ts/src/planning/weekSave.ts +117 -0
  412. package/ts/src/providers/config.ts +189 -0
  413. package/ts/src/providers/cost.ts +64 -0
  414. package/ts/src/providers/credits.ts +204 -0
  415. package/ts/src/providers/embedding.ts +321 -0
  416. package/ts/src/providers/familyProviders.ts +147 -0
  417. package/ts/src/providers/llm.ts +334 -0
  418. package/ts/src/providers/openrouter.ts +244 -0
  419. package/ts/src/providers/redaction.ts +110 -0
  420. package/ts/src/providers/replay.ts +15 -0
  421. package/ts/src/providers/transport.ts +410 -0
  422. package/ts/src/repair/encodingRepair.ts +253 -0
  423. package/ts/src/runtime/backup.ts +151 -0
  424. package/ts/src/runtime/channel.ts +146 -0
  425. package/ts/src/runtime/config.ts +168 -0
  426. package/ts/src/runtime/diagnose.ts +686 -0
  427. package/ts/src/runtime/git.ts +510 -0
  428. package/ts/src/runtime/installKind.ts +220 -0
  429. package/ts/src/runtime/migrate.ts +154 -0
  430. package/ts/src/runtime/packageIdentity.ts +102 -0
  431. package/ts/src/runtime/releaseNotes.ts +244 -0
  432. package/ts/src/runtime/status.ts +557 -0
  433. package/ts/src/runtime/strategies/clone.ts +200 -0
  434. package/ts/src/runtime/strategies/package.ts +328 -0
  435. package/ts/src/runtime/treeRoot.ts +30 -0
  436. package/ts/src/runtime/update.ts +445 -0
  437. package/ts/src/runtime/updateGate.ts +40 -0
  438. package/ts/src/runtime/updatePipeline.ts +159 -0
  439. package/ts/src/runtime/version.ts +36 -0
  440. package/ts/src/runtime/wiring.ts +130 -0
  441. package/ts/src/search/memorySearch.ts +338 -0
  442. package/ts/src/search/ranker.ts +154 -0
  443. package/ts/src/seed/seed.ts +256 -0
  444. package/ts/src/seed/seedYaml.ts +133 -0
  445. package/ts/src/soul/apply.ts +218 -0
  446. package/ts/src/soul/harvest.ts +267 -0
  447. package/ts/src/soul/prompts/soul_beauty_voice.md +103 -0
  448. package/ts/src/soul/prompts/soul_self_voice.md +219 -0
  449. package/ts/src/soul/prompts/soul_wisdom_voice.md +121 -0
  450. package/ts/src/soul/prompts.ts +85 -0
  451. package/ts/src/soul/render.ts +358 -0
  452. package/ts/src/soul/state.ts +212 -0
  453. package/ts/src/soul/transition.ts +37 -0
  454. package/ts/src/tasks/journeyPathParse.ts +113 -0
  455. package/ts/src/tasks/taskImportSync.ts +142 -0
  456. package/ts/src/tasks/taskStore.ts +312 -0
  457. package/ts/src/tasks/weekView.ts +120 -0
  458. package/ts/src/util/clipCodePoints.ts +26 -0
  459. package/ts/src/util/paths.ts +63 -0
  460. package/ts/src/util/program.ts +9 -0
  461. package/ts/src/util/pyGenerators.ts +144 -0
  462. package/ts/src/util/pythonText.ts +317 -0
  463. package/ts/src/util/semver.ts +45 -0
  464. package/ts/src/util/shellWord.ts +25 -0
  465. package/ts/src/util/slug.ts +64 -0
  466. package/ts/src/welcome/card.ts +339 -0
  467. package/ts/src/welcome/statusLine.ts +101 -0
  468. package/ts/src/welcome/updateCache.ts +129 -0
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env bash
2
+ # Mirror Mind hook wrapper — GENERATED by scripts/generate_hook_wrappers.sh.
3
+ # Edit the generator, never this file: every wrapper shares these bytes and
4
+ # differs only in the hook name and in how it finds the Mirror entry
5
+ # (CV22.DS10.TS5 decision D4; CV22.DS10.US3 decision D5).
6
+ set -uo pipefail
7
+ HOOK="gemini:session-start"
8
+
9
+ # Resolve the tree from THIS FILE, never from the cwd: a runtime spawns hooks
10
+ # from whatever directory the session is in. The tree is a checkout or the
11
+ # installed package; the wrapper's depth to its root is the same in both.
12
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
13
+ REPO_ROOT="$(cd "$HERE/../.." && pwd -P)"
14
+
15
+ # A hook never fails the user's turn, and never fails in silence: when it
16
+ # cannot do its job it appends ONE line to <mirror home>/hooks.log and exits 0.
17
+ # The home is found the way the core finds it -- MIRROR_HOME, else
18
+ # MIRROR_USER -- from the environment when set there, else from the tree's
19
+ # .env when the wrapper is in a tree, else from the user's config file
20
+ # (~/.config/mirror/env), in the core's order (US3, D3). A GUI launch carries
21
+ # neither variable, and the first wrappers then wrote to a home nobody reads.
22
+ env_file_value() {
23
+ [ -f "$2" ] || return 0
24
+ sed -n -E "s/^[[:space:]]*(export[[:space:]]+)?$1[[:space:]]*=[[:space:]]*//p" "$2" \
25
+ | tail -n 1 | tr -d "\"'\r"
26
+ }
27
+ env_value() {
28
+ local value=""
29
+ if [ -n "${!1+set}" ]; then printf '%s' "${!1}"; return; fi
30
+ [ -z "$REPO_ROOT" ] || value="$(env_file_value "$1" "$REPO_ROOT/.env")"
31
+ [ -n "$value" ] || value="$(env_file_value "$1" "${XDG_CONFIG_HOME:-$HOME/.config}/mirror/env")"
32
+ printf '%s' "$value"
33
+ }
34
+ note() {
35
+ local home user
36
+ home="$(env_value MIRROR_HOME)"
37
+ user="$(env_value MIRROR_USER)"
38
+ if [ -n "$home" ]; then
39
+ home="${home/#\~/$HOME}"
40
+ elif [ -n "$user" ]; then
41
+ home="$HOME/.mirror-minds/$user"
42
+ [ -d "$home" ] || [ ! -d "$HOME/.mirror/$user" ] || home="$HOME/.mirror/$user"
43
+ else
44
+ return 0
45
+ fi
46
+ mkdir -p "$home" 2>/dev/null \
47
+ && printf '%s %s: %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$HOOK" "$1" >> "$home/hooks.log" 2>/dev/null
48
+ }
49
+
50
+ ENTRY="$REPO_ROOT/bin/mirror-hook.js"
51
+
52
+ # Find Node. /usr/bin/python3 exists on every macOS; node usually lives under
53
+ # nvm or Homebrew and is NOT on the PATH of a GUI-launched runtime. The Python
54
+ # hooks could end in `|| true` because they always found their interpreter;
55
+ # the same silence here would hide a new failure — no logging, no inject, no
56
+ # close tail — that a user would meet weeks later as "Mirror stopped
57
+ # remembering". So an unresolvable Node is RECORDED, never swallowed. The
58
+ # directory the entry was found in is searched first: an npm global bin holds
59
+ # the `node` that installed the package beside `mirror-hook`, while a system
60
+ # `/usr/local/bin/node` can be years older (CI's Ubuntu runner has one).
61
+ NODE="${MIRROR_NODE:-}"
62
+ if [ -z "$NODE" ] || [ ! -x "$NODE" ]; then
63
+ NODE="$(command -v node 2>/dev/null || true)"
64
+ fi
65
+ for candidate in "$(dirname "$ENTRY")/node" "$HOME/.nvm/current/bin/node" /opt/homebrew/bin/node /usr/local/bin/node; do
66
+ [ -n "$NODE" ] && break
67
+ [ -x "$candidate" ] && NODE="$candidate"
68
+ done
69
+
70
+ if [ -z "$NODE" ]; then
71
+ note "node not found on PATH; hook skipped. Set MIRROR_NODE."
72
+ exit 0
73
+ fi
74
+
75
+ # Not `exec`: the entry exits 0 whatever happens inside it, so a non-zero exit
76
+ # means Node itself could not run the hook -- too old for the loader shim, or
77
+ # it crashed. That used to vanish into a stderr no runtime shows (TS5 handoff
78
+ # review, finding N1). No flags: the entry reads configuration and silences
79
+ # its own warnings (US3, D3).
80
+ "$NODE" "$ENTRY" "$HOOK" "$@"
81
+ status=$?
82
+ if [ "$status" -ne 0 ]; then
83
+ note "node exited $status before the hook finished ($NODE, $("$NODE" --version 2>/dev/null || echo 'version unknown')); Mirror needs Node 24 or later. Set MIRROR_NODE."
84
+ fi
85
+ exit 0
@@ -0,0 +1,561 @@
1
+ /**
2
+ * Mirror Logger Extension
3
+ *
4
+ * Integrates Pi with the Mirror Mind memory system.
5
+ *
6
+ * Events handled:
7
+ * - session_start → unmute + close stale orphans + extract pending memories
8
+ * - before_agent_start → log user prompt with explicit session id
9
+ * - agent_end → log assistant response (all messages in the turn)
10
+ * - session_shutdown → close conversation + backup database
11
+ *
12
+ * All heavy logic lives in the TypeScript core, reached through the `mirror`
13
+ * bin that ships beside this file (`bin/mirror.js`). This extension is a thin
14
+ * dispatcher. Failures are swallowed to never block Pi — but logged to
15
+ * mirror-logger.log in the resolved mirror home (homes root only as bootstrap
16
+ * fallback when no mirror home is resolvable).
17
+ *
18
+ * Since CV22.DS10.US3 this extension assumes nothing about the cwd: the core it
19
+ * runs is the one it ships with, located from this file (D6); configuration
20
+ * is the core's own (D3); and for a session Pi did not open inside the tree,
21
+ * it supplies the Operating Instructions (`AGENTS.md`) itself (D13), and says
22
+ * so once when `mirror` is not on the PATH.
23
+ *
24
+ * External skill prep note:
25
+ * Pi reads installed external skills from
26
+ * ~/.mirror-minds/<user>/runtime/skills/pi/extensions.json
27
+ * rather than from source manifests under ~/.mirror-minds/<user>/extensions/.
28
+ */
29
+
30
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
31
+ import { accessSync, appendFileSync, closeSync, constants, existsSync, mkdirSync, openSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
32
+ import { homedir } from "node:os";
33
+ import { delimiter, dirname, join } from "node:path";
34
+ import { fileURLToPath } from "node:url";
35
+ import { spawn } from "node:child_process";
36
+
37
+ /** The tree this extension ships in: `.pi/extensions/` sits two below the root. */
38
+ const TREE_ROOT = fileURLToPath(new URL("../../", import.meta.url)).replace(/\/$/, "");
39
+ /** The core this extension runs: the bin beside it, never one found by the cwd (D6). */
40
+ const MIRROR_BIN = join(TREE_ROOT, "bin", "mirror.js");
41
+ /** The Operating Instructions that ship with the core (D13). */
42
+ const AGENTS_FILE = join(TREE_ROOT, "AGENTS.md");
43
+
44
+ // Mirror home directory names. Mirrors the Python core contract in
45
+ // src/memory/config.py (_DEFAULT_USER_HOMES_DIR_NAME / _LEGACY_USER_HOMES_DIR_NAME).
46
+ // The core migrated the default homes root from ~/.mirror to ~/.mirror-minds; the
47
+ // legacy layout stays supported indefinitely. This extension is TypeScript and cannot
48
+ // import the Python constants, so it duplicates the contract — keep the names here.
49
+ const NEW_HOMES_DIR = ".mirror-minds";
50
+ const LEGACY_HOMES_DIR = ".mirror";
51
+
52
+ // Respect MEMORY_DIR so Pi session files land in the same directory Python reads.
53
+ // Fallback to ~/.mirror-minds if unset (matches config.DEFAULT_MEMORY_DIR).
54
+ function _resolveMemoryDir(): string {
55
+ // CV9.E2.S6 — log containment. Precedence mirrors the Python core:
56
+ // explicit MEMORY_DIR > resolved mirror home (shell env > .env, with the
57
+ // legacy ~/.mirror/<user> location honored when only it exists) > homes
58
+ // root. The homes-root fallback is deliberate: it is the bootstrap error
59
+ // channel that records "could not resolve the mirror home" itself.
60
+ const raw = process.env.MEMORY_DIR;
61
+ if (raw) return raw.startsWith("~") ? join(homedir(), raw.slice(2)) : raw;
62
+ const { home, user } = _effectiveMirrorEnv();
63
+ if (home) return home.startsWith("~") ? join(homedir(), home.slice(2)) : home;
64
+ if (user) {
65
+ const newHome = join(homedir(), NEW_HOMES_DIR, user);
66
+ const legacyHome = join(homedir(), LEGACY_HOMES_DIR, user);
67
+ if (!_isDir(newHome) && _isDir(legacyHome)) return legacyHome;
68
+ return newHome;
69
+ }
70
+ return join(homedir(), NEW_HOMES_DIR);
71
+ }
72
+
73
+ /** True when the path is an existing directory. */
74
+ function _isDir(path: string): boolean {
75
+ try {
76
+ return statSync(path).isDirectory();
77
+ } catch {
78
+ return false;
79
+ }
80
+ }
81
+
82
+ /** True when a user home has an installed Pi external-skill catalog. */
83
+ function _hasPiCatalog(userHome: string): boolean {
84
+ try {
85
+ return existsSync(join(userHome, "runtime", "skills", "pi", "extensions.json"));
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+
91
+ /** User homes under a homes root that carry an installed Pi external-skill catalog. */
92
+ function _piCatalogHomes(homesDirName: string): string[] {
93
+ const root = join(homedir(), homesDirName);
94
+ try {
95
+ return readdirSync(root)
96
+ .map((name) => join(root, name))
97
+ .filter((path) => _isDir(path) && _hasPiCatalog(path));
98
+ } catch {
99
+ return [];
100
+ }
101
+ }
102
+
103
+ // One KEY=VALUE file, as the core reads it (no quote stripping, comments
104
+ // skipped). Pi (Node) never loads .env on its own, so without this the
105
+ // extension cannot see the MIRROR_USER the core will resolve.
106
+ function _readEnvFile(path: string): Record<string, string> {
107
+ try {
108
+ if (!statSync(path).isFile()) return {};
109
+ const out: Record<string, string> = {};
110
+ for (const rawLine of readFileSync(path, "utf-8").split(/\r?\n/)) {
111
+ const line = rawLine.trim();
112
+ if (!line || line.startsWith("#") || !line.includes("=")) continue;
113
+ const eq = line.indexOf("=");
114
+ const key = line.slice(0, eq).trim();
115
+ if (key) out[key] = line.slice(eq + 1).trim();
116
+ }
117
+ return out;
118
+ } catch {
119
+ return {};
120
+ }
121
+ }
122
+
123
+ /** The user's config file, where the core reads it: `${XDG_CONFIG_HOME:-~/.config}/mirror/env`. */
124
+ function _userConfigFile(): string {
125
+ const xdg = process.env.XDG_CONFIG_HOME;
126
+ return join(xdg && xdg.length > 0 ? xdg : join(homedir(), ".config"), "mirror", "env");
127
+ }
128
+
129
+ // Effective Mirror env, in the core's own order (CV22.DS10.US3 D3): the real
130
+ // shell env wins; then the TREE's `.env` -- a clone's, located from this file,
131
+ // never the cwd's; then the user's config file. The core resolves the database
132
+ // the same way, so the two cannot disagree about which mirror a session logs to.
133
+ function _effectiveMirrorEnv(): { home?: string; user?: string } {
134
+ const fromFiles = { ..._readEnvFile(_userConfigFile()), ..._readEnvFile(join(TREE_ROOT, ".env")) };
135
+ const rawHome = "MIRROR_HOME" in process.env ? process.env.MIRROR_HOME : fromFiles.MIRROR_HOME;
136
+ const rawUser = "MIRROR_USER" in process.env ? process.env.MIRROR_USER : fromFiles.MIRROR_USER;
137
+ return { home: rawHome?.trim() || undefined, user: rawUser?.trim() || undefined };
138
+ }
139
+
140
+ /**
141
+ * Where `mirror` resolves on this PATH, or null. The skills say `mirror`
142
+ * (CV22.DS10.US3 D7), so an agent in a session where it does not resolve
143
+ * meets `command not found` and improvises -- which is why session start says
144
+ * so once, naming the fix, instead of leaving the agent to guess.
145
+ */
146
+ function _mirrorOnPath(): string | null {
147
+ const dirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
148
+ for (const dir of dirs) {
149
+ const candidate = join(dir, process.platform === "win32" ? "mirror.cmd" : "mirror");
150
+ try {
151
+ accessSync(candidate, constants.X_OK);
152
+ return candidate;
153
+ } catch {
154
+ // keep looking
155
+ }
156
+ }
157
+ return null;
158
+ }
159
+
160
+ /** True when `path` is the same file as this tree's AGENTS.md (symlinks resolved). */
161
+ function _isOurAgentsFile(path: string): boolean {
162
+ try {
163
+ return realpathSync(path) === realpathSync(AGENTS_FILE);
164
+ } catch {
165
+ return false;
166
+ }
167
+ }
168
+
169
+ const MIRROR_DIR = _resolveMemoryDir();
170
+ const LOG_FILE = join(MIRROR_DIR, "mirror-logger.log");
171
+
172
+ // Content size limit for CLI arguments (~50KB, safe for macOS ARG_MAX)
173
+ const MAX_CONTENT_SIZE = 50_000;
174
+
175
+ type RuntimeCatalogEntry = {
176
+ id?: string;
177
+ command_name?: string;
178
+ installed_skill_path?: string;
179
+ };
180
+
181
+ type RuntimeCatalog = {
182
+ schema_version?: string;
183
+ runtime?: string;
184
+ target_root?: string;
185
+ generated_at?: string;
186
+ extensions?: RuntimeCatalogEntry[];
187
+ };
188
+
189
+ type MirrorStatusContext = {
190
+ hasUI: boolean;
191
+ sessionManager: {
192
+ getSessionFile(): string | undefined;
193
+ };
194
+ ui: {
195
+ setStatus(key: string, value: string | undefined): void;
196
+ };
197
+ };
198
+
199
+ /**
200
+ * One registration per Pi process. Pi can reach this file twice -- as the
201
+ * project's `.pi/extensions/` inside the checkout and as a personal package
202
+ * installed from `npm root -g`, which `npm link` makes the same file under
203
+ * two paths -- and two registrations would log every turn twice. The guard is
204
+ * process-scoped, not module-scoped, because two paths are two module
205
+ * instances (CV22.DS10.US3 plateau 2, the quality-assurance finding).
206
+ */
207
+ const LOADED_KEY = "__mirrorLoggerRegistered";
208
+
209
+ export default function (pi: ExtensionAPI) {
210
+ const globals = globalThis as Record<string, unknown>;
211
+ if (globals[LOADED_KEY]) {
212
+ try {
213
+ appendFileSync(LOG_FILE, `${new Date().toISOString()} [INFO] second registration skipped: ${import.meta.url}\n`);
214
+ } catch {
215
+ // never block Pi
216
+ }
217
+ return;
218
+ }
219
+ globals[LOADED_KEY] = import.meta.url;
220
+
221
+ // --- Helpers ---
222
+
223
+ /**
224
+ * Every Mirror command this extension runs enters the TypeScript front
225
+ * door through the `mirror` bin beside this file (`bin/mirror.js`), the
226
+ * same entry the skills reach through the PATH, so `routing.ts` decides
227
+ * the route and `front-door.log` records it. The bin, not `cli.ts`
228
+ * directly: under `npm root -g` Node will not strip types (D15), and the
229
+ * bin is the one entry that runs in a checkout and an install alike.
230
+ * It reads configuration and silences warnings itself (D3), so no flag
231
+ * and no `.env` path travels here. Nothing depends on the cwd.
232
+ */
233
+ const FRONT_DOOR_ARGV = [MIRROR_BIN];
234
+
235
+ function log(level: string, msg: string): void {
236
+ try {
237
+ const ts = new Date().toISOString();
238
+ mkdirSync(MIRROR_DIR, { recursive: true });
239
+ appendFileSync(LOG_FILE, `${ts} [${level}] ${msg}\n`);
240
+ } catch {
241
+ // Logging failure must never break anything
242
+ }
243
+ }
244
+
245
+ function runMirrorBackground(args: string[], label: string): void {
246
+ let logFd: number | undefined;
247
+ try {
248
+ mkdirSync(MIRROR_DIR, { recursive: true });
249
+ logFd = openSync(LOG_FILE, "a");
250
+ const child = spawn("node", [...FRONT_DOOR_ARGV, ...args], {
251
+ cwd: process.cwd(),
252
+ stdio: ["ignore", logFd, logFd],
253
+ // Windows: a detached console child materializes as a visible
254
+ // console (or a Windows Terminal tab, when WT is the default
255
+ // terminal, which does not honor CREATE_NO_WINDOW for detached
256
+ // sessions). Sharing Pi's console makes it truly invisible.
257
+ // POSIX keeps detached so the write survives Pi exiting.
258
+ detached: process.platform !== "win32",
259
+ windowsHide: true,
260
+ });
261
+ child.unref();
262
+ log("INFO", `${label} started in detached background process ${child.pid ?? "(unknown pid)"}`);
263
+ } catch (err: unknown) {
264
+ const message = err instanceof Error ? err.message : String(err);
265
+ log("ERROR", `${label} failed: ${message.slice(0, 500)}`);
266
+ } finally {
267
+ if (logFd !== undefined) {
268
+ try {
269
+ closeSync(logFd);
270
+ } catch {
271
+ // Ignore close failure.
272
+ }
273
+ }
274
+ }
275
+ }
276
+
277
+ async function runMirror(args: string[]): Promise<string> {
278
+ try {
279
+ const result = await pi.exec("node", [...FRONT_DOOR_ARGV, ...args], {
280
+ timeout: 30_000,
281
+ });
282
+ const stderr = (result?.stderr ?? "").trim();
283
+ if (stderr) {
284
+ log("WARN", `stderr from [${args.slice(0, 2).join(" ")}]: ${stderr.slice(0, 500)}`);
285
+ }
286
+ return (result?.stdout ?? "").trim();
287
+ } catch (err: unknown) {
288
+ const message = err instanceof Error ? err.message : String(err);
289
+ log("ERROR", `runMirror failed [${args.slice(0, 2).join(" ")}]: ${message.slice(0, 500)}`);
290
+ return "";
291
+ }
292
+ }
293
+
294
+ /** Extract readable text from a content blocks array or plain string. */
295
+ function extractText(content: unknown): string {
296
+ if (typeof content === "string") return content;
297
+ if (!Array.isArray(content)) return "";
298
+ return content
299
+ .filter((b: Record<string, unknown>) => b && b.type === "text" && typeof b.text === "string")
300
+ .map((b: Record<string, unknown>) => b.text as string)
301
+ .join("\n");
302
+ }
303
+
304
+ /** Truncate content to fit in CLI arguments. */
305
+ function truncate(text: string): string {
306
+ if (text.length <= MAX_CONTENT_SIZE) return text;
307
+ return text.slice(0, MAX_CONTENT_SIZE) + "\n[… truncated]";
308
+ }
309
+
310
+ async function refreshMirrorStatus(ctx: MirrorStatusContext): Promise<void> {
311
+ if (!ctx.hasUI) return;
312
+ const sessionId = ctx.sessionManager.getSessionFile() ?? null;
313
+ const statusArgs = ["welcome", "--status-line"];
314
+ if (sessionId) {
315
+ statusArgs.push("--session-id", sessionId);
316
+ }
317
+ const compactStatus = (await runMirror(statusArgs)).trim();
318
+ const externalCatalog = loadInstalledPiExternalSkills();
319
+ const externalSkills = externalCatalog?.extensions ?? [];
320
+ const status = compactStatus || "◇ Mirror · ?";
321
+ ctx.ui.setStatus(
322
+ "mirror",
323
+ externalSkills.length > 0 ? `${status} · ext ${externalSkills.length}` : status,
324
+ );
325
+ }
326
+
327
+ // Resolve the active Mirror home the same way the Python core does
328
+ // (src/memory/config.py resolve_mirror_home), including the per-workspace
329
+ // .env that Node would otherwise never see.
330
+ function resolveMirrorHome(): string | null {
331
+ const { home: explicitHome, user: mirrorUser } = _effectiveMirrorEnv();
332
+
333
+ if (explicitHome) {
334
+ return explicitHome.startsWith("~") ? join(homedir(), explicitHome.slice(2)) : explicitHome;
335
+ }
336
+
337
+ // Prefer the new ~/.mirror-minds/<user> location, and fall back to the
338
+ // legacy ~/.mirror/<user> only when the new home does not exist.
339
+ if (mirrorUser) {
340
+ const newUserHome = join(homedir(), NEW_HOMES_DIR, mirrorUser);
341
+ const legacyUserHome = join(homedir(), LEGACY_HOMES_DIR, mirrorUser);
342
+ if (!_isDir(newUserHome) && _isDir(legacyUserHome)) {
343
+ return legacyUserHome;
344
+ }
345
+ return newUserHome;
346
+ }
347
+
348
+ // No explicit home/user from shell or .env: infer the active Mirror home
349
+ // from a single installed catalog. Prefer the new homes root; only fall
350
+ // back to legacy when the new root has none. Ambiguity (more than one
351
+ // candidate under a root) requires MIRROR_USER/MIRROR_HOME — never guess.
352
+ for (const homesDir of [NEW_HOMES_DIR, LEGACY_HOMES_DIR]) {
353
+ const candidates = _piCatalogHomes(homesDir);
354
+ if (candidates.length === 1) return candidates[0];
355
+ if (candidates.length > 1) {
356
+ log(
357
+ "WARN",
358
+ `multiple Mirror homes with Pi external skill catalogs under ${homesDir}; set MIRROR_USER or MIRROR_HOME`,
359
+ );
360
+ return null;
361
+ }
362
+ }
363
+ return null;
364
+ }
365
+
366
+ function loadInstalledPiExternalSkills(): RuntimeCatalog | null {
367
+ try {
368
+ const mirrorHome = resolveMirrorHome();
369
+ if (!mirrorHome) return null;
370
+ const catalogPath = join(mirrorHome, "runtime", "skills", "pi", "extensions.json");
371
+ if (!existsSync(catalogPath)) return null;
372
+
373
+ const raw = readFileSync(catalogPath, "utf-8");
374
+ const data = JSON.parse(raw) as RuntimeCatalog;
375
+ if (data.schema_version !== "1") {
376
+ log("WARN", `unsupported Pi external skill catalog schema: ${String(data.schema_version ?? "(missing)")}`);
377
+ return null;
378
+ }
379
+ if (data.runtime !== "pi") {
380
+ log("WARN", `unexpected Pi external skill catalog runtime: ${String(data.runtime ?? "(missing)")}`);
381
+ return null;
382
+ }
383
+ if (!Array.isArray(data.extensions)) {
384
+ log("WARN", "invalid Pi external skill catalog: extensions must be an array");
385
+ return null;
386
+ }
387
+ return data;
388
+ } catch (err: unknown) {
389
+ const message = err instanceof Error ? err.message : String(err);
390
+ log("WARN", `failed to load Pi external skill catalog: ${message.slice(0, 500)}`);
391
+ return null;
392
+ }
393
+ }
394
+
395
+ function getInstalledPiSkillPaths(): string[] {
396
+ const catalog = loadInstalledPiExternalSkills();
397
+ const items = catalog?.extensions ?? [];
398
+ const skillPaths = items
399
+ .map((item) => item.installed_skill_path)
400
+ .filter((path): path is string => typeof path === "string" && path.length > 0)
401
+ .filter((path) => existsSync(path));
402
+ return [...new Set(skillPaths)];
403
+ }
404
+
405
+ // --- dynamic resources → installed external Pi skills ---
406
+
407
+ pi.on("resources_discover", async () => {
408
+ const skillPaths = getInstalledPiSkillPaths();
409
+ if (skillPaths.length > 0) {
410
+ log("INFO", `resources_discover: loaded ${skillPaths.length} installed Pi external skill(s)`);
411
+ }
412
+ return { skillPaths };
413
+ });
414
+
415
+ // --- 0. the Operating Instructions, for a session Pi did not open in the tree ---
416
+ //
417
+ // Pi loads one context file per directory from the cwd upward. Inside the
418
+ // checkout that is this tree's AGENTS.md; from anywhere else -- an npm
419
+ // user's project, /tmp -- it is not, and a session that answers /mm-mirror
420
+ // without the modes, the persona signature, or the Builder boundary is not
421
+ // a Mirror session (CV22.DS10.US3 D13). So the extension appends the file
422
+ // it ships with, unless Pi already loaded that very file.
423
+
424
+ pi.on("before_agent_start", async (event) => {
425
+ const files = event.systemPromptOptions.contextFiles;
426
+ if (files.some((file) => _isOurAgentsFile(file.path))) return;
427
+ let content: string;
428
+ try {
429
+ content = readFileSync(AGENTS_FILE, "utf-8");
430
+ } catch (err: unknown) {
431
+ log("WARN", `AGENTS.md not readable at ${AGENTS_FILE}: ${err instanceof Error ? err.message : String(err)}`);
432
+ return;
433
+ }
434
+ if (files.some((file) => file.content === content)) return;
435
+ files.push({ path: AGENTS_FILE, content });
436
+ log("INFO", `operating instructions appended from ${AGENTS_FILE}`);
437
+ });
438
+
439
+ // --- 1. session_start → unmute + close stale orphans + extract pending ---
440
+
441
+ pi.on("session_start", async (_event, ctx) => {
442
+ log("INFO", "session_start fired");
443
+ if (ctx.hasUI) {
444
+ ctx.ui.setStatus("mirror", "◇ Mirror · starting… maintenance will continue in background");
445
+ }
446
+ // The skills say `mirror`. Say once, visibly, when that will not work.
447
+ if (_mirrorOnPath() === null) {
448
+ const fix = existsSync(join(TREE_ROOT, ".git"))
449
+ ? `run \`npm link\` once in ${TREE_ROOT}`
450
+ : "add `$(npm prefix -g)/bin` to the PATH";
451
+ const line = `Mirror: \`mirror\` is not on the PATH — ${fix}. Skills will fail until it is.`;
452
+ log("WARN", line);
453
+ if (ctx.hasUI) ctx.ui.notify(line, "warning");
454
+ }
455
+ const summary = await runMirror(["conversation-logger", "session-start", "--fast"]);
456
+ if (ctx.hasUI) {
457
+ ctx.ui.setStatus("mirror", "◇ Mirror · checking release status…");
458
+ }
459
+ const externalCatalog = loadInstalledPiExternalSkills();
460
+ const externalSkills = externalCatalog?.extensions ?? [];
461
+ const externalSkillSummary = externalSkills.length
462
+ ? `External skills: ${externalSkills.map((item) => item.command_name ?? item.id ?? "(unknown)").join(", ")}`
463
+ : "External skills: none";
464
+ log("INFO", `session-start result: ${summary || "(empty)"}`);
465
+ log("INFO", externalSkillSummary);
466
+
467
+ const welcome = (await runMirror(["welcome"])).trim();
468
+ if (welcome) {
469
+ log("INFO", `welcome: ${welcome.split("\n")[0]}`);
470
+ }
471
+
472
+ if (ctx.hasUI) {
473
+ if (welcome) {
474
+ ctx.ui.notify(welcome, "info");
475
+ }
476
+ await refreshMirrorStatus(ctx);
477
+ runMirrorBackground(["conversation-logger", "session-maintenance"], "session-maintenance");
478
+ } else {
479
+ runMirrorBackground(["conversation-logger", "session-maintenance"], "session-maintenance");
480
+ }
481
+ });
482
+
483
+ // --- 2. before_agent_start → log user prompt with explicit session id ---
484
+
485
+ pi.on("before_agent_start", async (event, ctx) => {
486
+ const sessionId = ctx.sessionManager.getSessionFile() ?? null;
487
+ if (!sessionId) return;
488
+
489
+ const prompt = event.prompt ?? "";
490
+ if (!prompt || prompt.startsWith("/")) return;
491
+
492
+ log("INFO", `log-user: ${prompt.slice(0, 80)}...`);
493
+ await runMirror([
494
+ "conversation-logger",
495
+ "log-user",
496
+ sessionId,
497
+ truncate(prompt),
498
+ "--interface",
499
+ "pi",
500
+ ]);
501
+ });
502
+
503
+ // --- 3. agent_end → log assistant response ---
504
+ //
505
+ // agent_end fires once per user prompt, with ALL messages in the cycle
506
+ // (assistant + tool calls + tool results). Extract only assistant text
507
+ // and log as a single consolidated message.
508
+
509
+ pi.on("agent_end", async (event, ctx) => {
510
+ const sessionId = ctx.sessionManager.getSessionFile() ?? null;
511
+ if (!sessionId) return;
512
+
513
+ const messages = (event as unknown as Record<string, unknown>).messages;
514
+ if (!Array.isArray(messages) || messages.length === 0) return;
515
+
516
+ const assistantTexts: string[] = [];
517
+ for (const msg of messages) {
518
+ if (
519
+ msg &&
520
+ typeof msg === "object" &&
521
+ "role" in msg &&
522
+ (msg as Record<string, unknown>).role === "assistant"
523
+ ) {
524
+ const text = extractText((msg as Record<string, unknown>).content);
525
+ if (text.trim()) {
526
+ assistantTexts.push(text);
527
+ }
528
+ }
529
+ }
530
+
531
+ if (assistantTexts.length === 0) return;
532
+
533
+ log("INFO", `log-assistant: ${assistantTexts.length} block(s), ${assistantTexts.join("").length} chars`);
534
+
535
+ const combined = assistantTexts.join("\n\n---\n\n");
536
+ const content = truncate(combined);
537
+
538
+ runMirrorBackground(
539
+ ["conversation-logger", "log-assistant", sessionId, content, "--interface", "pi"],
540
+ "log-assistant",
541
+ );
542
+ await refreshMirrorStatus(ctx);
543
+ });
544
+
545
+ // --- 4. session_shutdown → close conversation + backup ---
546
+ //
547
+ // Uses extract=False because extraction calls the LLM and can take 30s+.
548
+ // Extraction happens at the next session_start via extract_pending.
549
+
550
+ pi.on("session_shutdown", async (_event, ctx) => {
551
+ const sessionId = ctx.sessionManager.getSessionFile() ?? null;
552
+
553
+ if (sessionId) {
554
+ await runMirror(["conversation-logger", "session-end-pi", sessionId]);
555
+ log("INFO", `session closed: ${sessionId}`);
556
+ }
557
+
558
+ // The dated zip backup answers from TypeScript (CV22.DS7.TS1).
559
+ await runMirror(["backup", "--silent"]);
560
+ });
561
+ }
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: "mm-backup"
3
+ description: Backs up the production memory database
4
+ user-invocable: true
5
+ ---
6
+
7
+ # Backup
8
+
9
+ When receiving `/mm-backup`: answered by the TS core (CV22.DS7.TS1).
10
+
11
+ ```bash
12
+ mirror backup
13
+ ```
14
+
15
+ The backup succeeded only when the command exits 0 and prints a
16
+ `Backup created:` line. Then tell the user: "Memory database backed up." and show
17
+ that line.
18
+
19
+ If the command exits non-zero, or prints a line starting with
20
+ `backup: no archive was written:`, the backup failed. Tell the user so, quote
21
+ that line, and never say the database was backed up.