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,652 @@
1
+ // The extension catalog WRITES (CV22.DS7.TS4 plateau 5).
2
+ //
3
+ // Port of `sync_extensions_for_runtime`, `install_extension`,
4
+ // `uninstall_extension`, `expose_claude_runtime_skills`, and
5
+ // `cleanup_claude_runtime_skills` from `src/memory/cli/extensions.py`.
6
+ //
7
+ // These are the first commands in the family that touch the filesystem, and
8
+ // the corpus grades them by what they LEAVE ON DISK -- every file in the mirror
9
+ // home, the runtime target root, and the Claude project root -- not by what
10
+ // they print. Four measured facts shape the port:
11
+ //
12
+ // 1. **The catalog is written with Python's `json.dumps(doc, indent=2)`, so
13
+ // non-ASCII is ESCAPED.** A summary reading `Café notes — a fixture` lands
14
+ // as `Caf\u00e9 notes \u2014 a fixture`. `JSON.stringify` would write raw
15
+ // UTF-8 and silently change a file both cores read. And the key order is
16
+ // insertion order, not sorted.
17
+ // 2. **A Claude command name contains `:`, which is illegal on Windows**, so
18
+ // filesystem names go through `filesystemSkillDirName` while the CATALOG
19
+ // keeps the runtime command unchanged. `ext:notes` lives in `ext-notes/`.
20
+ // 3. **Install identifies an extension by its DIRECTORY NAME**, never by the
21
+ // manifest id. An extension in `ext-mismatch/` whose manifest says
22
+ // `id: mismatch` has its migrations checked against the prefix
23
+ // `ext_ext_mismatch_` and fails. Reading the id from the manifest would
24
+ // "fix" a behavior the installed world depends on.
25
+ // 4. **Install rebuilds each runtime catalog from EVERY installed
26
+ // extension**, not from the one being installed, while the printed report
27
+ // stays focused on that one. Feeding the writer a single manifest would
28
+ // drop every other extension from the catalog Pi and Claude discover
29
+ // through, leaving their SKILL.md files orphaned on disk.
30
+
31
+ import {
32
+ copyFileSync,
33
+ cpSync,
34
+ existsSync,
35
+ lstatSync,
36
+ mkdirSync,
37
+ readdirSync,
38
+ readFileSync,
39
+ realpathSync,
40
+ rmSync,
41
+ writeFileSync,
42
+ } from "node:fs";
43
+ import { basename, dirname, join } from "node:path";
44
+ import type { WritableDatabase } from "#db/database.ts";
45
+ import { PROGRAM } from "#util/program.ts";
46
+ import { pythonJsonDumpsIndentedOrdered } from "#util/pyGenerators.ts";
47
+ import type { BindingDeps } from "./bindings.ts";
48
+ import {
49
+ discoverExtensions,
50
+ extensionsRootForHome,
51
+ type RenderedCommand,
52
+ runtimeSkillsRootForHome,
53
+ } from "./catalog.ts";
54
+ import { readProvidersWithoutRuntime } from "./contextRuntime.ts";
55
+ import { readSubcommandListing } from "./dispatch.ts";
56
+ import { ExtensionError, ExtensionValidationError } from "./errors.ts";
57
+ import { type ExtensionManifest, loadExtensionManifest } from "./manifest.ts";
58
+ import { runMigrations } from "./migrations.ts";
59
+
60
+ /** Port of `_COPY_IGNORE_PATTERNS`: never shipped inside an installed tree. */
61
+ const COPY_IGNORE_EXACT = new Set([
62
+ ".git",
63
+ "__pycache__",
64
+ ".venv",
65
+ ".pytest_cache",
66
+ ".ruff_cache",
67
+ ".mypy_cache",
68
+ "node_modules",
69
+ ".DS_Store",
70
+ ]);
71
+ const COPY_IGNORE_SUFFIX = ".pyc";
72
+
73
+ // A port of Python's `re.sub(r'[<>:"/\\|?*\x00-\x1f]', '-', ...)`, the rule that
74
+ // keeps a runtime command name legal as a Windows path segment. Dropping the
75
+ // control range would let a control character reach a directory name and
76
+ // diverge from the oracle on the one platform this guard exists for.
77
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: the control range is the ported rule
78
+ const ILLEGAL_PATH_CHARACTERS = /[<>:"/\\|?*\u0000-\u001f]/g;
79
+
80
+ export interface CatalogEntry {
81
+ id: string;
82
+ name: string;
83
+ category: string;
84
+ kind: string;
85
+ summary: string;
86
+ runtime: string;
87
+ command_name: string;
88
+ source_extension_dir: string;
89
+ manifest_path: string;
90
+ source_skill_path: string;
91
+ installed_skill_path: string;
92
+ }
93
+
94
+ /** Port of `_filesystem_skill_dir_name`. */
95
+ export function filesystemSkillDirName(commandName: string): string {
96
+ const safe = pyStripSpacesAndDots(commandName.replace(ILLEGAL_PATH_CHARACTERS, "-"));
97
+ return safe || "extension";
98
+ }
99
+
100
+ /** Python's `str.strip(" .")`: both ends, any mix of the two characters. */
101
+ function pyStripSpacesAndDots(text: string): string {
102
+ return text.replace(/^[ .]+/, "").replace(/[ .]+$/, "");
103
+ }
104
+
105
+ /** Port of `_legacy_filesystem_skill_dir_names`. */
106
+ export function legacyFilesystemSkillDirNames(commandName: string): string[] {
107
+ const safe = filesystemSkillDirName(commandName);
108
+ return commandName === safe ? [] : [commandName];
109
+ }
110
+
111
+ /** Port of `_skill_markdown_declares_command`. */
112
+ function skillMarkdownDeclaresCommand(skillPath: string, commandName: string): boolean {
113
+ let content: string;
114
+ try {
115
+ content = readFileSync(skillPath, "utf8");
116
+ } catch {
117
+ return false;
118
+ }
119
+ const names = new Set([commandName, filesystemSkillDirName(commandName)]);
120
+ for (const name of names) {
121
+ for (const marker of [`name: "${name}"`, `name: '${name}'`, `name: ${name}`]) {
122
+ if (content.includes(marker)) return true;
123
+ }
124
+ }
125
+ return false;
126
+ }
127
+
128
+ /**
129
+ * Port of `_remove_legacy_skill_dir_if_owned`.
130
+ *
131
+ * Deliberately conservative, and the conservatism is the point: it deletes a
132
+ * directory named after the raw command only when the SKILL.md inside declares
133
+ * that same command. A `ext:notes/` directory a human wrote for something else
134
+ * is left alone.
135
+ */
136
+ export function removeLegacySkillDirIfOwned(root: string, commandName: string): string[] {
137
+ const removed: string[] = [];
138
+ for (const legacyName of legacyFilesystemSkillDirNames(commandName)) {
139
+ const legacyDir = join(root, legacyName);
140
+ if (!isDirectory(legacyDir)) continue;
141
+ if (!skillMarkdownDeclaresCommand(join(legacyDir, "SKILL.md"), commandName)) continue;
142
+ rmSync(legacyDir, { recursive: true, force: true });
143
+ removed.push(legacyDir);
144
+ }
145
+ return removed;
146
+ }
147
+
148
+ function isDirectory(path: string): boolean {
149
+ try {
150
+ return lstatSync(path).isDirectory();
151
+ } catch {
152
+ return false;
153
+ }
154
+ }
155
+
156
+ /** Port of `_catalog_entry_for_manifest`. */
157
+ function catalogEntryForManifest(
158
+ manifest: ExtensionManifest,
159
+ runtime: string,
160
+ targetSkillPath: string,
161
+ ): CatalogEntry {
162
+ const runtimeData = manifest.runtimes[runtime];
163
+ return {
164
+ id: manifest.id,
165
+ name: manifest.name,
166
+ category: manifest.category,
167
+ kind: manifest.kind,
168
+ summary: manifest.summary,
169
+ runtime,
170
+ command_name: runtimeData?.commandName ?? "",
171
+ source_extension_dir: manifest.root,
172
+ manifest_path: manifest.manifestPath,
173
+ source_skill_path: runtimeData?.skillPath ?? "",
174
+ installed_skill_path: targetSkillPath,
175
+ };
176
+ }
177
+
178
+ /** Port of `_write_catalog` + `_catalog_document`. */
179
+ function writeCatalog(
180
+ path: string,
181
+ runtime: string,
182
+ targetRoot: string,
183
+ items: readonly CatalogEntry[],
184
+ deps: BindingDeps,
185
+ ): void {
186
+ mkdirSync(dirname(path), { recursive: true });
187
+ const document = {
188
+ schema_version: "1",
189
+ runtime,
190
+ target_root: targetRoot,
191
+ generated_at: deps.nowIso(),
192
+ extensions: items,
193
+ };
194
+ writeFileSync(path, `${pythonJsonDumpsIndentedOrdered(document)}\n`, "utf8");
195
+ }
196
+
197
+ /** Port of `_load_catalog`: a missing or corrupt catalog is an EMPTY one. */
198
+ function loadCatalogItems(path: string): unknown[] {
199
+ if (!existsSync(path)) return [];
200
+ let data: unknown;
201
+ try {
202
+ data = JSON.parse(readFileSync(path, "utf8"));
203
+ } catch {
204
+ return [];
205
+ }
206
+ if (Array.isArray(data)) return data;
207
+ if (typeof data !== "object" || data === null) return [];
208
+ const extensions = (data as Record<string, unknown>).extensions;
209
+ return Array.isArray(extensions) ? extensions : [];
210
+ }
211
+
212
+ /** Port of `_prune_catalog_for_extension`. */
213
+ function pruneCatalogForExtension(
214
+ catalogPath: string,
215
+ extensionId: string,
216
+ runtime: string,
217
+ targetRoot: string,
218
+ deps: BindingDeps,
219
+ ): void {
220
+ const items = loadCatalogItems(catalogPath).filter(
221
+ (item) =>
222
+ typeof item === "object" &&
223
+ item !== null &&
224
+ !Array.isArray(item) &&
225
+ (item as Record<string, unknown>).id !== extensionId,
226
+ ) as CatalogEntry[];
227
+ writeCatalog(catalogPath, runtime, targetRoot, items, deps);
228
+ }
229
+
230
+ /** Port of `sync_extensions_for_runtime`. */
231
+ export function syncExtensionsForRuntime(
232
+ manifests: readonly ExtensionManifest[],
233
+ runtime: string,
234
+ targetRoot: string,
235
+ deps: BindingDeps,
236
+ ): CatalogEntry[] {
237
+ mkdirSync(targetRoot, { recursive: true });
238
+ const synced: CatalogEntry[] = [];
239
+ for (const manifest of manifests) {
240
+ const runtimeData = manifest.runtimes[runtime];
241
+ if (!runtimeData) continue;
242
+ const skillPath = runtimeData.skillPath;
243
+ if (!skillPath) continue;
244
+ const commandName = runtimeData.commandName;
245
+ const targetDir = join(targetRoot, filesystemSkillDirName(commandName));
246
+ mkdirSync(targetDir, { recursive: true });
247
+ const targetSkillPath = join(targetDir, "SKILL.md");
248
+ copyFileSync(skillPath, targetSkillPath);
249
+ removeLegacySkillDirIfOwned(targetRoot, commandName);
250
+ synced.push(catalogEntryForManifest(manifest, runtime, targetSkillPath));
251
+ }
252
+ writeCatalog(join(targetRoot, "extensions.json"), runtime, targetRoot, synced, deps);
253
+ return synced;
254
+ }
255
+
256
+ /**
257
+ * Port of `_should_copy_source_tree`.
258
+ *
259
+ * The symlink branch is a refusal, not an optimization: the production layout
260
+ * links an installed extension straight at its source repository, and
261
+ * `copytree(dirs_exist_ok=True)` writes THROUGH such a link, mutating a
262
+ * directory the user never asked to change.
263
+ */
264
+ export function shouldCopySourceTree(sourceDir: string, targetDir: string): boolean {
265
+ if (existsSync(targetDir) && realpathSync(sourceDir) === realpathSync(targetDir)) return false;
266
+ if (isSymlink(targetDir)) {
267
+ throw new ExtensionValidationError(
268
+ `installed extension path is a symlink pointing outside the install ` +
269
+ `source: ${targetDir} -> ${realpathSync(targetDir)}. Refusing to install ` +
270
+ `through it, which would modify the link target. Remove the symlink ` +
271
+ `first, or run \`${PROGRAM} ext ${basename(targetDir)} migrate\` to ` +
272
+ `re-run migrations without copying.`,
273
+ );
274
+ }
275
+ return true;
276
+ }
277
+
278
+ function isSymlink(path: string): boolean {
279
+ try {
280
+ return lstatSync(path).isSymbolicLink();
281
+ } catch {
282
+ return false;
283
+ }
284
+ }
285
+
286
+ /** Port of `shutil.copytree(..., ignore=ignore_patterns(*_COPY_IGNORE_PATTERNS))`. */
287
+ function copyTreeIgnoring(sourceDir: string, targetDir: string): void {
288
+ cpSync(sourceDir, targetDir, {
289
+ recursive: true,
290
+ force: true,
291
+ filter: (source) => {
292
+ const name = basename(source);
293
+ return !COPY_IGNORE_EXACT.has(name) && !name.endsWith(COPY_IGNORE_SUFFIX);
294
+ },
295
+ });
296
+ }
297
+
298
+ export interface InstallReport {
299
+ extensionId: string;
300
+ sourceDir: string;
301
+ installedDir: string;
302
+ synced: Array<readonly [string, CatalogEntry[]]>;
303
+ migrationsApplied: number;
304
+ /**
305
+ * Capabilities the manifest documents but declares no runtime for.
306
+ *
307
+ * Install SUCCEEDS with these present. A skill-only extension, or one being
308
+ * migrated a capability at a time, is legal -- refusing here would block the
309
+ * very incremental migration CV22.DS10.TS2 exists to enable. The report says
310
+ * so out loud instead, because the alternative is a user discovering it at
311
+ * the first refusal a week later.
312
+ */
313
+ unmigrated: string[];
314
+ }
315
+
316
+ export interface InstallOptions {
317
+ extensionId: string;
318
+ sourceRoot: string;
319
+ mirrorHome: string;
320
+ runtime: string | null;
321
+ db: WritableDatabase;
322
+ deps: BindingDeps;
323
+ }
324
+
325
+ /** Port of `install_extension` + `_post_install_command_skill`. */
326
+ export function installExtension(options: InstallOptions): InstallReport {
327
+ const { extensionId, mirrorHome, deps } = options;
328
+ const sourceDir = join(options.sourceRoot, extensionId);
329
+ loadExtensionManifest(sourceDir);
330
+
331
+ const targetExtensionsRoot = extensionsRootForHome(mirrorHome);
332
+ const targetExtensionDir = join(targetExtensionsRoot, extensionId);
333
+ mkdirSync(targetExtensionsRoot, { recursive: true });
334
+ if (shouldCopySourceTree(sourceDir, targetExtensionDir)) {
335
+ copyTreeIgnoring(sourceDir, targetExtensionDir);
336
+ }
337
+
338
+ const installedManifest = loadExtensionManifest(targetExtensionDir);
339
+ const runtimes =
340
+ options.runtime !== null ? [options.runtime] : Object.keys(installedManifest.runtimes).sort();
341
+
342
+ // A prompt-skill has no Python at all: no migrations, no register.
343
+ let migrationsApplied = 0;
344
+ if (installedManifest.kind === "command-skill") {
345
+ try {
346
+ migrationsApplied = runMigrations(
347
+ options.db,
348
+ extensionId,
349
+ join(targetExtensionDir, "migrations"),
350
+ deps,
351
+ );
352
+ } catch (error) {
353
+ if (!(error instanceof ExtensionError)) throw error;
354
+ throw new ExtensionValidationError(
355
+ `migrations failed for extension/${extensionId}: ${error.message}`,
356
+ );
357
+ }
358
+ }
359
+
360
+ // Fact 4: rebuild from everything installed, report only this one.
361
+ const installed = discoverExtensions(targetExtensionsRoot).manifests;
362
+ const synced: Array<readonly [string, CatalogEntry[]]> = [];
363
+ for (const runtimeName of runtimes) {
364
+ const runtimeTargetRoot = runtimeSkillsRootForHome(mirrorHome, runtimeName);
365
+ const all = syncExtensionsForRuntime(installed, runtimeName, runtimeTargetRoot, deps);
366
+ synced.push([runtimeName, all.filter((entry) => entry.id === extensionId)] as const);
367
+ }
368
+
369
+ return {
370
+ extensionId,
371
+ sourceDir,
372
+ installedDir: targetExtensionDir,
373
+ synced,
374
+ migrationsApplied,
375
+ unmigrated: unmigratedCapabilities(targetExtensionDir),
376
+ };
377
+ }
378
+
379
+ /**
380
+ * What still needs a runtime, in one list, labelled by kind.
381
+ *
382
+ * Each module reads its own manifest section -- `dispatch` owns
383
+ * `cli.subcommands[]`, `contextRuntime` owns `mirror_context_providers[]` --
384
+ * and install merely composes them. Putting a second manifest parser here is
385
+ * how the three readers would drift.
386
+ */
387
+ function unmigratedCapabilities(extensionDir: string): string[] {
388
+ const subcommands = readSubcommandListing(extensionDir)
389
+ .filter((entry) => !entry.hasRuntime)
390
+ .map((entry) => `subcommand '${entry.name}'`);
391
+ const providers = readProvidersWithoutRuntime(extensionDir).map(
392
+ (id) => `context provider '${id}'`,
393
+ );
394
+ return [...subcommands, ...providers];
395
+ }
396
+
397
+ export interface UninstallReport {
398
+ extensionId: string;
399
+ installedDir: string;
400
+ bindingsRemoved: number;
401
+ removed: Array<readonly [string, string[]]>;
402
+ sourceRemoved: boolean;
403
+ }
404
+
405
+ /** Port of `uninstall_extension`. */
406
+ export function uninstallExtension(options: {
407
+ extensionId: string;
408
+ mirrorHome: string;
409
+ runtime: string | null;
410
+ db: WritableDatabase;
411
+ deps: BindingDeps;
412
+ }): UninstallReport {
413
+ const { extensionId, mirrorHome, deps } = options;
414
+ const installedDir = join(extensionsRootForHome(mirrorHome), extensionId);
415
+ if (!existsSync(installedDir)) {
416
+ throw new ExtensionValidationError(`installed extension not found: ${installedDir}`);
417
+ }
418
+ const manifest = loadExtensionManifest(installedDir);
419
+ const runtimes =
420
+ options.runtime !== null ? [options.runtime] : Object.keys(manifest.runtimes).sort();
421
+
422
+ const removed: Array<readonly [string, string[]]> = [];
423
+ for (const runtimeName of runtimes) {
424
+ const runtimeData = manifest.runtimes[runtimeName];
425
+ if (!runtimeData) {
426
+ removed.push([runtimeName, []] as const);
427
+ continue;
428
+ }
429
+ const runtimeRoot = runtimeSkillsRootForHome(mirrorHome, runtimeName);
430
+ const commandName = runtimeData.commandName;
431
+ const targetDirs = [
432
+ join(runtimeRoot, filesystemSkillDirName(commandName)),
433
+ ...legacyFilesystemSkillDirNames(commandName).map((name) => join(runtimeRoot, name)),
434
+ ];
435
+ const removedPaths: string[] = [];
436
+ for (const targetDir of targetDirs) {
437
+ if (!existsSync(targetDir)) continue;
438
+ rmSync(targetDir, { recursive: true, force: true });
439
+ removedPaths.push(targetDir);
440
+ }
441
+ pruneCatalogForExtension(
442
+ join(runtimeRoot, "extensions.json"),
443
+ extensionId,
444
+ runtimeName,
445
+ runtimeRoot,
446
+ deps,
447
+ );
448
+ removed.push([runtimeName, removedPaths] as const);
449
+ }
450
+
451
+ // D4: the code goes, the DATA stays. Bindings cannot resolve without the
452
+ // source tree, so they are deleted; the extension's own `ext_<id>_*` tables
453
+ // are deliberately preserved for a future purge command to decide about.
454
+ let bindingsRemoved = 0;
455
+ if (options.runtime === null) {
456
+ if (manifest.kind === "command-skill") {
457
+ const result = options.db
458
+ .prepare("DELETE FROM _ext_bindings WHERE extension_id = ?")
459
+ .run(extensionId);
460
+ bindingsRemoved = Number(result.changes) || 0;
461
+ }
462
+ rmSync(installedDir, { recursive: true, force: true });
463
+ }
464
+
465
+ return {
466
+ extensionId,
467
+ installedDir,
468
+ bindingsRemoved,
469
+ removed,
470
+ sourceRemoved: options.runtime === null,
471
+ };
472
+ }
473
+
474
+ export interface ClaudeCleanupReport {
475
+ projectRoot: string;
476
+ claudeSkillsRoot: string;
477
+ overlayCatalogPath: string;
478
+ removed: string[];
479
+ }
480
+
481
+ export interface ClaudeExposeReport extends ClaudeCleanupReport {
482
+ exposed: Array<{ commandName: string; sourceSkillPath: string; targetSkillPath: string }>;
483
+ }
484
+
485
+ function claudeSkillsRootFor(projectRoot: string): string {
486
+ return join(projectRoot, ".claude", "skills");
487
+ }
488
+
489
+ /** Port of `_load_claude_overlay_catalog`: a LIST, or nothing. */
490
+ function loadOverlayCatalog(path: string): unknown[] {
491
+ if (!existsSync(path)) return [];
492
+ try {
493
+ const data = JSON.parse(readFileSync(path, "utf8"));
494
+ return Array.isArray(data) ? data : [];
495
+ } catch {
496
+ return [];
497
+ }
498
+ }
499
+
500
+ /** Port of `cleanup_claude_runtime_skills`. */
501
+ export function cleanupClaudeRuntimeSkills(projectRoot: string): ClaudeCleanupReport {
502
+ const claudeSkillsRoot = claudeSkillsRootFor(projectRoot);
503
+ const overlayCatalogPath = join(claudeSkillsRoot, "extensions.external.json");
504
+ const removed: string[] = [];
505
+
506
+ for (const item of loadOverlayCatalog(overlayCatalogPath)) {
507
+ if (typeof item !== "object" || item === null || Array.isArray(item)) continue;
508
+ const targetSkillPath = (item as Record<string, unknown>).target_skill_path;
509
+ if (typeof targetSkillPath !== "string") continue;
510
+ if (existsSync(targetSkillPath)) {
511
+ rmSync(targetSkillPath);
512
+ removed.push(targetSkillPath);
513
+ }
514
+ // Only an EMPTY parent is removed: a directory holding anything else is
515
+ // not ours to delete.
516
+ const parent = dirname(targetSkillPath);
517
+ if (existsSync(parent) && readdirSync(parent).length === 0) rmSync(parent, { recursive: true });
518
+ }
519
+
520
+ if (existsSync(overlayCatalogPath)) rmSync(overlayCatalogPath);
521
+
522
+ return { projectRoot, claudeSkillsRoot, overlayCatalogPath, removed };
523
+ }
524
+
525
+ /** Port of `expose_claude_runtime_skills`. */
526
+ export function exposeClaudeRuntimeSkills(
527
+ projectRoot: string,
528
+ catalog: { extensions: unknown[] },
529
+ ): ClaudeExposeReport {
530
+ const claudeSkillsRoot = claudeSkillsRootFor(projectRoot);
531
+ mkdirSync(claudeSkillsRoot, { recursive: true });
532
+ const cleanup = cleanupClaudeRuntimeSkills(projectRoot);
533
+
534
+ const exposed: ClaudeExposeReport["exposed"] = [];
535
+ for (const item of catalog.extensions) {
536
+ if (typeof item !== "object" || item === null || Array.isArray(item)) continue;
537
+ const record = item as Record<string, unknown>;
538
+ const commandName = record.command_name;
539
+ const installedSkillPath = record.installed_skill_path;
540
+ if (typeof commandName !== "string" || typeof installedSkillPath !== "string") continue;
541
+ if (!existsSync(installedSkillPath)) continue;
542
+ const targetDir = join(claudeSkillsRoot, filesystemSkillDirName(commandName));
543
+ mkdirSync(targetDir, { recursive: true });
544
+ const targetSkillPath = join(targetDir, "SKILL.md");
545
+ copyFileSync(installedSkillPath, targetSkillPath);
546
+ cleanup.removed.push(...removeLegacySkillDirIfOwned(claudeSkillsRoot, commandName));
547
+ exposed.push({ commandName, sourceSkillPath: installedSkillPath, targetSkillPath });
548
+ }
549
+
550
+ const overlayCatalogPath = join(claudeSkillsRoot, "extensions.external.json");
551
+ writeFileSync(
552
+ overlayCatalogPath,
553
+ `${pythonJsonDumpsIndentedOrdered(
554
+ exposed.map((entry) => ({
555
+ command_name: entry.commandName,
556
+ source_skill_path: entry.sourceSkillPath,
557
+ target_skill_path: entry.targetSkillPath,
558
+ })),
559
+ )}\n`,
560
+ "utf8",
561
+ );
562
+ return {
563
+ projectRoot,
564
+ claudeSkillsRoot,
565
+ overlayCatalogPath,
566
+ removed: cleanup.removed,
567
+ exposed,
568
+ };
569
+ }
570
+
571
+ /** Port of `load_runtime_catalog`: the STRICT reader, which refuses. */
572
+ export function loadRuntimeCatalog(runtime: string, mirrorHome: string): { extensions: unknown[] } {
573
+ const catalogPath = join(runtimeSkillsRootForHome(mirrorHome, runtime), "extensions.json");
574
+ if (!existsSync(catalogPath)) {
575
+ throw new ExtensionValidationError(`runtime catalog not found: ${catalogPath}`);
576
+ }
577
+ let parsed: unknown = {};
578
+ try {
579
+ parsed = JSON.parse(readFileSync(catalogPath, "utf8"));
580
+ } catch {
581
+ parsed = { schema_version: "1", extensions: [] };
582
+ }
583
+ const catalog = (
584
+ Array.isArray(parsed)
585
+ ? { schema_version: "0", extensions: parsed }
586
+ : typeof parsed === "object" && parsed !== null
587
+ ? (parsed as Record<string, unknown>)
588
+ : { schema_version: "1", extensions: [] }
589
+ ) as Record<string, unknown>;
590
+ if (catalog.schema_version !== "1") {
591
+ throw new ExtensionValidationError(
592
+ `unsupported runtime catalog schema '${String(catalog.schema_version)}' in ${catalogPath}`,
593
+ );
594
+ }
595
+ if (catalog.runtime !== runtime) {
596
+ throw new ExtensionValidationError(
597
+ `runtime catalog ${catalogPath} does not match runtime '${runtime}'`,
598
+ );
599
+ }
600
+ if (!Array.isArray(catalog.extensions)) {
601
+ throw new ExtensionValidationError(
602
+ `invalid runtime catalog extensions payload in ${catalogPath}`,
603
+ );
604
+ }
605
+ return { extensions: catalog.extensions };
606
+ }
607
+
608
+ /** The `install` report, in Python's print order. */
609
+ export function renderInstallReport(report: InstallReport): RenderedCommand {
610
+ const lines = [
611
+ `Installed extension/${report.extensionId}`,
612
+ ` source: ${report.sourceDir}`,
613
+ ` installed: ${report.installedDir}`,
614
+ ];
615
+ for (const [runtimeName, items] of report.synced) {
616
+ lines.push(` runtime ${runtimeName}:`);
617
+ for (const item of items) {
618
+ lines.push(` ${item.command_name} -> ${item.installed_skill_path}`);
619
+ }
620
+ }
621
+ // On stderr, not stdout: the install SUCCEEDED, and a warning must not land
622
+ // in the bytes a caller parses as the install report.
623
+ const stderr =
624
+ report.unmigrated.length === 0
625
+ ? ""
626
+ : [
627
+ `Warning: extension/${report.extensionId} declares no runtime for ` +
628
+ `${report.unmigrated.length} capability(ies):`,
629
+ ...report.unmigrated.map((entry) => ` ${entry}`),
630
+ "These will refuse until they declare a runtime. " +
631
+ "See docs/releases/pending-cutoffs.md.",
632
+ "",
633
+ ].join("\n");
634
+ return { stdout: `${lines.join("\n")}\n`, stderr, exitCode: 0 };
635
+ }
636
+
637
+ /** The `uninstall` report, in Python's print order. */
638
+ export function renderUninstallReport(report: UninstallReport): RenderedCommand {
639
+ const lines = [
640
+ `Uninstalled extension/${report.extensionId}`,
641
+ ` installed: ${report.installedDir}`,
642
+ ];
643
+ if (report.sourceRemoved) lines.push(" source tree: removed");
644
+ if (report.bindingsRemoved) {
645
+ lines.push(` bindings: ${report.bindingsRemoved} row(s) removed`);
646
+ }
647
+ for (const [runtimeName, items] of report.removed) {
648
+ lines.push(` runtime ${runtimeName}:`);
649
+ for (const item of items) lines.push(` removed ${item}`);
650
+ }
651
+ return { stdout: `${lines.join("\n")}\n`, stderr: "", exitCode: 0 };
652
+ }