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,110 @@
1
+ // Front-door `tasks import|sync|sync-config` write handlers (CV22.DS7.US2
2
+ // slice 3c). The testable core of routing these subcommands to the TS core,
3
+ // mirroring `memory.cli.tasks_cmd`'s `cmd_import`/`cmd_sync`/`cmd_sync_config`.
4
+
5
+ import { existsSync } from "node:fs";
6
+ import type { Database, WritableDatabase } from "#db/database.ts";
7
+ import { listIdentityByLayer } from "#identity/identityRead.ts";
8
+ import { getSyncFile, JOURNEY_LAYER, setSyncFile } from "#journey/journeySyncFile.ts";
9
+ import {
10
+ importTasksFromJourneyPath,
11
+ type SyncResult,
12
+ syncTasksFromFile,
13
+ } from "#tasks/taskImportSync.ts";
14
+ import type { Task } from "#tasks/taskStore.ts";
15
+ import { normalizeProjectPath } from "#util/paths.ts";
16
+
17
+ /** Journeys to operate on for `tasks import [journey]`: the explicit one, or every known journey (key order). */
18
+ export function resolveImportJourneys(db: Database, explicitJourney: string | null): string[] {
19
+ if (explicitJourney) return [explicitJourney];
20
+ return listIdentityByLayer(db, JOURNEY_LAYER).map((row) => row.key);
21
+ }
22
+
23
+ /** One journey's import result, included in rendering only when it created at least one task. */
24
+ export interface ImportJourneyResult {
25
+ journey: string;
26
+ created: Task[];
27
+ }
28
+
29
+ /**
30
+ * `tasks import [journey]`: import pending tasks for each resolved journey.
31
+ * Matches Python's `if created:` filter -- a journey with zero imports never
32
+ * appears in the render, but still contributes to nothing (not an error).
33
+ */
34
+ export function applyTasksImport(
35
+ db: WritableDatabase,
36
+ explicitJourney: string | null,
37
+ ): ImportJourneyResult[] {
38
+ const journeys = resolveImportJourneys(db, explicitJourney);
39
+ const results: ImportJourneyResult[] = [];
40
+ for (const journey of journeys) {
41
+ const created = importTasksFromJourneyPath(db, journey);
42
+ if (created.length > 0) {
43
+ results.push({ journey, created });
44
+ }
45
+ }
46
+ return results;
47
+ }
48
+
49
+ /** Journeys to operate on for `tasks sync [journey]`: the explicit one (regardless of sync-file config), or every journey WITH a sync file configured. */
50
+ export function resolveSyncJourneys(db: Database, explicitJourney: string | null): string[] {
51
+ if (explicitJourney) return [explicitJourney];
52
+ return listIdentityByLayer(db, JOURNEY_LAYER)
53
+ .map((row) => row.key)
54
+ .filter((key) => getSyncFile(db, key) !== null);
55
+ }
56
+
57
+ export type SyncJourneyOutcome =
58
+ | { kind: "no_sync_file"; journey: string }
59
+ | { kind: "synced"; journey: string; syncFile: string; result: SyncResult }
60
+ | { kind: "error"; journey: string; message: string };
61
+
62
+ /**
63
+ * `tasks sync`'s per-journey step: re-checks the sync file (an explicit
64
+ * journey may have none configured -- Python's own per-iteration check, not a
65
+ * whole-command precondition), then syncs, catching ANY error the same way
66
+ * Python's two `except` clauses collapse to the identical print.
67
+ */
68
+ export function applyTasksSyncForJourney(
69
+ db: WritableDatabase,
70
+ journey: string,
71
+ ): SyncJourneyOutcome {
72
+ const syncFile = getSyncFile(db, journey);
73
+ if (!syncFile) return { kind: "no_sync_file", journey };
74
+ try {
75
+ const result = syncTasksFromFile(db, journey);
76
+ return { kind: "synced", journey, syncFile, result };
77
+ } catch (error) {
78
+ return {
79
+ kind: "error",
80
+ journey,
81
+ message: error instanceof Error ? error.message : String(error),
82
+ };
83
+ }
84
+ }
85
+
86
+ export interface SyncConfigOutcome {
87
+ journey: string;
88
+ resolvedPath: string;
89
+ fileExisted: boolean;
90
+ }
91
+
92
+ /**
93
+ * `tasks sync-config <journey> <file>`: normalize the path (the same
94
+ * expanduser+resolve `normalizeProjectPath` already ports for `journey
95
+ * set-path`), record whether it currently exists (a warning, not a failure),
96
+ * and configure it. Throws `JourneyNotFoundError` for an unknown journey --
97
+ * Python's `cmd_sync_config` does not catch this either (see
98
+ * `journeySyncFile.ts`'s module doc).
99
+ */
100
+ export function applyTasksSyncConfig(
101
+ db: WritableDatabase,
102
+ journey: string,
103
+ rawFilePath: string,
104
+ nowIso: string,
105
+ ): SyncConfigOutcome {
106
+ const resolvedPath = normalizeProjectPath(rawFilePath);
107
+ const fileExisted = existsSync(resolvedPath);
108
+ setSyncFile(db, journey, resolvedPath, nowIso);
109
+ return { journey, resolvedPath, fileExisted };
110
+ }
@@ -0,0 +1,101 @@
1
+ // Front-door `tasks add|done|doing|block|delete` write handlers (CV22.DS7.US2
2
+ // slice 3a). The testable core of routing these subcommands to the TS core:
3
+ // each function takes an already-open `WritableDatabase` plus injected
4
+ // id/now (the DS4 determinism-injection idiom) and returns a result the
5
+ // `render/tasks.ts` module turns into Python-identical stdout.
6
+ //
7
+ // `tasks import`/`sync`/`sync-config` (which also depend on the journey
8
+ // sync-file/project-path metadata subsystem) and `week plan`/`save` (LLM-gated,
9
+ // reassigned to US5) are NOT here -- see the plan's slice breakdown.
10
+
11
+ import type { WritableDatabase } from "#db/database.ts";
12
+ import {
13
+ completeTask,
14
+ createTask,
15
+ deleteTaskById,
16
+ resolveTaskByIdOrPrefix,
17
+ type Task,
18
+ updateTaskStatus,
19
+ } from "#tasks/taskStore.ts";
20
+
21
+ /** Fields accepted by `tasks add`, mirroring the Python CLI's flags. */
22
+ export interface AddTaskParams {
23
+ title: string;
24
+ journey?: string | null;
25
+ due?: string | null;
26
+ stage?: string | null;
27
+ }
28
+
29
+ /** `tasks add`: create the task and return it for rendering. */
30
+ export function applyTasksAdd(
31
+ db: WritableDatabase,
32
+ params: AddTaskParams,
33
+ id: string,
34
+ nowIso: string,
35
+ ): Task {
36
+ return createTask(
37
+ db,
38
+ { title: params.title, journey: params.journey, dueDate: params.due, stage: params.stage },
39
+ id,
40
+ nowIso,
41
+ );
42
+ }
43
+
44
+ /** The Python-visible status a `done|doing|block` command applies. */
45
+ export type TaskStatusTarget = "doing" | "blocked" | "done";
46
+
47
+ export type StatusChangeOutcome =
48
+ | { kind: "changed"; task: Task; newStatus: TaskStatusTarget }
49
+ | { kind: "ambiguous"; idOrPrefix: string; matches: Task[] }
50
+ | { kind: "not_found"; idOrPrefix: string };
51
+
52
+ /**
53
+ * `tasks done|doing|block`: resolve the id/prefix via the ONE shared resolver,
54
+ * apply the status change, and report the outcome. `done` completes the task
55
+ * (status + completed_at); `doing`/`blocked` only change status -- matching
56
+ * Python's `if new_status == "done": complete_task(...) else: update_task(...)`.
57
+ */
58
+ export function applyTasksStatusChange(
59
+ db: WritableDatabase,
60
+ idOrPrefix: string,
61
+ newStatus: TaskStatusTarget,
62
+ nowIso: string,
63
+ ): StatusChangeOutcome {
64
+ const resolved = resolveTaskByIdOrPrefix(db, idOrPrefix);
65
+ if (resolved.kind === "not_found") return { kind: "not_found", idOrPrefix };
66
+ if (resolved.kind === "ambiguous") {
67
+ return { kind: "ambiguous", idOrPrefix, matches: resolved.matches };
68
+ }
69
+
70
+ if (newStatus === "done") {
71
+ completeTask(db, resolved.task.id, nowIso);
72
+ } else {
73
+ updateTaskStatus(db, resolved.task.id, newStatus, nowIso);
74
+ }
75
+
76
+ const task: Task = {
77
+ ...resolved.task,
78
+ status: newStatus,
79
+ updated_at: nowIso,
80
+ completed_at: newStatus === "done" ? nowIso : resolved.task.completed_at,
81
+ };
82
+ return { kind: "changed", task, newStatus };
83
+ }
84
+
85
+ export type DeleteOutcome =
86
+ | { kind: "deleted"; task: Task }
87
+ | { kind: "not_found"; idOrPrefix: string };
88
+
89
+ /**
90
+ * `tasks delete`: resolve via the SAME shared resolver as status-change, but
91
+ * fold "ambiguous" into "not_found" -- Python's `cmd_delete` only branches on
92
+ * `len(matches) == 1`, treating both zero AND multiple matches as "not found".
93
+ * This is the deliberate asymmetry the plan calls out; it is expressed here,
94
+ * in presentation, not inside the shared resolver.
95
+ */
96
+ export function applyTasksDelete(db: WritableDatabase, idOrPrefix: string): DeleteOutcome {
97
+ const resolved = resolveTaskByIdOrPrefix(db, idOrPrefix);
98
+ if (resolved.kind !== "found") return { kind: "not_found", idOrPrefix };
99
+ deleteTaskById(db, resolved.task.id);
100
+ return { kind: "deleted", task: resolved.task };
101
+ }
@@ -0,0 +1,176 @@
1
+ // The front door's own answer for a name it does not know (CV22.DS10.TS5, D2).
2
+ //
3
+ // Until TS5 every such name fell through to Python: an unknown command reached
4
+ // `_dispatch`'s `Unknown command` and its usage block, an unknown subcommand
5
+ // reached argparse, which names the program `__main__.py`. With the fallback
6
+ // gone nobody would own the answer, and an unowned answer is how a surface
7
+ // disappears without anyone deciding to remove it. So TypeScript owns it, in
8
+ // the shapes D2 records:
9
+ //
10
+ // * top level -- `Unknown command: <name>`, a blank line, and the usage
11
+ // block, all on stdout, exit 1: `_dispatch`'s shape, the one scripts saw.
12
+ // No command at all prints the usage block alone, exit 1.
13
+ // * a family -- argparse's shape: one `usage:` line and one `error:` line on
14
+ // stderr, exit 2, with the FAMILY as the program name instead of
15
+ // `__main__.py`. US2 gave `runtime` exactly this shape first; this module
16
+ // generalizes it, byte-for-byte.
17
+ // * `-h` / `--help` -- the usage on stdout, exit 0, at either level (the
18
+ // plateau-2 amendment to D2): help must not become an error just because
19
+ // the program that used to print it is gone.
20
+ //
21
+ // Seven families answered an unknown subcommand differently in the oracle
22
+ // (inventory F6): a usage line on stdout with exit 1, `Unknown subcommand` with
23
+ // exit 1, or -- `conversation-logger` -- nothing at all with exit 0. They move
24
+ // to the uniform shape deliberately; the table is in the story's inventory.
25
+
26
+ import { PROGRAM } from "#util/program.ts";
27
+
28
+ /** The program name the top-level usage prints: `#util/program.ts`, the one place US3 renames. */
29
+ export { PROGRAM };
30
+
31
+ /**
32
+ * Every top-level command the front door answers, in the oracle's order, with a
33
+ * one-line summary. The unknown-command answer prints this; a test proves every
34
+ * name here routes somewhere other than the unknown-command answer.
35
+ */
36
+ export const FRONT_DOOR_COMMANDS: ReadonlyArray<readonly [name: string, summary: string]> = [
37
+ ["init", "Initialize a user home from identity templates"],
38
+ ["seed", "Seed identity YAML files into the database"],
39
+ ["identity", "Read and update identity in the database"],
40
+ ["list", "List personas, journeys, extensions, or all"],
41
+ ["inspect", "Inspect a persona, an extension, the runtime catalog, or the call ledgers"],
42
+ ["detect-persona", "Show persona routing matches for a query"],
43
+ ["extensions", "List, validate, sync, install, or uninstall extensions"],
44
+ ["ext", "Run a command-skill extension's CLI"],
45
+ ["mirror", "Mirror Mode: load context, log, deactivate"],
46
+ ["mode", "Operating mode lifecycle"],
47
+ ["conversation-logger", "Conversation logging"],
48
+ ["backup", "Create a zipped backup of the memory database"],
49
+ ["repair-encoding", "Repair reversible UTF-8 mojibake in user text"],
50
+ ["journal", "Record a journal entry"],
51
+ ["journey", "Inspect or update a journey"],
52
+ ["build", "Builder Mode and the Ariad lifecycle"],
53
+ ["explore", "Explorer Mode"],
54
+ ["soul", "Soul Mode"],
55
+ ["memories", "List or search memories"],
56
+ ["conversations", "List conversations, or append a message batch"],
57
+ ["recall", "Load messages from a previous conversation"],
58
+ ["tasks", "Task management"],
59
+ ["week", "Weekly planning"],
60
+ ["journeys", "List journeys with status and stage"],
61
+ ["consult", "Ask other LLMs through OpenRouter"],
62
+ ["descriptor", "Generate and list routing descriptors"],
63
+ ["consolidate", "Scan memories for patterns and manage consolidation proposals"],
64
+ ["shadow", "Surface and promote shadow-layer observations"],
65
+ ["mcp", "Run the Mirror MCP server over stdio"],
66
+ ["runtime", "Runtime status, drift, backups, version, and updates"],
67
+ ["welcome", "Render the welcome card"],
68
+ ];
69
+
70
+ const NAME_WIDTH = Math.max(...FRONT_DOOR_COMMANDS.map(([name]) => name.length)) + 2;
71
+
72
+ /** The top-level usage block. */
73
+ export const USAGE = [
74
+ `Usage: ${PROGRAM} <command> [args]`,
75
+ "",
76
+ "Commands:",
77
+ ...FRONT_DOOR_COMMANDS.map(([name, summary]) => ` ${name.padEnd(NAME_WIDTH)}${summary}`),
78
+ "",
79
+ ].join("\n");
80
+
81
+ /** What routing hands the front door when a name is not one it answers. */
82
+ export type UsageRequest =
83
+ | {
84
+ readonly scope: "top-level";
85
+ /** The unknown name, or null when no command was given at all. */
86
+ readonly given: string | null;
87
+ }
88
+ | {
89
+ readonly scope: "family";
90
+ /** The family as a user types it: `build`, `explore story`. */
91
+ readonly program: string;
92
+ /** What the family does answer, from routing's own allowlist. */
93
+ readonly choices: readonly string[];
94
+ /** The unrecognized subcommand, or "" when none was given. */
95
+ readonly given: string;
96
+ };
97
+
98
+ export interface UsageAnswer {
99
+ readonly stream: "stdout" | "stderr";
100
+ readonly exitCode: number;
101
+ readonly text: string;
102
+ }
103
+
104
+ const HELP = new Set(["-h", "--help"]);
105
+
106
+ /** One family's `usage:` line. */
107
+ export function familyUsageLine(program: string, choices: readonly string[]): string {
108
+ return `usage: ${program} [-h] {${choices.join(",")}} ...\n`;
109
+ }
110
+
111
+ /**
112
+ * argparse's refusal, in the product's vocabulary: the usage line, then one
113
+ * `error:` line naming what was wrong.
114
+ */
115
+ export function renderUnknownSubcommand(
116
+ program: string,
117
+ choices: readonly string[],
118
+ given: string,
119
+ ): string {
120
+ const detail =
121
+ given === ""
122
+ ? "the following arguments are required: command"
123
+ : `argument command: invalid choice: '${given}' (choose from ${choices.join(", ")})`;
124
+ return `${familyUsageLine(program, choices)}${program}: error: ${detail}\n`;
125
+ }
126
+
127
+ /**
128
+ * What the front door knows about the machine when it answers bare `mirror`:
129
+ * whether any source configured a user, and where the config file would be.
130
+ */
131
+ export interface UsageContext {
132
+ readonly userConfigured: boolean;
133
+ readonly configFile: string;
134
+ }
135
+
136
+ /**
137
+ * The first seconds after `npm install -g mirror-mind` (CV22.DS10.US3): bare
138
+ * `mirror` on a machine with no user configured is an orientation, not a
139
+ * usage dump -- three lines that say what is missing, what to run, and where
140
+ * the configuration will live. With a user configured, bare `mirror` is the
141
+ * usage block, as before.
142
+ */
143
+ export function renderOrientation(configFile: string): string {
144
+ return [
145
+ "No user configured yet.",
146
+ ` Run: ${PROGRAM} init <your-name>`,
147
+ ` Configuration will live at: ${configFile}`,
148
+ "",
149
+ ].join("\n");
150
+ }
151
+
152
+ /** Render the answer for a request routing could not place. */
153
+ export function answerUsage(request: UsageRequest, context?: UsageContext): UsageAnswer {
154
+ if (request.scope === "top-level") {
155
+ if (request.given !== null && HELP.has(request.given)) {
156
+ return { stream: "stdout", exitCode: 0, text: USAGE };
157
+ }
158
+ if (request.given === null && context !== undefined && !context.userConfigured) {
159
+ return { stream: "stdout", exitCode: 1, text: renderOrientation(context.configFile) };
160
+ }
161
+ const unknown = request.given === null ? "" : `Unknown command: ${request.given}\n\n`;
162
+ return { stream: "stdout", exitCode: 1, text: `${unknown}${USAGE}` };
163
+ }
164
+ if (HELP.has(request.given)) {
165
+ return {
166
+ stream: "stdout",
167
+ exitCode: 0,
168
+ text: familyUsageLine(request.program, request.choices),
169
+ };
170
+ }
171
+ return {
172
+ stream: "stderr",
173
+ exitCode: 2,
174
+ text: renderUnknownSubcommand(request.program, request.choices, request.given),
175
+ };
176
+ }
@@ -0,0 +1,252 @@
1
+ // Generate the canonical Mirror Mind Claude plugin (manifest + skills), and the
2
+ // Claude Code copies of Pi-sourced skills.
3
+ //
4
+ // The Node port of `src/memory/plugins/claude.py` and its wrapper
5
+ // `scripts/build_claude_plugin.py` (CV22.DS10.TS5, slice B).
6
+ //
7
+ // The Zero Python gate assigns the builder to **US3**, on the grounds that US3
8
+ // decides what it builds. But it is a `.py` file, and TS5's done condition is
9
+ // that `git ls-files '*.py'` is empty -- so the file cannot survive this story
10
+ // whoever owns its content. Decision D6 resolves that: the port lands here,
11
+ // byte-identical in output; US3 still owns the shape of what it emits when the
12
+ // npm entry point becomes real.
13
+ //
14
+ // The plugin skills are generated from `.claude/skills/` (the Claude-tuned
15
+ // source), with one exception since CR102. A Pi-sourced skill
16
+ // (`piSourcedSkills.ts`) has one body in every runtime, so this module also
17
+ // generates its `.claude/skills/` copy from `.pi/skills/`, and takes its plugin
18
+ // copy from that generated content rather than from the file on disk. Hooks
19
+ // under `plugins/mirror-mind/hooks/` are hand-authored plugin source and are
20
+ // NOT managed by this module -- which is precisely why TS5's inventory had to
21
+ // find their interpreter spawns by reading them.
22
+
23
+ import {
24
+ existsSync,
25
+ mkdirSync,
26
+ readdirSync,
27
+ readFileSync,
28
+ rmdirSync,
29
+ unlinkSync,
30
+ writeFileSync,
31
+ } from "node:fs";
32
+ import { dirname, join, relative } from "node:path";
33
+
34
+ import { packageVersion } from "#runtime/version.ts";
35
+ import { claudeVariant, PI_SKILLS_DIR, PI_SOURCED_SKILLS } from "./piSourcedSkills.ts";
36
+
37
+ export const PLUGIN_NAME = "mirror-mind";
38
+ export const PLUGIN_DESCRIPTION =
39
+ "Mirror Mind — local-first memory and identity for agentic runtimes.";
40
+ export const PLUGIN_AUTHOR = "Mirror Mind";
41
+
42
+ export const SKILLS_SOURCE_DIR = ".claude/skills";
43
+ export const PLUGIN_DIR = "plugins/mirror-mind";
44
+ export const SKILL_FILENAME = "SKILL.md";
45
+
46
+ /** A file the generator owns, addressed relative to the repo root. */
47
+ export interface GeneratedFile {
48
+ readonly relativePath: string;
49
+ readonly content: string;
50
+ /** The authored file it derives from, when a person could mistake it for one. */
51
+ readonly source?: string;
52
+ }
53
+
54
+ /**
55
+ * The project version, read from source.
56
+ *
57
+ * Read from source rather than from installed metadata, so the build is
58
+ * deterministic. `packageVersion` is the single body that answers this for the
59
+ * whole core (US2 decision D2); CV22.DS10.TS5 re-pointed it at
60
+ * `package.json` and this builder followed with no edit, which is the point
61
+ * of there being one body.
62
+ */
63
+ export function readVersion(repoRoot: string): string {
64
+ const version = packageVersion(repoRoot);
65
+ if (!version) throw new Error(`version not found from ${repoRoot}`);
66
+ return version;
67
+ }
68
+
69
+ /**
70
+ * The Claude plugin manifest (minimal; no unsupported keys).
71
+ *
72
+ * Declares the Mirror MCP server so the canonical package carries it. Since
73
+ * CV22.DS9.TS2 the entry is the plugin's own launcher rather than an engine,
74
+ * so reverting never requires editing a plugin installed inside someone's
75
+ * runtime (DS9 decision D5). TS5 removes that launcher's Python branch; US3
76
+ * repoints this at the npm entry point.
77
+ */
78
+ export function buildManifest(version: string): Record<string, unknown> {
79
+ return {
80
+ name: PLUGIN_NAME,
81
+ version,
82
+ description: PLUGIN_DESCRIPTION,
83
+ author: { name: PLUGIN_AUTHOR },
84
+ mcpServers: {
85
+ // The placeholder is Claude Code's, expanded when it loads the plugin. A
86
+ // JavaScript template would resolve at BUILD time, against this
87
+ // repository, shipping an absolute path from the release machine.
88
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: Claude expands this at load time, not JS
89
+ [PLUGIN_NAME]: { command: "${CLAUDE_PLUGIN_ROOT}/mcp/launch.sh" },
90
+ },
91
+ };
92
+ }
93
+
94
+ /** The manifest as deterministic JSON text, byte-identical to Python's. */
95
+ export function manifestJson(version: string): string {
96
+ return `${JSON.stringify(buildManifest(version), null, 2)}\n`;
97
+ }
98
+
99
+ /**
100
+ * The skill markdown file, matching `skill.md` case-insensitively.
101
+ *
102
+ * Case-insensitive makes the generator robust on both case-sensitive and
103
+ * case-insensitive filesystems, including older checkouts that may still
104
+ * contain lowercase `skill.md`.
105
+ */
106
+ function findSkillMarkdown(skillDir: string): string | null {
107
+ for (const entry of readdirSync(skillDir, { withFileTypes: true, encoding: "utf8" }).sort(
108
+ (a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0),
109
+ )) {
110
+ if (entry.isFile() && entry.name.toLowerCase() === SKILL_FILENAME.toLowerCase()) {
111
+ return join(skillDir, entry.name);
112
+ }
113
+ }
114
+ return null;
115
+ }
116
+
117
+ /**
118
+ * `[skillDirName, markdownPath]` for each skill under `.claude/skills/`. Most
119
+ * are authored there. A Pi-sourced skill's file is generated instead
120
+ * (`piSourcedSkills.ts`), and `planGeneratedFiles` takes that one from its Pi
121
+ * copy, never from this path.
122
+ */
123
+ export function discoverSkillSources(repoRoot: string): [string, string][] {
124
+ const base = join(repoRoot, SKILLS_SOURCE_DIR);
125
+ const sources: [string, string][] = [];
126
+ const entries = readdirSync(base, { withFileTypes: true, encoding: "utf8" }).sort((a, b) =>
127
+ a.name < b.name ? -1 : a.name > b.name ? 1 : 0,
128
+ );
129
+ for (const child of entries) {
130
+ if (!child.isDirectory()) continue;
131
+ const markdown = findSkillMarkdown(join(base, child.name));
132
+ if (markdown === null) continue;
133
+ sources.push([child.name, markdown]);
134
+ }
135
+ return sources;
136
+ }
137
+
138
+ /**
139
+ * The `.claude/skills/` copy of each Pi-sourced skill, generated from its Pi
140
+ * copy. Refuses, before anything is written, when a listed skill has no Pi
141
+ * copy: generation would otherwise stop without a word, and the old Claude copy
142
+ * would pass for an authored one again.
143
+ */
144
+ function planPiSourcedClaudeCopies(repoRoot: string): Map<string, GeneratedFile> {
145
+ const copies = new Map<string, GeneratedFile>();
146
+ for (const skill of PI_SOURCED_SKILLS) {
147
+ const source = join(PI_SKILLS_DIR, skill, SKILL_FILENAME);
148
+ const path = join(repoRoot, source);
149
+ if (!existsSync(path)) {
150
+ throw new Error(
151
+ `${source}: missing, but ${skill} is listed as Pi-sourced in ts/src/guards/piSourcedSkills.ts`,
152
+ );
153
+ }
154
+ copies.set(skill, {
155
+ relativePath: join(SKILLS_SOURCE_DIR, skill, SKILL_FILENAME),
156
+ content: claudeVariant(skill, readFileSync(path, "utf8"), source),
157
+ source,
158
+ });
159
+ }
160
+ return copies;
161
+ }
162
+
163
+ /** Every file the generator owns, without touching the filesystem. */
164
+ export function planGeneratedFiles(repoRoot: string): GeneratedFile[] {
165
+ const version = readVersion(repoRoot);
166
+ const claudeCopies = planPiSourcedClaudeCopies(repoRoot);
167
+
168
+ const pluginSkills = new Map<string, GeneratedFile>();
169
+ for (const [name, markdown] of discoverSkillSources(repoRoot)) {
170
+ if (claudeCopies.has(name)) continue;
171
+ pluginSkills.set(name, {
172
+ relativePath: join(PLUGIN_DIR, "skills", name, SKILL_FILENAME),
173
+ content: readFileSync(markdown, "utf8"),
174
+ });
175
+ }
176
+ for (const [name, claudeCopy] of claudeCopies) {
177
+ pluginSkills.set(name, {
178
+ relativePath: join(PLUGIN_DIR, "skills", name, SKILL_FILENAME),
179
+ content: claudeCopy.content,
180
+ source: claudeCopy.source,
181
+ });
182
+ }
183
+
184
+ return [
185
+ {
186
+ relativePath: join(PLUGIN_DIR, ".claude-plugin", "plugin.json"),
187
+ content: manifestJson(version),
188
+ },
189
+ ...claudeCopies.values(),
190
+ ...[...pluginSkills].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)).map(([, file]) => file),
191
+ ];
192
+ }
193
+
194
+ /** One drift line; a derived file also names the source a person should edit. */
195
+ function driftLine(kind: "missing" | "out of date", file: GeneratedFile): string {
196
+ const from = file.source
197
+ ? ` (generated from ${file.source}: edit the source, then regenerate)`
198
+ : "";
199
+ return `${kind}: ${file.relativePath}${from}`;
200
+ }
201
+
202
+ function generatedSkillFiles(repoRoot: string): string[] {
203
+ const skillsRoot = join(repoRoot, PLUGIN_DIR, "skills");
204
+ if (!existsSync(skillsRoot)) return [];
205
+ const found: string[] = [];
206
+ for (const entry of readdirSync(skillsRoot, { withFileTypes: true, encoding: "utf8" })) {
207
+ if (!entry.isDirectory()) continue;
208
+ const candidate = join(skillsRoot, entry.name, SKILL_FILENAME);
209
+ if (existsSync(candidate)) found.push(candidate);
210
+ }
211
+ return found.sort();
212
+ }
213
+
214
+ /**
215
+ * Write every generated file, the plugin and the `.claude/skills/` copies of
216
+ * Pi-sourced skills, or report drift when `write` is false.
217
+ *
218
+ * Returns human-readable drift descriptions. In check mode the list is the
219
+ * assertion target (empty == in sync). In write mode it is empty and the files
220
+ * are written; stale generated skills are removed.
221
+ */
222
+ export function materialize(repoRoot: string, options: { write: boolean }): string[] {
223
+ const desired = planGeneratedFiles(repoRoot);
224
+ const desiredPaths = new Set(desired.map((file) => file.relativePath));
225
+ const problems: string[] = [];
226
+
227
+ for (const generated of desired) {
228
+ const target = join(repoRoot, generated.relativePath);
229
+ if (options.write) {
230
+ mkdirSync(dirname(target), { recursive: true });
231
+ writeFileSync(target, generated.content, "utf8");
232
+ } else if (!existsSync(target)) {
233
+ problems.push(driftLine("missing", generated));
234
+ } else if (readFileSync(target, "utf8") !== generated.content) {
235
+ problems.push(driftLine("out of date", generated));
236
+ }
237
+ }
238
+
239
+ for (const existing of generatedSkillFiles(repoRoot)) {
240
+ const rel = relative(repoRoot, existing);
241
+ if (desiredPaths.has(rel)) continue;
242
+ if (options.write) {
243
+ const parent = dirname(existing);
244
+ for (const leftover of readdirSync(parent).sort()) unlinkSync(join(parent, leftover));
245
+ rmdirSync(parent);
246
+ } else {
247
+ problems.push(`stale: ${rel}`);
248
+ }
249
+ }
250
+
251
+ return problems;
252
+ }