mirror-mind 0.0.1 → 0.33.0

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 (474) hide show
  1. package/.claude/hooks/log-assistant-reply.sh +85 -0
  2. package/.claude/hooks/log-session-end.sh +85 -0
  3. package/.claude/hooks/log-user-prompt.sh +85 -0
  4. package/.claude/hooks/mirror-inject.sh +85 -0
  5. package/.claude/hooks/session-start.sh +85 -0
  6. package/.gemini/hooks/log-assistant.sh +85 -0
  7. package/.gemini/hooks/log-user.sh +85 -0
  8. package/.gemini/hooks/session-end.sh +85 -0
  9. package/.gemini/hooks/session-start.sh +85 -0
  10. package/.pi/extensions/mirror-logger.ts +646 -0
  11. package/.pi/skills/mm-backup/SKILL.md +21 -0
  12. package/.pi/skills/mm-build/SKILL.md +1206 -0
  13. package/.pi/skills/mm-consolidate/SKILL.md +112 -0
  14. package/.pi/skills/mm-consult/SKILL.md +70 -0
  15. package/.pi/skills/mm-conversations/SKILL.md +16 -0
  16. package/.pi/skills/mm-discard/SKILL.md +39 -0
  17. package/.pi/skills/mm-explore/SKILL.md +255 -0
  18. package/.pi/skills/mm-help/SKILL.md +74 -0
  19. package/.pi/skills/mm-identity/SKILL.md +96 -0
  20. package/.pi/skills/mm-journal/SKILL.md +16 -0
  21. package/.pi/skills/mm-journey/SKILL.md +23 -0
  22. package/.pi/skills/mm-journeys/SKILL.md +15 -0
  23. package/.pi/skills/mm-memories/SKILL.md +17 -0
  24. package/.pi/skills/mm-mirror/SKILL.md +98 -0
  25. package/.pi/skills/mm-mute/SKILL.md +18 -0
  26. package/.pi/skills/mm-new/SKILL.md +18 -0
  27. package/.pi/skills/mm-recall/SKILL.md +17 -0
  28. package/.pi/skills/mm-release-notes/SKILL.md +52 -0
  29. package/.pi/skills/mm-seed/SKILL.md +19 -0
  30. package/.pi/skills/mm-shadow/SKILL.md +117 -0
  31. package/.pi/skills/mm-soul/SKILL.md +502 -0
  32. package/.pi/skills/mm-tasks/SKILL.md +24 -0
  33. package/.pi/skills/mm-update/SKILL.md +32 -0
  34. package/.pi/skills/mm-week/SKILL.md +28 -0
  35. package/.pi/skills/mm-welcome/SKILL.md +24 -0
  36. package/AGENTS.md +188 -0
  37. package/README.md +126 -9
  38. package/bin/loader.js +67 -0
  39. package/bin/mirror-hook.js +7 -0
  40. package/bin/mirror.js +7 -0
  41. package/docs/releases/index.md +100 -0
  42. package/docs/releases/pending-cutoffs.md +510 -0
  43. package/docs/releases/v0.10.0.md +123 -0
  44. package/docs/releases/v0.10.1.md +66 -0
  45. package/docs/releases/v0.10.10.md +51 -0
  46. package/docs/releases/v0.10.11.md +39 -0
  47. package/docs/releases/v0.10.12.md +57 -0
  48. package/docs/releases/v0.10.13.md +35 -0
  49. package/docs/releases/v0.10.14.md +42 -0
  50. package/docs/releases/v0.10.15.md +53 -0
  51. package/docs/releases/v0.10.16.md +35 -0
  52. package/docs/releases/v0.10.17.md +43 -0
  53. package/docs/releases/v0.10.2.md +64 -0
  54. package/docs/releases/v0.10.3.md +78 -0
  55. package/docs/releases/v0.10.4.md +42 -0
  56. package/docs/releases/v0.10.5.md +57 -0
  57. package/docs/releases/v0.10.6.md +47 -0
  58. package/docs/releases/v0.10.7.md +53 -0
  59. package/docs/releases/v0.10.8.md +49 -0
  60. package/docs/releases/v0.10.9.md +37 -0
  61. package/docs/releases/v0.11.0.md +86 -0
  62. package/docs/releases/v0.12.0.md +67 -0
  63. package/docs/releases/v0.13.0.md +64 -0
  64. package/docs/releases/v0.14.0.md +68 -0
  65. package/docs/releases/v0.15.0.md +48 -0
  66. package/docs/releases/v0.16.0.md +50 -0
  67. package/docs/releases/v0.16.1.md +55 -0
  68. package/docs/releases/v0.16.2.md +36 -0
  69. package/docs/releases/v0.17.0.md +50 -0
  70. package/docs/releases/v0.17.1.md +58 -0
  71. package/docs/releases/v0.17.2.md +42 -0
  72. package/docs/releases/v0.17.3.md +58 -0
  73. package/docs/releases/v0.18.0.md +84 -0
  74. package/docs/releases/v0.18.1.md +51 -0
  75. package/docs/releases/v0.18.2.md +69 -0
  76. package/docs/releases/v0.19.0.md +85 -0
  77. package/docs/releases/v0.19.1.md +46 -0
  78. package/docs/releases/v0.20.0.md +85 -0
  79. package/docs/releases/v0.21.0.md +61 -0
  80. package/docs/releases/v0.21.1.md +47 -0
  81. package/docs/releases/v0.22.0.md +77 -0
  82. package/docs/releases/v0.22.1.md +42 -0
  83. package/docs/releases/v0.23.0.md +105 -0
  84. package/docs/releases/v0.24.0.md +125 -0
  85. package/docs/releases/v0.25.0.md +107 -0
  86. package/docs/releases/v0.26.0.md +139 -0
  87. package/docs/releases/v0.26.1.md +65 -0
  88. package/docs/releases/v0.26.2.md +43 -0
  89. package/docs/releases/v0.27.0.md +63 -0
  90. package/docs/releases/v0.28.0.md +59 -0
  91. package/docs/releases/v0.29.0.md +61 -0
  92. package/docs/releases/v0.29.1.md +59 -0
  93. package/docs/releases/v0.29.2.md +59 -0
  94. package/docs/releases/v0.29.3.md +93 -0
  95. package/docs/releases/v0.30.0.md +75 -0
  96. package/docs/releases/v0.30.1.md +93 -0
  97. package/docs/releases/v0.30.2.md +66 -0
  98. package/docs/releases/v0.31.0.md +122 -0
  99. package/docs/releases/v0.31.1.md +142 -0
  100. package/docs/releases/v0.31.10.md +118 -0
  101. package/docs/releases/v0.31.11.md +88 -0
  102. package/docs/releases/v0.31.12.md +105 -0
  103. package/docs/releases/v0.31.13.md +99 -0
  104. package/docs/releases/v0.31.14.md +97 -0
  105. package/docs/releases/v0.31.2.md +115 -0
  106. package/docs/releases/v0.31.3.md +142 -0
  107. package/docs/releases/v0.31.4.md +117 -0
  108. package/docs/releases/v0.31.5.md +155 -0
  109. package/docs/releases/v0.31.6.md +132 -0
  110. package/docs/releases/v0.31.7.md +132 -0
  111. package/docs/releases/v0.31.8.md +120 -0
  112. package/docs/releases/v0.31.9.md +120 -0
  113. package/docs/releases/v0.32.0.md +261 -0
  114. package/docs/releases/v0.32.1.md +59 -0
  115. package/docs/releases/v0.33.0.md +164 -0
  116. package/docs/releases/v0.8.0.md +118 -0
  117. package/docs/releases/v0.9.0.md +98 -0
  118. package/docs/releases/v0.9.1.md +83 -0
  119. package/package.json +102 -6
  120. package/plugins/mirror-mind/.claude-plugin/plugin.json +17 -0
  121. package/plugins/mirror-mind/hooks/hooks.json +62 -0
  122. package/plugins/mirror-mind/hooks/log-assistant-reply.sh +98 -0
  123. package/plugins/mirror-mind/hooks/log-session-end.sh +98 -0
  124. package/plugins/mirror-mind/hooks/log-user-prompt.sh +98 -0
  125. package/plugins/mirror-mind/hooks/mirror-inject.sh +98 -0
  126. package/plugins/mirror-mind/hooks/session-start.sh +98 -0
  127. package/plugins/mirror-mind/skills/mm-backup/SKILL.md +27 -0
  128. package/plugins/mirror-mind/skills/mm-build/SKILL.md +1206 -0
  129. package/plugins/mirror-mind/skills/mm-consolidate/SKILL.md +112 -0
  130. package/plugins/mirror-mind/skills/mm-consult/SKILL.md +83 -0
  131. package/plugins/mirror-mind/skills/mm-conversations/SKILL.md +17 -0
  132. package/plugins/mirror-mind/skills/mm-discard/SKILL.md +40 -0
  133. package/plugins/mirror-mind/skills/mm-explore/SKILL.md +241 -0
  134. package/plugins/mirror-mind/skills/mm-help/SKILL.md +104 -0
  135. package/plugins/mirror-mind/skills/mm-identity/SKILL.md +96 -0
  136. package/plugins/mirror-mind/skills/mm-journal/SKILL.md +21 -0
  137. package/plugins/mirror-mind/skills/mm-journey/SKILL.md +37 -0
  138. package/plugins/mirror-mind/skills/mm-journeys/SKILL.md +15 -0
  139. package/plugins/mirror-mind/skills/mm-memories/SKILL.md +20 -0
  140. package/plugins/mirror-mind/skills/mm-mirror/SKILL.md +67 -0
  141. package/plugins/mirror-mind/skills/mm-mute/SKILL.md +18 -0
  142. package/plugins/mirror-mind/skills/mm-new/SKILL.md +18 -0
  143. package/plugins/mirror-mind/skills/mm-recall/SKILL.md +17 -0
  144. package/plugins/mirror-mind/skills/mm-release-notes/SKILL.md +31 -0
  145. package/plugins/mirror-mind/skills/mm-seed/SKILL.md +15 -0
  146. package/plugins/mirror-mind/skills/mm-shadow/SKILL.md +117 -0
  147. package/plugins/mirror-mind/skills/mm-soul/SKILL.md +488 -0
  148. package/plugins/mirror-mind/skills/mm-tasks/SKILL.md +36 -0
  149. package/plugins/mirror-mind/skills/mm-update/SKILL.md +30 -0
  150. package/plugins/mirror-mind/skills/mm-week/SKILL.md +46 -0
  151. package/plugins/mirror-mind/skills/mm-welcome/SKILL.md +22 -0
  152. package/scripts/codex-hooks/session-end.sh +85 -0
  153. package/scripts/codex-hooks/session-start.sh +85 -0
  154. package/scripts/codex-mirror.sh +48 -0
  155. package/templates/identity/README.md +119 -0
  156. package/templates/identity/ego/behavior.yaml +52 -0
  157. package/templates/identity/ego/constraints.yaml +15 -0
  158. package/templates/identity/ego/identity.yaml +43 -0
  159. package/templates/identity/journeys/personal-growth.yaml +39 -0
  160. package/templates/identity/organization/identity.yaml +24 -0
  161. package/templates/identity/organization/principles.yaml +17 -0
  162. package/templates/identity/personas/coach.yaml +94 -0
  163. package/templates/identity/personas/designer.yaml +93 -0
  164. package/templates/identity/personas/doctor.yaml +96 -0
  165. package/templates/identity/personas/engineer.yaml +77 -0
  166. package/templates/identity/personas/financial.yaml +97 -0
  167. package/templates/identity/personas/prompt-engineer.yaml +122 -0
  168. package/templates/identity/personas/researcher.yaml +88 -0
  169. package/templates/identity/personas/strategist.yaml +91 -0
  170. package/templates/identity/personas/teacher.yaml +95 -0
  171. package/templates/identity/personas/therapist.yaml +91 -0
  172. package/templates/identity/personas/thinker.yaml +74 -0
  173. package/templates/identity/personas/writer.yaml +76 -0
  174. package/templates/identity/self/config.yaml +16 -0
  175. package/templates/identity/self/soul.yaml +105 -0
  176. package/templates/identity/user/identity.yaml +30 -0
  177. package/ts/src/backup/staging.ts +62 -0
  178. package/ts/src/backup/zipBackup.ts +267 -0
  179. package/ts/src/backup/zipReader.ts +109 -0
  180. package/ts/src/backup/zipWriter.ts +148 -0
  181. package/ts/src/builder/activeCheckpoint.ts +134 -0
  182. package/ts/src/builder/aggregateStatus.ts +34 -0
  183. package/ts/src/builder/approve.ts +149 -0
  184. package/ts/src/builder/argv.ts +292 -0
  185. package/ts/src/builder/ariadMethod.ts +536 -0
  186. package/ts/src/builder/artifacts/artifactSurfaces.ts +106 -0
  187. package/ts/src/builder/artifacts/artifactWriter.ts +113 -0
  188. package/ts/src/builder/artifacts/closureArtifacts.ts +199 -0
  189. package/ts/src/builder/artifacts/deliveryStoryArtifacts.ts +316 -0
  190. package/ts/src/builder/artifacts/planArtifacts.ts +242 -0
  191. package/ts/src/builder/artifacts/scaffoldSections.ts +273 -0
  192. package/ts/src/builder/artifacts/scaffoldState.ts +227 -0
  193. package/ts/src/builder/artifacts/storyFiles.ts +61 -0
  194. package/ts/src/builder/artifacts/storyIndex.ts +166 -0
  195. package/ts/src/builder/cadence.ts +59 -0
  196. package/ts/src/builder/card.ts +192 -0
  197. package/ts/src/builder/checkpointRefused.ts +66 -0
  198. package/ts/src/builder/closure.ts +964 -0
  199. package/ts/src/builder/commands.ts +2053 -0
  200. package/ts/src/builder/cursorPosition.ts +176 -0
  201. package/ts/src/builder/cursorTransitions.ts +77 -0
  202. package/ts/src/builder/deliveryCursor.ts +577 -0
  203. package/ts/src/builder/deliveryStoryClosure.ts +521 -0
  204. package/ts/src/builder/deliveryStoryPlan.ts +536 -0
  205. package/ts/src/builder/deliveryStoryReady.ts +160 -0
  206. package/ts/src/builder/deliveryStoryRoadmapClosure.ts +151 -0
  207. package/ts/src/builder/expand.ts +336 -0
  208. package/ts/src/builder/flowUnit.ts +339 -0
  209. package/ts/src/builder/homeSurface.ts +122 -0
  210. package/ts/src/builder/implementationGuard.ts +125 -0
  211. package/ts/src/builder/index.ts +11 -0
  212. package/ts/src/builder/lifecycleRefusal.ts +92 -0
  213. package/ts/src/builder/lifecycleRibbon.ts +159 -0
  214. package/ts/src/builder/load.ts +313 -0
  215. package/ts/src/builder/methodAdoption.ts +113 -0
  216. package/ts/src/builder/methodDefinition.ts +429 -0
  217. package/ts/src/builder/methodInspection.ts +371 -0
  218. package/ts/src/builder/plan.ts +408 -0
  219. package/ts/src/builder/planPreauthorization.ts +265 -0
  220. package/ts/src/builder/prepare.ts +225 -0
  221. package/ts/src/builder/projectPaths.ts +64 -0
  222. package/ts/src/builder/pull.ts +356 -0
  223. package/ts/src/builder/pullCandidates.ts +463 -0
  224. package/ts/src/builder/pullCandidatesRender.ts +238 -0
  225. package/ts/src/builder/refinementField.ts +93 -0
  226. package/ts/src/builder/releaseIntent.ts +173 -0
  227. package/ts/src/builder/resumeState.ts +84 -0
  228. package/ts/src/builder/resumeSurface.ts +149 -0
  229. package/ts/src/builder/roadmapGrammar.ts +300 -0
  230. package/ts/src/builder/roadmapScan.ts +97 -0
  231. package/ts/src/builder/roadmapScope.ts +210 -0
  232. package/ts/src/builder/scopePhrases.ts +146 -0
  233. package/ts/src/builder/storyPaths.ts +196 -0
  234. package/ts/src/builder/storyPlanPreauthorization.ts +287 -0
  235. package/ts/src/builder/surfaceProtocol.ts +42 -0
  236. package/ts/src/builder/templateGeneration.ts +148 -0
  237. package/ts/src/builder/transition.ts +178 -0
  238. package/ts/src/consult/args.ts +99 -0
  239. package/ts/src/consult/core.ts +122 -0
  240. package/ts/src/consult/modelCatalog.ts +55 -0
  241. package/ts/src/consult/render.ts +47 -0
  242. package/ts/src/conversation/append.ts +414 -0
  243. package/ts/src/conversation/backfill.ts +387 -0
  244. package/ts/src/conversation/closeTail.ts +467 -0
  245. package/ts/src/conversation/extraction.ts +333 -0
  246. package/ts/src/conversation/extractionDriver.ts +130 -0
  247. package/ts/src/conversation/extractionRun.ts +70 -0
  248. package/ts/src/conversation/hookPayload.ts +51 -0
  249. package/ts/src/conversation/journeyInference.ts +165 -0
  250. package/ts/src/conversation/journeyRepair.ts +114 -0
  251. package/ts/src/conversation/lifecycleFaces.ts +197 -0
  252. package/ts/src/conversation/lifecycleWrites.ts +313 -0
  253. package/ts/src/conversation/listing.ts +77 -0
  254. package/ts/src/conversation/logger.ts +851 -0
  255. package/ts/src/conversation/loggerCli.ts +321 -0
  256. package/ts/src/conversation/loggerRuntime.ts +259 -0
  257. package/ts/src/conversation/metadataLifecycle.ts +443 -0
  258. package/ts/src/conversation/recall.ts +66 -0
  259. package/ts/src/conversation/sessionComposites.ts +258 -0
  260. package/ts/src/conversation/sessionImport.ts +90 -0
  261. package/ts/src/conversation/transcriptBackfill.ts +141 -0
  262. package/ts/src/cultivation/applyActions.ts +241 -0
  263. package/ts/src/cultivation/cluster.ts +86 -0
  264. package/ts/src/cultivation/consolidationStore.ts +281 -0
  265. package/ts/src/cultivation/promptContext.ts +62 -0
  266. package/ts/src/cultivation/propose.ts +305 -0
  267. package/ts/src/cultivation/scan.ts +155 -0
  268. package/ts/src/db/backupGate.ts +47 -0
  269. package/ts/src/db/bootstrap.ts +108 -0
  270. package/ts/src/db/bootstrapLock.ts +223 -0
  271. package/ts/src/db/copyGuard.ts +60 -0
  272. package/ts/src/db/database.ts +397 -0
  273. package/ts/src/db/decode.ts +66 -0
  274. package/ts/src/db/ftsIntegrity.ts +41 -0
  275. package/ts/src/db/migrateOnOpen.ts +214 -0
  276. package/ts/src/db/migrations.ts +576 -0
  277. package/ts/src/db/readyOnOpen.ts +35 -0
  278. package/ts/src/db/rowDecode.ts +42 -0
  279. package/ts/src/db/schema.ts +432 -0
  280. package/ts/src/db/schemaInventory.ts +225 -0
  281. package/ts/src/db/schemaInventorySnapshot.ts +2107 -0
  282. package/ts/src/db/schemaState.ts +91 -0
  283. package/ts/src/db/schemaTsDivergence.ts +114 -0
  284. package/ts/src/descriptor/descriptorRead.ts +49 -0
  285. package/ts/src/docs/docsLint.ts +265 -0
  286. package/ts/src/explorer/handoff.ts +522 -0
  287. package/ts/src/explorer/render.ts +335 -0
  288. package/ts/src/explorer/story.ts +769 -0
  289. package/ts/src/explorer/transition.ts +72 -0
  290. package/ts/src/extensions/bindings.ts +170 -0
  291. package/ts/src/extensions/catalog.ts +249 -0
  292. package/ts/src/extensions/catalogCommands.ts +513 -0
  293. package/ts/src/extensions/catalogWrites.ts +652 -0
  294. package/ts/src/extensions/contextRuntime.ts +325 -0
  295. package/ts/src/extensions/dispatch.ts +374 -0
  296. package/ts/src/extensions/errors.ts +24 -0
  297. package/ts/src/extensions/manifest.ts +356 -0
  298. package/ts/src/extensions/migrations.ts +350 -0
  299. package/ts/src/extraction/conversation.ts +304 -0
  300. package/ts/src/extraction/conversationMetadata.ts +206 -0
  301. package/ts/src/extraction/fencing.ts +106 -0
  302. package/ts/src/extraction/json.ts +17 -0
  303. package/ts/src/extraction/prompts.ts +470 -0
  304. package/ts/src/frontDoor/args.ts +30 -0
  305. package/ts/src/frontDoor/argvShape.ts +106 -0
  306. package/ts/src/frontDoor/buildLoadRuntime.ts +139 -0
  307. package/ts/src/frontDoor/buildRoute.ts +398 -0
  308. package/ts/src/frontDoor/cli.ts +1872 -0
  309. package/ts/src/frontDoor/cloneRoleGuard.ts +98 -0
  310. package/ts/src/frontDoor/consultRoute.ts +50 -0
  311. package/ts/src/frontDoor/contentTailRoute.ts +239 -0
  312. package/ts/src/frontDoor/conversationLoggerRoute.ts +83 -0
  313. package/ts/src/frontDoor/cultivationRoute.ts +210 -0
  314. package/ts/src/frontDoor/dbPath.ts +131 -0
  315. package/ts/src/frontDoor/dbSafetyToolsRoute.ts +240 -0
  316. package/ts/src/frontDoor/exploreRoute.ts +572 -0
  317. package/ts/src/frontDoor/extensionCatalogRoute.ts +252 -0
  318. package/ts/src/frontDoor/frontDoorLog.ts +57 -0
  319. package/ts/src/frontDoor/identityWrite.ts +36 -0
  320. package/ts/src/frontDoor/journeyWriteRoute.ts +26 -0
  321. package/ts/src/frontDoor/lifecycleWriteRoute.ts +110 -0
  322. package/ts/src/frontDoor/liveBackup.ts +100 -0
  323. package/ts/src/frontDoor/mirrorModeRoute.ts +163 -0
  324. package/ts/src/frontDoor/namedRefusal.ts +35 -0
  325. package/ts/src/frontDoor/nodeSupport.ts +24 -0
  326. package/ts/src/frontDoor/render/consolidate.ts +199 -0
  327. package/ts/src/frontDoor/render/conversations.ts +33 -0
  328. package/ts/src/frontDoor/render/cultivationShared.ts +21 -0
  329. package/ts/src/frontDoor/render/descriptor.ts +20 -0
  330. package/ts/src/frontDoor/render/detectPersona.ts +50 -0
  331. package/ts/src/frontDoor/render/icons.ts +13 -0
  332. package/ts/src/frontDoor/render/identity.ts +59 -0
  333. package/ts/src/frontDoor/render/identityRows.ts +17 -0
  334. package/ts/src/frontDoor/render/inspectPersona.ts +53 -0
  335. package/ts/src/frontDoor/render/journeyStatus.ts +78 -0
  336. package/ts/src/frontDoor/render/journeys.ts +79 -0
  337. package/ts/src/frontDoor/render/list.ts +41 -0
  338. package/ts/src/frontDoor/render/memories.ts +67 -0
  339. package/ts/src/frontDoor/render/recall.ts +49 -0
  340. package/ts/src/frontDoor/render/shadow.ts +155 -0
  341. package/ts/src/frontDoor/render/tasks.ts +99 -0
  342. package/ts/src/frontDoor/render/tasksImportSync.ts +63 -0
  343. package/ts/src/frontDoor/render/week.ts +113 -0
  344. package/ts/src/frontDoor/routing.ts +1001 -0
  345. package/ts/src/frontDoor/runtimeRoute.ts +623 -0
  346. package/ts/src/frontDoor/searchRoute.ts +166 -0
  347. package/ts/src/frontDoor/seedPaths.ts +98 -0
  348. package/ts/src/frontDoor/sessionStartWelcomeRoute.ts +166 -0
  349. package/ts/src/frontDoor/soulRoute.ts +433 -0
  350. package/ts/src/frontDoor/stdinSource.ts +37 -0
  351. package/ts/src/frontDoor/tasksImportSyncRoute.ts +110 -0
  352. package/ts/src/frontDoor/tasksWriteRoute.ts +101 -0
  353. package/ts/src/frontDoor/usage.ts +176 -0
  354. package/ts/src/guards/claudePlugin.ts +298 -0
  355. package/ts/src/guards/packContents.ts +306 -0
  356. package/ts/src/guards/piSourcedSkills.ts +70 -0
  357. package/ts/src/guards/retiredRouteShadows.ts +57 -0
  358. package/ts/src/guards/retiredSurfaces.ts +564 -0
  359. package/ts/src/hooks/claude.ts +214 -0
  360. package/ts/src/hooks/codex.ts +45 -0
  361. package/ts/src/hooks/gemini.ts +126 -0
  362. package/ts/src/hooks/main.ts +109 -0
  363. package/ts/src/hooks/mirrorState.ts +71 -0
  364. package/ts/src/hooks/payload.ts +73 -0
  365. package/ts/src/hooks/runtime.ts +179 -0
  366. package/ts/src/hooks/runtimeDetection.ts +34 -0
  367. package/ts/src/identity/applyConsolidationIdentityUpdate.ts +67 -0
  368. package/ts/src/identity/descriptorGenerate.ts +162 -0
  369. package/ts/src/identity/identityEdit.ts +143 -0
  370. package/ts/src/identity/identityKey.ts +77 -0
  371. package/ts/src/identity/identityRead.ts +69 -0
  372. package/ts/src/identity/identityStore.ts +80 -0
  373. package/ts/src/identity/journeyListing.ts +53 -0
  374. package/ts/src/identity/personaInspect.ts +44 -0
  375. package/ts/src/identity/personaListing.ts +37 -0
  376. package/ts/src/identity/setIdentity.ts +64 -0
  377. package/ts/src/index.ts +231 -0
  378. package/ts/src/init/init.ts +111 -0
  379. package/ts/src/journey/journeyOptions.ts +131 -0
  380. package/ts/src/journey/journeyRemoval.ts +158 -0
  381. package/ts/src/journey/journeyStatus.ts +149 -0
  382. package/ts/src/journey/journeySyncFile.ts +87 -0
  383. package/ts/src/journey/journeyWrite.ts +203 -0
  384. package/ts/src/journey/parentJourney.ts +41 -0
  385. package/ts/src/journey/validateParentJourney.ts +55 -0
  386. package/ts/src/mcp/boundary.ts +151 -0
  387. package/ts/src/mcp/guards.ts +244 -0
  388. package/ts/src/mcp/main.ts +121 -0
  389. package/ts/src/mcp/payload.ts +141 -0
  390. package/ts/src/mcp/protocol.ts +141 -0
  391. package/ts/src/mcp/readModels.ts +241 -0
  392. package/ts/src/mcp/registry.ts +201 -0
  393. package/ts/src/mcp/serve.ts +120 -0
  394. package/ts/src/mcp/tools/deterministic.ts +137 -0
  395. package/ts/src/mcp/tools/providerCrossing.ts +187 -0
  396. package/ts/src/mcp/wire.ts +8 -0
  397. package/ts/src/memory/journal.ts +180 -0
  398. package/ts/src/memory/listing.ts +107 -0
  399. package/ts/src/memory/memoryWrite.ts +88 -0
  400. package/ts/src/memory/reinforcement.ts +35 -0
  401. package/ts/src/mirror/context.ts +161 -0
  402. package/ts/src/mirror/defaultResolution.ts +199 -0
  403. package/ts/src/mirror/mirrorForm.ts +43 -0
  404. package/ts/src/mirror/orchestration.ts +364 -0
  405. package/ts/src/mirror/reception.ts +194 -0
  406. package/ts/src/mirror/render.ts +75 -0
  407. package/ts/src/mirror/runtimeSession.ts +205 -0
  408. package/ts/src/mode/operatingMode.ts +148 -0
  409. package/ts/src/observability/callOutcome.ts +115 -0
  410. package/ts/src/observability/inspectLedger.ts +329 -0
  411. package/ts/src/observability/ledgerHooks.ts +126 -0
  412. package/ts/src/observability/llmCalls.ts +154 -0
  413. package/ts/src/persona/detectPersona.ts +81 -0
  414. package/ts/src/planning/promptAssembly.ts +111 -0
  415. package/ts/src/planning/weekPending.ts +81 -0
  416. package/ts/src/planning/weekPlan.ts +183 -0
  417. package/ts/src/planning/weekSave.ts +117 -0
  418. package/ts/src/providers/config.ts +189 -0
  419. package/ts/src/providers/cost.ts +64 -0
  420. package/ts/src/providers/credits.ts +204 -0
  421. package/ts/src/providers/embedding.ts +321 -0
  422. package/ts/src/providers/familyProviders.ts +147 -0
  423. package/ts/src/providers/llm.ts +334 -0
  424. package/ts/src/providers/openrouter.ts +244 -0
  425. package/ts/src/providers/redaction.ts +110 -0
  426. package/ts/src/providers/replay.ts +15 -0
  427. package/ts/src/providers/transport.ts +410 -0
  428. package/ts/src/repair/encodingRepair.ts +253 -0
  429. package/ts/src/runtime/backup.ts +151 -0
  430. package/ts/src/runtime/channel.ts +146 -0
  431. package/ts/src/runtime/config.ts +168 -0
  432. package/ts/src/runtime/diagnose.ts +686 -0
  433. package/ts/src/runtime/git.ts +510 -0
  434. package/ts/src/runtime/installKind.ts +220 -0
  435. package/ts/src/runtime/migrate.ts +154 -0
  436. package/ts/src/runtime/packageIdentity.ts +102 -0
  437. package/ts/src/runtime/releaseNotes.ts +244 -0
  438. package/ts/src/runtime/status.ts +557 -0
  439. package/ts/src/runtime/strategies/clone.ts +200 -0
  440. package/ts/src/runtime/strategies/package.ts +328 -0
  441. package/ts/src/runtime/treeRoot.ts +30 -0
  442. package/ts/src/runtime/update.ts +445 -0
  443. package/ts/src/runtime/updateGate.ts +40 -0
  444. package/ts/src/runtime/updatePipeline.ts +159 -0
  445. package/ts/src/runtime/version.ts +36 -0
  446. package/ts/src/runtime/wiring.ts +130 -0
  447. package/ts/src/search/memorySearch.ts +338 -0
  448. package/ts/src/search/ranker.ts +154 -0
  449. package/ts/src/seed/seed.ts +256 -0
  450. package/ts/src/seed/seedYaml.ts +133 -0
  451. package/ts/src/soul/apply.ts +218 -0
  452. package/ts/src/soul/harvest.ts +267 -0
  453. package/ts/src/soul/prompts/soul_beauty_voice.md +103 -0
  454. package/ts/src/soul/prompts/soul_self_voice.md +219 -0
  455. package/ts/src/soul/prompts/soul_wisdom_voice.md +121 -0
  456. package/ts/src/soul/prompts.ts +85 -0
  457. package/ts/src/soul/render.ts +358 -0
  458. package/ts/src/soul/state.ts +212 -0
  459. package/ts/src/soul/transition.ts +37 -0
  460. package/ts/src/tasks/journeyPathParse.ts +113 -0
  461. package/ts/src/tasks/taskImportSync.ts +142 -0
  462. package/ts/src/tasks/taskStore.ts +312 -0
  463. package/ts/src/tasks/weekView.ts +120 -0
  464. package/ts/src/util/clipCodePoints.ts +26 -0
  465. package/ts/src/util/paths.ts +63 -0
  466. package/ts/src/util/program.ts +9 -0
  467. package/ts/src/util/pyGenerators.ts +144 -0
  468. package/ts/src/util/pythonText.ts +317 -0
  469. package/ts/src/util/semver.ts +45 -0
  470. package/ts/src/util/shellWord.ts +25 -0
  471. package/ts/src/util/slug.ts +64 -0
  472. package/ts/src/welcome/card.ts +339 -0
  473. package/ts/src/welcome/statusLine.ts +101 -0
  474. package/ts/src/welcome/updateCache.ts +129 -0
@@ -0,0 +1,1001 @@
1
+ import {
2
+ BUILD_LOAD_COMPOSITION,
3
+ CONSULT_ASK_TRANSPORT,
4
+ CONSULT_CREDITS_TRANSPORT,
5
+ CONVERSATION_TAIL_TRANSPORT,
6
+ type ComposedProviderTransportSpec,
7
+ CULTIVATION_APPLY_TRANSPORT,
8
+ CULTIVATION_SCAN_TRANSPORT,
9
+ DESCRIPTOR_TRANSPORT,
10
+ JOURNAL_TRANSPORT,
11
+ MIRROR_QUERY_TRANSPORT,
12
+ type ProviderTransportSpec,
13
+ resolveComposedProviderTransport,
14
+ resolveProviderTransport,
15
+ SEARCH_TRANSPORT,
16
+ SOUL_HARVEST_TRANSPORT,
17
+ WEEK_PLAN_TRANSPORT,
18
+ } from "#providers/transport.ts";
19
+
20
+ import {
21
+ RUNTIME_SUBCOMMANDS,
22
+ TS_RUNTIME_READ_SUBCOMMANDS,
23
+ TS_RUNTIME_UPDATE_SUBCOMMANDS,
24
+ } from "./runtimeRoute.ts";
25
+ import type { UsageRequest } from "./usage.ts";
26
+
27
+ /**
28
+ * The engine a routed command answers from. One member since CV22.DS10.TS5:
29
+ * `"python"` left with the fallback it named, and every site that still
30
+ * reached for it stopped compiling -- which is how the last of them were found.
31
+ */
32
+ export type FrontDoorEngine = "ts";
33
+
34
+ /**
35
+ * A surface CV22.DS10 removed rather than ported.
36
+ *
37
+ * Until TS4 a retired name was simply unclaimed: it fell through to Python,
38
+ * which answered `Unknown command: <name>` with its usage block. That answer
39
+ * belongs to the engine TS5 deletes, and it names commands DS10 has already
40
+ * removed. These entries let the front door say *removed* itself, in one line,
41
+ * pointing at the cutoff that explains what to do instead.
42
+ *
43
+ * `matches` reads argv by NAME. Nothing here is matched by inheritance: a verb
44
+ * a retired family never had keeps its family's existing answer (the CR055
45
+ * lesson `conversations append` paid for).
46
+ */
47
+ export interface RetiredSurface {
48
+ /** The shape as a user types it, e.g. `journey export-registry`. */
49
+ readonly surface: string;
50
+ /** Heading anchor in `docs/releases/pending-cutoffs.md`. A CONSTANT. */
51
+ readonly anchor: string;
52
+ readonly matches: (argv: readonly string[]) => boolean;
53
+ }
54
+
55
+ export type RouteDecision =
56
+ | {
57
+ command: string | null;
58
+ engine: FrontDoorEngine;
59
+ reason: string;
60
+ }
61
+ | {
62
+ command: string | null;
63
+ engine: "retired";
64
+ reason: string;
65
+ /** The retired shape, from the matched entry -- never from argv. */
66
+ surface: string;
67
+ /** The cutoff anchor, from the matched entry -- never from argv. */
68
+ anchor: string;
69
+ }
70
+ | {
71
+ command: string | null;
72
+ /**
73
+ * CV22.DS10.TS5 (D2): a name the front door does not answer. Rendered by
74
+ * `usage.ts` before dispatch, like `retired` -- nothing opened, nothing
75
+ * read from stdin, nothing spawned.
76
+ */
77
+ engine: "usage";
78
+ reason: string;
79
+ request: UsageRequest;
80
+ };
81
+
82
+ /**
83
+ * A family was named and its subcommand is not one it answers (D2).
84
+ *
85
+ * `choices` is the allowlist the family ALREADY routes by, so the usage line a
86
+ * user sees cannot drift from what the build answers.
87
+ */
88
+ function unknownSubcommand(
89
+ command: string,
90
+ choices: Iterable<string>,
91
+ given: string | undefined,
92
+ program: string = command,
93
+ ): RouteDecision {
94
+ const name = given ?? "";
95
+ return {
96
+ command,
97
+ engine: "usage",
98
+ reason:
99
+ name === "" ? `${program} needs a subcommand` : `unknown ${program} subcommand: ${name}`,
100
+ request: { scope: "family", program, choices: [...choices], given: name },
101
+ };
102
+ }
103
+
104
+ /** A top-level name nothing answers, or no command at all (D2). */
105
+ function unknownCommand(given: string | null): RouteDecision {
106
+ return {
107
+ // Whatever the caller typed is not a command name, so the log records none.
108
+ command: null,
109
+ engine: "usage",
110
+ reason: given === null ? "no command" : `unknown command: ${given}`,
111
+ request: { scope: "top-level", given },
112
+ };
113
+ }
114
+
115
+ const TS_READ_COMMANDS = new Set(["detect-persona", "journeys"]);
116
+
117
+ // The Conversation Metadata Lifecycle (ES-001) flags on `conversations`.
118
+ // CR068 found the whole family unowned: DS7.US1 called it "own slice" and no
119
+ // slice claimed it. DS7.US11 splits it three ways by what each flag actually
120
+ // needs, rather than treating them as one block.
121
+ //
122
+ // READS -- pure, over the engine DS7.US10 already ported.
123
+ const TS_LIFECYCLE_READ_FLAGS = [
124
+ "--metadata-lifecycle-dry-run",
125
+ "--metadata-lifecycle-preview-at-message",
126
+ ];
127
+
128
+ // WRITES -- both need `apply_metadata_lifecycle`, ~80 lines of unported
129
+ // decision logic (`closeTail.ts` carries the same refusal), and `demo` calls
130
+ // `apply`. Refused BY NAME and assigned to DS7.TS4 by Navigator decision
131
+ // 2026-09-09, rather than silently inheriting a route or being reported as
132
+ // ported.
133
+ const TS4_LIFECYCLE_WRITE_FLAGS = ["--metadata-lifecycle-apply", "--metadata-lifecycle-demo"];
134
+
135
+ // One-shot backfill of pre-ES-001 rows, retired by CV22.DS10.TS4 with a
136
+ // documented cutoff. Named here ONLY for the retired entries below: those match
137
+ // before any family, so a family branch naming these flags would be dead code
138
+ // (TS5 deleted the one that was -- `retiredRouteShadows.ts` keeps it deleted).
139
+ const DS10_BACKFILL_FLAGS = ["--metadata-backfill-preview", "--metadata-backfill-apply"];
140
+
141
+ // CV22.DS7.TS4: the extension catalog family, allowlisted by NAME.
142
+ //
143
+ // `extensions` and `inspect` are claimed commands with subcommands Python may
144
+ // grow, and this family has already paid for inheritance once: `conversations`
145
+ // grew `append` after DS7.US1 claimed the command, and the new subcommand
146
+ // silently rendered a listing and discarded the caller's messages. So every
147
+ // verb TS answers is listed here, and anything else stays Python's.
148
+ export const TS4_EXTENSIONS_VERBS = new Set([
149
+ "list",
150
+ "validate",
151
+ "sync",
152
+ "install",
153
+ "uninstall",
154
+ "expose-claude",
155
+ "clean-claude",
156
+ ]);
157
+
158
+ const TS4_INSPECT_TARGETS = new Set([
159
+ "extension",
160
+ "runtime-catalog",
161
+ "llm-calls",
162
+ "embedding-provenance",
163
+ ]);
164
+
165
+ /**
166
+ * `ext`'s two levels, exported as the AUDITED denominator rather than as a
167
+ * filter.
168
+ *
169
+ * Level one is every head `cmd_ext` recognises as a verb; level two is the four
170
+ * built-in verbs that follow an id. Everything else — `<id>`, `<id> <builtin>`,
171
+ * `<id> <subcommand>` — is the dynamic leaf, whose subcommand belongs to an
172
+ * extension Mirror does not own and therefore cannot be enumerated.
173
+ *
174
+ * Because the route cannot refuse an unknown head (Python reads it as an id),
175
+ * these sets are checked against `cli/ext.py` itself by a route test. That is
176
+ * what keeps the inheritance honest: the day Python grows a verb beside
177
+ * `list`, the test fails instead of the user silently getting `extension not
178
+ * installed: .../doctor`.
179
+ */
180
+ export const TS4_EXT_TOP_LEVEL_VERBS = new Set(["list", "--help", "-h", "help"]);
181
+ export const TS4_EXT_BUILTIN_VERBS = new Set(["bind", "unbind", "bindings", "migrate"]);
182
+
183
+ // CV22.DS7.US8 plateau 8: the 27 Builder leaves ported in plateaus 1–7, plus `show`
184
+ // (CR067 with CR020), the one leaf written new in TypeScript rather than ported.
185
+ // Exported so the route tests can prove that the allowlist and its audited
186
+ // denominator stay the same. The two legacy Workbench groups are deliberately
187
+ // absent: their twenty leaves retire unported in DS10.
188
+ export const TS_BUILD_SUBCOMMANDS = new Set([
189
+ "load",
190
+ "inspect-method",
191
+ "adopt",
192
+ "prepare-templates",
193
+ "sync-cursor",
194
+ "pull-candidates",
195
+ "pull-item",
196
+ "prepare-item",
197
+ "plan-item",
198
+ "approve-plan",
199
+ "cancel-plan-preauthorization",
200
+ "check-implementation",
201
+ "show",
202
+ "validate-item",
203
+ "review-item",
204
+ "coherence-item",
205
+ "done-item",
206
+ "set-flow-unit",
207
+ "plan-delivery-story",
208
+ "approve-delivery-story-plan",
209
+ "cancel-delivery-story-plan-preauthorization",
210
+ "validate-delivery-story",
211
+ "review-delivery-story",
212
+ "coherence-delivery-story",
213
+ "done-delivery-story",
214
+ "set-cadence",
215
+ "release-intent",
216
+ "continue-lifecycle",
217
+ ]);
218
+
219
+ export const TS_BUILD_WORKBENCH_ACTIONS = {
220
+ "refinement-story": new Set([
221
+ "create",
222
+ "overview",
223
+ "pull",
224
+ "review",
225
+ "coherence",
226
+ "close",
227
+ "park",
228
+ ]),
229
+ "change-request": new Set([
230
+ "capture",
231
+ "attach",
232
+ "discard",
233
+ "select",
234
+ "confirm",
235
+ "resume",
236
+ "plan",
237
+ "mark-implemented",
238
+ "validate",
239
+ "done",
240
+ "park",
241
+ "reject",
242
+ "promote",
243
+ ]),
244
+ } as const;
245
+
246
+ /**
247
+ * The surfaces CV22.DS10.TS4 removed, each with the cutoff that explains it.
248
+ *
249
+ * A LIST OF PREDICATES, not a map keyed by command: the six shapes are four
250
+ * different matchers (whole command; command + verb; command + flag; command +
251
+ * subcommand + verb), and a map would push those differences into ad-hoc string
252
+ * checks inside `routeMemoryCommand`.
253
+ *
254
+ * The surfaces TS1, US1, and TS3 retired are deliberately NOT here. Their
255
+ * stories are closed, and the post-Python answer for every unclaimed name is
256
+ * TS5's to shape at once -- adding them now would reopen three done packages
257
+ * for a cosmetic change. (Naming them here would also trip their own
258
+ * retired-surface rows, which is the guard working.)
259
+ */
260
+ export const RETIRED_SURFACES: readonly RetiredSurface[] = [
261
+ {
262
+ surface: "migrate-legacy",
263
+ anchor: "legacy-migration",
264
+ matches: (argv) => argv[0] === "migrate-legacy",
265
+ },
266
+ {
267
+ surface: "journey export-registry",
268
+ anchor: "journey-admin-verbs",
269
+ matches: (argv) => argv[0] === "journey" && argv[1] === "export-registry",
270
+ },
271
+ {
272
+ surface: "journey mutate",
273
+ anchor: "journey-admin-verbs",
274
+ matches: (argv) => argv[0] === "journey" && argv[1] === "mutate",
275
+ },
276
+ {
277
+ surface: "conversations --metadata-backfill-preview",
278
+ anchor: "conversation-metadata-backfill",
279
+ matches: (argv) => argv[0] === "conversations" && argv.includes(DS10_BACKFILL_FLAGS[0]),
280
+ },
281
+ {
282
+ surface: "conversations --metadata-backfill-apply",
283
+ anchor: "conversation-metadata-backfill",
284
+ matches: (argv) => argv[0] === "conversations" && argv.includes(DS10_BACKFILL_FLAGS[1]),
285
+ },
286
+ // CV22.DS10.US2 (D1): the release chain leaves the PRODUCT surface. These
287
+ // two were offered to every installed user and need a git checkout, a clean
288
+ // tree, tags, and push rights -- none of which an installed user has. They
289
+ // are maintainer tooling now, reached through `npm run release:*`, so the
290
+ // old names answer with their cutoff rather than being ported.
291
+ {
292
+ surface: "runtime release-doctor",
293
+ anchor: "release-tooling-leaves-the-product-command-surface",
294
+ matches: (argv) => argv[0] === "runtime" && argv[1] === "release-doctor",
295
+ },
296
+ {
297
+ surface: "runtime release-promote",
298
+ anchor: "release-tooling-leaves-the-product-command-surface",
299
+ matches: (argv) => argv[0] === "runtime" && argv[1] === "release-promote",
300
+ },
301
+ ...(
302
+ [
303
+ "refinement-story",
304
+ "change-request",
305
+ ] as const satisfies readonly (keyof typeof TS_BUILD_WORKBENCH_ACTIONS)[]
306
+ ).flatMap((family) =>
307
+ [...TS_BUILD_WORKBENCH_ACTIONS[family]].map((verb) => ({
308
+ surface: `build ${family} ${verb}`,
309
+ anchor: "the-sqlite-refinement-workbench",
310
+ matches: (argv: readonly string[]) =>
311
+ argv[0] === "build" && argv[1] === family && argv[2] === verb,
312
+ })),
313
+ ),
314
+ ];
315
+
316
+ /** The matched entry, or `null` when nothing this story retired was named. */
317
+ function retiredSurfaceFor(argv: readonly string[]): RetiredSurface | null {
318
+ return RETIRED_SURFACES.find((entry) => entry.matches(argv)) ?? null;
319
+ }
320
+
321
+ /**
322
+ * The one line a caller sees. No traceback, no usage block, no partial output.
323
+ *
324
+ * Every substitution comes from the matched entry, never from argv: a caller
325
+ * cannot push text into this message, and a path passed to a retired command
326
+ * cannot reach a terminal, a log, or a screen recording through it.
327
+ */
328
+ export function retiredRefusal(decision: RouteDecision & { engine: "retired" }): string {
329
+ return (
330
+ `Mirror: '${decision.surface}' was removed in the CV22 migration. ` +
331
+ `See docs/releases/pending-cutoffs.md#${decision.anchor}.\n`
332
+ );
333
+ }
334
+
335
+ // A type alias rather than an interface: aliases get an implicit index
336
+ // signature, which is what lets the named variables below still be passed to
337
+ // `resolveProviderTransport`, whose families name their variables as data.
338
+ //
339
+ // Only what still CHOOSES something is here: the replay fixtures, which choose
340
+ // a test transport, and `MEMORY_RECEPTION`. The `MIRROR_TS_<FAMILY>` revert
341
+ // gates chose an engine, and left with it (CV22.DS10.TS5, D3). A value left in
342
+ // someone's `.env` changes no route; `runtime diagnose` names it inert.
343
+ export type RouteEnvironment = {
344
+ MIRROR_TS_SEARCH_EMBEDDING_REPLAY?: string;
345
+ MIRROR_TS_CONSULT_LLM_REPLAY?: string;
346
+ MIRROR_TS_CREDITS_REPLAY?: string;
347
+ MIRROR_TS_CULTIVATION_LLM_REPLAY?: string;
348
+ MIRROR_TS_CULTIVATION_EMBEDDING_REPLAY?: string;
349
+ MIRROR_TS_MIRROR_LLM_REPLAY?: string;
350
+ MIRROR_TS_MIRROR_EMBEDDING_REPLAY?: string;
351
+ MIRROR_TS_CONVERSATION_LLM_REPLAY?: string;
352
+ MIRROR_TS_CONVERSATION_EMBEDDING_REPLAY?: string;
353
+ MIRROR_TS_SOUL_EMBEDDING_REPLAY?: string;
354
+ MIRROR_TS_BUILD_LLM_REPLAY?: string;
355
+ MIRROR_TS_BUILD_EMBEDDING_REPLAY?: string;
356
+ MIRROR_TS_JOURNAL_LLM_REPLAY?: string;
357
+ MIRROR_TS_JOURNAL_EMBEDDING_REPLAY?: string;
358
+ MIRROR_TS_WEEK_LLM_REPLAY?: string;
359
+ MIRROR_TS_DESCRIPTOR_LLM_REPLAY?: string;
360
+ MEMORY_RECEPTION?: string;
361
+ };
362
+
363
+ // CV22.DS7.US5 slice A, extended by CV22.DS7.US10 slice F. These
364
+ // `conversation-logger` subcommands are deterministic end to end and route to
365
+ // TS under the family switch alone.
366
+ const TS_CONVERSATION_LOGGER_SUBCOMMANDS = new Set([
367
+ "mute",
368
+ "unmute",
369
+ "status",
370
+ "log-user",
371
+ "log-assistant",
372
+ "user-prompt",
373
+ // CV21.DS2.US8: the Stop hook's reply, deterministic like user-prompt.
374
+ "stop",
375
+ "discard-current",
376
+ // US10 slice F, group 2 (need slice E): diagnose, dry-run repair, Codex import.
377
+ "diagnose-journeys",
378
+ "repair-journeys",
379
+ "backfill-codex-session",
380
+ ]);
381
+
382
+ // CV22.DS7.US10 slice F. These reach Python's `end_conversation`, which runs
383
+ // extraction and close-time metadata finalization through the LLM. They route
384
+ // to TS only under the replay transport; an unconfigured install keeps the
385
+ // Python fallback, which is the live-cutover boundary DS8 owns. Flipped in the
386
+ // plan's dependency order: `switch`, `session-end-pi`, `session-end` first
387
+ // (they need slice C' only), then `session-start` and `session-maintenance`
388
+ // (they compose the close tail with slices D and E).
389
+ const TS_CONVERSATION_LOGGER_LLM_SUBCOMMANDS = new Set([
390
+ "switch",
391
+ "session-end-pi",
392
+ "session-end",
393
+ "session-start",
394
+ "session-maintenance",
395
+ ]);
396
+
397
+ // CV22.DS8.US2 staged the live cutover in the order US10 proved and flipped
398
+ // these in: group 1 (`switch`, `session-end-pi`, `session-end`) first, because
399
+ // `session-end` fires unattended from the Pi hook; then group 2
400
+ // (`session-start` full, `session-maintenance`), which composes the close tail
401
+ // WITH backfill and orphan handling and can close several conversations in one
402
+ // run, multiplying any defect.
403
+ //
404
+ // Group 1 was observed live on the real home on 2026-09-11 -- a full close
405
+ // tail with extraction_status=ok, six memories at 1536 dims, and every ledger
406
+ // row priced with bodies withheld -- so the staging has served its purpose and
407
+ // all five subcommands now share one live decision.
408
+
409
+ /** Python's `main()`: strip `--mirror-home X` and `--session-id X`, then `args[0]`. */
410
+ function conversationLoggerSubcommand(argv: readonly string[]): string | undefined {
411
+ const args = [...argv.slice(1)];
412
+ for (const option of ["--mirror-home", "--session-id"]) {
413
+ const index = args.indexOf(option);
414
+ if (index !== -1) args.splice(index, 2);
415
+ }
416
+ return args[0];
417
+ }
418
+
419
+ /**
420
+ * One decision for every provider-backed leaf (CV22.DS8.US3).
421
+ *
422
+ * `MIRROR_TS_EXTERNAL_ROUTES` is GONE, not merely unused. It was DS5's safety
423
+ * catch while replay was the PRODUCTION route for these leaves: requiring an
424
+ * extra opt-in made sense when "routed to TS" meant "answered from a fixture".
425
+ * After the live cutover replay is a test transport, so the gate would only
426
+ * add a second thing to set in CI and a stale value to trip over in a shell.
427
+ * A leftover `=1` or `=0` is inert, and a test pins that.
428
+ */
429
+ function providerRoute(
430
+ command: string | null,
431
+ env: RouteEnvironment,
432
+ spec: ProviderTransportSpec | ComposedProviderTransportSpec,
433
+ leaf?: string,
434
+ ): RouteDecision {
435
+ // A composition resolves three specs at once (`build load` and the one leaf
436
+ // whose tail it is, `explore story promote`); a plain spec resolves one. Both
437
+ // yield the same decision shape, so the reason below is written once.
438
+ const transport =
439
+ "owner" in spec
440
+ ? resolveComposedProviderTransport(env, spec)
441
+ : resolveProviderTransport(env, spec);
442
+ const reason = leaf ? `${transport.reason} (${leaf})` : transport.reason;
443
+ // Every transport answers from TypeScript, `incomplete_replay` included: the
444
+ // route refuses it by name before any provider is built. Until CV22.DS10.TS5
445
+ // a PLAIN family sent it to Python instead -- which has no replay transport
446
+ // and would have called the live provider, the silent spend CR077 made
447
+ // compositions refuse.
448
+ return { command, engine: "ts", reason };
449
+ }
450
+
451
+ export function routeMemoryCommand(
452
+ argv: readonly string[],
453
+ env: RouteEnvironment = process.env,
454
+ ): RouteDecision {
455
+ // Retired surfaces are matched FIRST, before any family claims the command.
456
+ // This is what closes CR089: `journey export-registry` and `journey mutate`
457
+ // can no longer fall into the `journey` status read and be treated as slugs
458
+ // -- a wrong answer with exit 0 on a read, and a silent no-op on a write.
459
+ const retired = retiredSurfaceFor(argv);
460
+ if (retired) {
461
+ return {
462
+ command: argv[0] ?? null,
463
+ engine: "retired",
464
+ reason: `${retired.surface} retired in CV22.DS10.TS4`,
465
+ surface: retired.surface,
466
+ anchor: retired.anchor,
467
+ };
468
+ }
469
+ return routeByFamily(argv, env);
470
+ }
471
+
472
+ /**
473
+ * The family routes, WITHOUT the retired-surface match in front of them.
474
+ *
475
+ * Exported for one reader: the guard that proves no family branch still names
476
+ * a shape a retired entry already answers (CV22.DS10.TS5, inventory F2). A
477
+ * branch like that is dead code nothing can observe -- the retired match wins
478
+ * before it is reached -- so every front-door test passes over it, which is
479
+ * exactly how TS4 left two of them behind. The front door itself always calls
480
+ * `routeMemoryCommand`.
481
+ */
482
+ export function routeByFamily(
483
+ argv: readonly string[],
484
+ env: RouteEnvironment = process.env,
485
+ ): RouteDecision {
486
+ const command = argv[0] ?? null;
487
+ if (!command) return unknownCommand(null);
488
+
489
+ if (TS_READ_COMMANDS.has(command)) {
490
+ return { command, engine: "ts", reason: "DS2 read command ported to TS" };
491
+ }
492
+
493
+ if (command === "memories") {
494
+ if (argv.includes("--search")) {
495
+ // CV22.DS8.US1: fresh semantic search reaches the live provider from TS
496
+ // by default; a replay fixture wins for CI and the smokes. One
497
+ // precedence, shared with every provider-backed family.
498
+ return providerRoute(command, env, SEARCH_TRANSPORT);
499
+ }
500
+ return { command, engine: "ts", reason: "DS2 memory listing read ported to TS" };
501
+ }
502
+
503
+ if (command === "consult") {
504
+ // Per leaf, because the fixtures are: `credits` needs the credits fixture
505
+ // alone, `ask` needs the chat fixture too. With only the credits one set,
506
+ // `credits` replays and `ask` refuses rather than going half-live.
507
+ return argv[1] === "credits"
508
+ ? providerRoute(command, env, CONSULT_CREDITS_TRANSPORT, "credits")
509
+ : providerRoute(command, env, CONSULT_ASK_TRANSPORT, "ask");
510
+ }
511
+
512
+ if (command === "identity") {
513
+ // `set` (DS4), `list`/`get` (DS7.US1), and `edit` (DS7.TS4's editor seam).
514
+ if (argv[1] === "set") {
515
+ return { command, engine: "ts", reason: "DS4 identity set write ported to TS" };
516
+ }
517
+ if (argv[1] === "list" || argv[1] === "get") {
518
+ return { command, engine: "ts", reason: "DS7.US1 identity list/get read ported to TS" };
519
+ }
520
+ if (argv[1] === "edit") {
521
+ return { command, engine: "ts", reason: "DS7.TS4 identity edit ported to TS" };
522
+ }
523
+ // Allowlisted by name rather than inherited: `identity` grew `edit` after
524
+ // `set` claimed the command.
525
+ return unknownSubcommand(command, IDENTITY_SUBCOMMANDS, argv[1]);
526
+ }
527
+
528
+ if (command === "extensions") {
529
+ const verb = argv[1] ?? "list";
530
+ if (!TS4_EXTENSIONS_VERBS.has(verb)) {
531
+ return unknownSubcommand(command, TS4_EXTENSIONS_VERBS, verb);
532
+ }
533
+ return { command, engine: "ts", reason: `DS7.TS4 extensions ${verb} ported to TS` };
534
+ }
535
+
536
+ if (command === "ext") {
537
+ // No verb allowlist is possible here, and saying so is better than writing
538
+ // one that accepts everything: `cmd_ext` treats EVERY head that is not
539
+ // `list` or a help flag as an extension id, so TS must too. The exposure
540
+ // that creates is real and named — a new top-level verb beside `list`
541
+ // would be read as an id and answered `extension not installed` — and it
542
+ // is guarded by a test that reads `cli/ext.py` and fails when that set of
543
+ // literals changes, rather than by a set duplicated here that nothing
544
+ // checks.
545
+ return { command, engine: "ts", reason: "DS7.TS4 ext dispatcher ported to TS" };
546
+ }
547
+
548
+ if (command === "init") {
549
+ return {
550
+ command,
551
+ engine: "ts",
552
+ reason: "DS7.US1 Slice B init (filesystem bootstrap) ported to TS",
553
+ };
554
+ }
555
+
556
+ if (command === "seed") {
557
+ return { command, engine: "ts", reason: "DS7.US1 Slice B seed write ported to TS" };
558
+ }
559
+
560
+ if (command === "recall") {
561
+ return { command, engine: "ts", reason: "DS7.US1 recall read ported to TS" };
562
+ }
563
+
564
+ if (command === "conversations") {
565
+ // The plain listing (DS7.US1) and `append` (DS7.US10) are ported; the
566
+ // metadata-lifecycle/backfill writes are not.
567
+ //
568
+ // `append` (v0.31.13) arrived on main after DS7.US1 claimed this command,
569
+ // and the entry matched every argv shape: the append request routed to TS,
570
+ // rendered a listing, exited 0, and silently discarded the caller's
571
+ // messages. Subcommands of a claimed family must be allowlisted, never
572
+ // inherited -- which is why `append` gets its own explicit entry below
573
+ // even now that it points at TS.
574
+ if (argv[1] === "append") {
575
+ return {
576
+ command,
577
+ engine: "ts",
578
+ reason: "DS7.US10 conversations append boundary ported to TS",
579
+ };
580
+ }
581
+ const writeFlag = TS4_LIFECYCLE_WRITE_FLAGS.find((flag) => argv.includes(flag));
582
+ if (writeFlag) {
583
+ return { command, engine: "ts", reason: `DS7.TS4 ${writeFlag} ported to TS` };
584
+ }
585
+ const readFlag = TS_LIFECYCLE_READ_FLAGS.find((flag) => argv.includes(flag));
586
+ if (readFlag) {
587
+ return {
588
+ command,
589
+ engine: "ts",
590
+ reason: `DS7.US11 ${readFlag} ported to TS`,
591
+ };
592
+ }
593
+ return { command, engine: "ts", reason: "DS7.US1 conversations listing read ported to TS" };
594
+ }
595
+
596
+ if (command === "inspect") {
597
+ // `persona` (DS7.US1) is a deterministic identity read. The other four
598
+ // targets are DS7.TS4's: `extension`/`runtime-catalog` share the catalog
599
+ // machinery, `llm-calls`/`embedding-provenance` read the ledger.
600
+ if (argv[1] === "persona") {
601
+ return { command, engine: "ts", reason: "DS7.US1 inspect persona read ported to TS" };
602
+ }
603
+ if (TS4_INSPECT_TARGETS.has(argv[1] ?? "")) {
604
+ return { command, engine: "ts", reason: `DS7.TS4 inspect ${argv[1]} ported to TS` };
605
+ }
606
+ return unknownSubcommand(command, ["persona", ...TS4_INSPECT_TARGETS], argv[1]);
607
+ }
608
+
609
+ if (command === "list") {
610
+ // `personas`/`journeys` (DS7.US1) are deterministic identity reads;
611
+ // `extensions` and `all` (which is also the no-target default) touch the
612
+ // extension catalog and belong to DS7.TS4.
613
+ if (argv[1] === "personas") {
614
+ return { command, engine: "ts", reason: "DS7.US1 list personas read ported to TS" };
615
+ }
616
+ if (argv[1] === "journeys") {
617
+ return { command, engine: "ts", reason: "DS7.US1 list journeys read ported to TS" };
618
+ }
619
+ const listTarget = argv[1] ?? "all";
620
+ if (listTarget === "extensions" || listTarget === "all") {
621
+ return { command, engine: "ts", reason: `DS7.TS4 list ${listTarget} ported to TS` };
622
+ }
623
+ return unknownSubcommand(command, LIST_TARGETS, listTarget);
624
+ }
625
+
626
+ if (command === "descriptor") {
627
+ // `list` (DS7.US1) is a deterministic read; `generate` calls the LLM
628
+ // (generate_descriptor) and stays on Python as the DS7↔DS8 live seam.
629
+ if (argv[1] === "list") {
630
+ return { command, engine: "ts", reason: "DS7.US1 descriptor list read ported to TS" };
631
+ }
632
+ if (argv[1] === "generate") {
633
+ return providerRoute(command, env, DESCRIPTOR_TRANSPORT, "generate");
634
+ }
635
+ return unknownSubcommand(command, DESCRIPTOR_SUBCOMMANDS, argv[1]);
636
+ }
637
+
638
+ if (command === "tasks") {
639
+ // `list` (and the bare `tasks` default) is a read; `add/done/doing/block/
640
+ // delete` are the deterministic writes ported in DS7.US2 slice 3a;
641
+ // `import/sync/sync-config` (which also touch the journey sync-file/
642
+ // project-path metadata subsystem) are ported in slice 3c.
643
+ //
644
+ // A LEADING OPTION IS NOT A LIST ANY MORE (CV22.DS10.TS5, F5). The rule
645
+ // here used to read `tasks --anything ...` as `list`, which is how
646
+ // `tasks --mirror-home H add "x"` printed the list and wrote nothing. The
647
+ // options the oracle accepts before the subcommand are now moved after it
648
+ // by `argvShape.ts` before routing runs, so one that is still here is one
649
+ // the oracle's parser rejected too -- or `-h`.
650
+ const sub = argv[1];
651
+ if (sub === undefined || sub === "list") {
652
+ return { command, engine: "ts", reason: "DS7.US2 tasks list read ported to TS" };
653
+ }
654
+ if (TASKS_SUBCOMMANDS.includes(sub)) {
655
+ return { command, engine: "ts", reason: "DS7.US2 tasks write ported to TS" };
656
+ }
657
+ return unknownSubcommand(command, TASKS_SUBCOMMANDS, sub);
658
+ }
659
+
660
+ if (command === "week") {
661
+ // `view` (and the bare `week` default) is a deterministic read ported in
662
+ // DS7.US2. CR068 corrected this family's accounting: US2 sent `plan` and
663
+ // `save` to US5, US5 was re-scoped without them, and nothing inherited the
664
+ // work -- and the reason recorded here claimed BOTH were "LLM-gated",
665
+ // which is false for `save`. `save_week_items` reads the pending file and
666
+ // calls `add_task`; it crosses no provider seam. Only `plan` calls a model.
667
+ const sub = argv[1];
668
+ if (sub === undefined || sub === "view") {
669
+ return { command, engine: "ts", reason: "DS7.US2 week view read ported to TS" };
670
+ }
671
+ if (sub === "save") {
672
+ return { command, engine: "ts", reason: "DS7.US11 week save (deterministic) ported to TS" };
673
+ }
674
+ if (sub === "plan") {
675
+ // One model call, no embedding -- so it needs the LLM replay fixture
676
+ // only, unlike `journal` which crosses the seam twice.
677
+ return providerRoute(command, env, WEEK_PLAN_TRANSPORT, "plan");
678
+ }
679
+ return unknownSubcommand(command, WEEK_SUBCOMMANDS, sub);
680
+ }
681
+
682
+ if (command === "consolidate") {
683
+ // `list`/`reject` are deterministic reads/writes -- always TS (DS7.US3).
684
+ // `apply` is gated as a WHOLE on the embedding replay config: its action
685
+ // is read from the DB, not argv, so the routing decision (made before any
686
+ // DB is opened) cannot special-case identity_update/shadow_candidate --
687
+ // the entire command routes together, same principle as `scan`.
688
+ const sub = argv[1];
689
+ if (sub === "list" || sub === "reject") {
690
+ return { command, engine: "ts", reason: "DS7.US3 consolidate list/reject ported to TS" };
691
+ }
692
+ // `apply` sends NO prompt: a merge embeds the merged content and an
693
+ // identity_update makes no provider call at all. So it is not blocked by
694
+ // TS2, unlike `scan`. Gated as a whole because its action is read from the
695
+ // DB, not argv, and the routing decision precedes any DB open.
696
+ if (sub === "apply") {
697
+ return providerRoute(command, env, CULTIVATION_APPLY_TRANSPORT, "apply");
698
+ }
699
+ if (sub === "scan") {
700
+ return providerRoute(command, env, CULTIVATION_SCAN_TRANSPORT, "scan");
701
+ }
702
+ return unknownSubcommand(command, CONSOLIDATE_SUBCOMMANDS, sub);
703
+ }
704
+
705
+ if (command === "shadow") {
706
+ // `list`/`show`/`reject`/`apply` are all deterministic -- `apply`'s write
707
+ // is a hardcoded-layer identity append, no LLM/embedding call, unlike
708
+ // consolidate's `apply` (which may need to embed a merge).
709
+ const sub = argv[1];
710
+ if (sub === "list" || sub === "show" || sub === "reject" || sub === "apply") {
711
+ return {
712
+ command,
713
+ engine: "ts",
714
+ reason: "DS7.US3 shadow list/show/reject/apply ported to TS",
715
+ };
716
+ }
717
+ if (sub === "scan") {
718
+ return providerRoute(command, env, CULTIVATION_SCAN_TRANSPORT, "shadow scan");
719
+ }
720
+ return unknownSubcommand(command, SHADOW_SUBCOMMANDS, sub);
721
+ }
722
+
723
+ if (command === "mirror") {
724
+ const sub = argv[1];
725
+ if (sub === "load") {
726
+ const hasQuery = argv.includes("--query");
727
+ if (!hasQuery) {
728
+ return { command, engine: "ts", reason: "DS7.US4 deterministic mirror load ported to TS" };
729
+ }
730
+ // `MEMORY_RECEPTION=0` skips the classifier on both engines, so the
731
+ // chat fixture stops being required for a replay run; the embedding half
732
+ // still is, because the query still searches attachments and journeys.
733
+ const spec =
734
+ env.MEMORY_RECEPTION === "0"
735
+ ? {
736
+ ...MIRROR_QUERY_TRANSPORT,
737
+ replay: { embedding: MIRROR_QUERY_TRANSPORT.replay?.embedding },
738
+ }
739
+ : MIRROR_QUERY_TRANSPORT;
740
+ return providerRoute(command, env, spec, "load --query");
741
+ }
742
+ if (sub === "deactivate" || sub === "log" || sub === "journeys") {
743
+ return { command, engine: "ts", reason: `DS7.US4 mirror ${sub} ported to TS` };
744
+ }
745
+ return unknownSubcommand(command, MIRROR_SUBCOMMANDS, sub);
746
+ }
747
+
748
+ if (command === "conversation-logger") {
749
+ // CV22.DS7.US5 slice A: flipped 2026-09-02 after the seven-point checklist
750
+ // went green (goldens, real-DB-copy write parity, hook-inclusive E2E,
751
+ // regression pass, redaction, revertibility, ledger).
752
+ const sub = conversationLoggerSubcommand(argv);
753
+ if (sub === "repair-journeys" && argv.includes("--apply")) {
754
+ // The mutating repair takes the dated zip backup (DS7.TS1) before it
755
+ // writes -- the route itself refuses without it.
756
+ return {
757
+ command,
758
+ engine: "ts",
759
+ reason: "DS7.TS1 conversation-logger repair-journeys --apply ported to TS",
760
+ };
761
+ }
762
+ if (sub && TS_CONVERSATION_LOGGER_SUBCOMMANDS.has(sub)) {
763
+ return {
764
+ command,
765
+ engine: "ts",
766
+ reason: `DS7.US5 conversation-logger ${sub} ported to TS`,
767
+ };
768
+ }
769
+ if (sub === "session-start" && argv.includes("--fast")) {
770
+ // Unmute and reorient only: no close tail, so no gate.
771
+ return {
772
+ command,
773
+ engine: "ts",
774
+ reason: "DS7.US10 conversation-logger session-start --fast ported to TS",
775
+ };
776
+ }
777
+ if (sub && TS_CONVERSATION_LOGGER_LLM_SUBCOMMANDS.has(sub)) {
778
+ return providerRoute(command, env, CONVERSATION_TAIL_TRANSPORT, sub);
779
+ }
780
+ // The oracle answered an unknown logger subcommand with NOTHING, exit 0 --
781
+ // a silent success for a hook that named a subcommand that does not exist.
782
+ // It now gets the family's answer like every other family (D2, F6).
783
+ return unknownSubcommand(
784
+ command,
785
+ [...TS_CONVERSATION_LOGGER_SUBCOMMANDS, ...TS_CONVERSATION_LOGGER_LLM_SUBCOMMANDS],
786
+ sub,
787
+ );
788
+ }
789
+
790
+ if (command === "mode") {
791
+ const sub = argv.find((value, index) => index > 0 && MODE_SUBCOMMANDS.includes(value));
792
+ return sub
793
+ ? { command, engine: "ts", reason: "DS7.US4 operating mode lifecycle ported to TS" }
794
+ : unknownSubcommand(command, MODE_SUBCOMMANDS, modeGivenSubcommand(argv));
795
+ }
796
+
797
+ if (command === "journey") {
798
+ // `set-path` (DS4), `update` (DS7.US1 Slice B), and the status read
799
+ // (DS7.US1 Slice A) are all ported. Everything besides `set-path`/
800
+ // `update` -- `status [slug]`, a bare slug, or no argument at all -- is a
801
+ // status read in the real Python dispatch (see render/journeyStatus.ts's
802
+ // slug-resolution quirk), so it all routes to the same TS status handler.
803
+ if (argv[1] === "set-path") {
804
+ return { command, engine: "ts", reason: "DS4 journey set-path write ported to TS" };
805
+ }
806
+ if (argv[1] === "update") {
807
+ return { command, engine: "ts", reason: "DS7.US1 Slice B journey update write ported to TS" };
808
+ }
809
+ return { command, engine: "ts", reason: "DS7.US1 journey status read ported to TS" };
810
+ }
811
+
812
+ if (command === "backup") {
813
+ return { command, engine: "ts", reason: "DS7.TS1 backup ported to TS" };
814
+ }
815
+
816
+ if (command === "repair-encoding") {
817
+ return { command, engine: "ts", reason: "DS7.TS1 repair-encoding ported to TS" };
818
+ }
819
+
820
+ if (command === "welcome") {
821
+ return { command, engine: "ts", reason: "DS7.TS3 welcome ported to TS" };
822
+ }
823
+
824
+ if (command === "runtime") {
825
+ const subcommand = argv[1] ?? "";
826
+ // Allowlist, not blocklist: a subcommand this build has never heard of
827
+ // must not acquire a TS route because `runtime` already has one.
828
+ if (!TS_RUNTIME_READ_SUBCOMMANDS.has(subcommand)) {
829
+ // CV22.DS10.US2: the updater family.
830
+ const reason = TS_RUNTIME_UPDATE_SUBCOMMANDS.get(subcommand);
831
+ if (reason !== undefined) return { command, engine: "ts", reason };
832
+ // CV22.DS10.US2 made the unknown answer TypeScript's own, rendered by
833
+ // the runtime route; CV22.DS10.TS5 moved it to the shared usage answer
834
+ // (D2), which renders the same bytes for every family.
835
+ return unknownSubcommand(command, RUNTIME_SUBCOMMANDS, subcommand);
836
+ }
837
+ return { command, engine: "ts", reason: `DS7.TS3 runtime ${subcommand} ported to TS` };
838
+ }
839
+
840
+ if (command === "soul") {
841
+ const subcommand = argv[1] ?? "";
842
+ // Allowlist by NAME, like `runtime`, and for the same reason: a subcommand
843
+ // the route never implemented must not inherit it because the family is
844
+ // claimed. That is the `conversations append` defect (RS009/CR055), which
845
+ // exited 0 and discarded the caller's payload.
846
+ if (!TS_SOUL_SUBCOMMANDS.has(subcommand)) {
847
+ return unknownSubcommand(command, TS_SOUL_SUBCOMMANDS, subcommand);
848
+ }
849
+ // `harvest save` is the one leaf that crosses the provider seam, through
850
+ // the embedding alone.
851
+ if (subcommand === "harvest" && soulHarvestAction(argv) === "save") {
852
+ return providerRoute(command, env, SOUL_HARVEST_TRANSPORT, "harvest save");
853
+ }
854
+ return { command, engine: "ts", reason: `DS7.US6 soul ${subcommand} ported to TS` };
855
+ }
856
+
857
+ if (command === "explore") {
858
+ const subcommand = argv[1] ?? "";
859
+ // TWO levels, because `explore` is the first family with a nested
860
+ // subparser. A single-level allowlist would claim `explore story
861
+ // <anything>` and answer an argv shape this route has never implemented.
862
+ if (!TS_EXPLORE_SUBCOMMANDS.has(subcommand)) {
863
+ return unknownSubcommand(command, TS_EXPLORE_SUBCOMMANDS, subcommand);
864
+ }
865
+ if (subcommand === "story") {
866
+ const action = exploreStoryAction(argv);
867
+ if (!TS_EXPLORE_STORY_ACTIONS.has(action ?? "")) {
868
+ return unknownSubcommand(command, TS_EXPLORE_STORY_ACTIONS, action, "explore story");
869
+ }
870
+ }
871
+ // `story promote` ends in a Builder session start (`cmd_story_promote`
872
+ // calls `cmd_load`), so it answers to the SAME composed transport decision
873
+ // `build load` does -- resolved here rather than inside the route because
874
+ // promote's writes are not idempotent, so a half-configured replay harness
875
+ // must be refused before the first mutation, not after.
876
+ if (subcommand === "story" && exploreStoryAction(argv) === "promote") {
877
+ return providerRoute(command, env, BUILD_LOAD_COMPOSITION, "story promote");
878
+ }
879
+ return { command, engine: "ts", reason: `DS7.US7 explore ${subcommand} ported to TS` };
880
+ }
881
+
882
+ if (command === "build") {
883
+ const subcommand = argv[1] ?? "";
884
+ // `refinement-story` and `change-request` are not build subcommands: the
885
+ // twenty verbs they had are answered by the retired table before this
886
+ // line, and anything else under them is an unknown build subcommand --
887
+ // what the oracle itself has answered since TS4 deleted the groups.
888
+ if (!TS_BUILD_SUBCOMMANDS.has(subcommand)) {
889
+ return unknownSubcommand(command, TS_BUILD_SUBCOMMANDS, subcommand);
890
+ }
891
+ // `load` composes the search and conversation-tail provider families. Its
892
+ // transport must be decided before the banner or any surface is printed.
893
+ if (subcommand === "load") {
894
+ return providerRoute(command, env, BUILD_LOAD_COMPOSITION, "load");
895
+ }
896
+ return { command, engine: "ts", reason: `DS7.US8 build ${subcommand} ported to TS` };
897
+ }
898
+
899
+ if (command === "journal") {
900
+ return providerRoute(command, env, JOURNAL_TRANSPORT);
901
+ }
902
+
903
+ if (command === "mcp") {
904
+ // CV22.DS10.TS5 (F9): the server DS9 ported, reached through the front
905
+ // door as the oracle's `mcp` was. The plugin launches it directly; this is
906
+ // the same server, not a second one.
907
+ return { command, engine: "ts", reason: "DS9 MCP server, launched through the front door" };
908
+ }
909
+
910
+ return unknownCommand(command);
911
+ }
912
+
913
+ // The subcommands of families whose routes above test them one by one. Named
914
+ // here so the usage answer lists exactly what the routes answer.
915
+ const IDENTITY_SUBCOMMANDS = ["list", "get", "set", "edit"];
916
+ const LIST_TARGETS = ["personas", "journeys", "extensions", "all"];
917
+ const DESCRIPTOR_SUBCOMMANDS = ["generate", "list"];
918
+ const TASKS_SUBCOMMANDS = [
919
+ "list",
920
+ "add",
921
+ "done",
922
+ "doing",
923
+ "block",
924
+ "import",
925
+ "delete",
926
+ "sync",
927
+ "sync-config",
928
+ ];
929
+ const WEEK_SUBCOMMANDS = ["view", "plan", "save"];
930
+ const CONSOLIDATE_SUBCOMMANDS = ["scan", "apply", "reject", "list"];
931
+ const SHADOW_SUBCOMMANDS = ["scan", "apply", "reject", "list", "show"];
932
+ const MIRROR_SUBCOMMANDS = ["load", "deactivate", "log", "journeys"];
933
+ const MODE_SUBCOMMANDS = ["activate", "deactivate", "status"];
934
+
935
+ /** `mode`'s first positional, past the two options its parser declares; "" when none. */
936
+ function modeGivenSubcommand(argv: readonly string[]): string {
937
+ const args = [...argv.slice(1)];
938
+ for (const option of ["--mirror-home", "--session-id"]) {
939
+ const index = args.indexOf(option);
940
+ if (index !== -1) args.splice(index, 2);
941
+ }
942
+ return args[0] ?? "";
943
+ }
944
+
945
+ // Python's argparse subcommands for `soul`, by name.
946
+ const TS_SOUL_SUBCOMMANDS = new Set([
947
+ "load",
948
+ "listen",
949
+ "rite",
950
+ "close",
951
+ "review",
952
+ "propose",
953
+ "apply",
954
+ "fruit",
955
+ "harvest",
956
+ "prompt",
957
+ ]);
958
+
959
+ // Python's argparse subcommands for `explore`, by name.
960
+ const TS_EXPLORE_SUBCOMMANDS = new Set(["load", "deactivate", "story"]);
961
+
962
+ // Python's `explore story` actions, by name.
963
+ //
964
+ // `promote` joined at CV22.DS7.US8 plateau 7, having waited on Python by name
965
+ // since US7 because `cmd_story_promote` ends by calling Builder `load`. It is
966
+ // the one action here whose engine also depends on the provider composition;
967
+ // see the decision above.
968
+ const TS_EXPLORE_STORY_ACTIONS = new Set([
969
+ "promote",
970
+ "show",
971
+ "list",
972
+ "archive",
973
+ "update",
974
+ "clear",
975
+ "open",
976
+ "thicken",
977
+ "snapshot",
978
+ "attractors",
979
+ "experiment",
980
+ "handoff",
981
+ ]);
982
+
983
+ /** `explore story <action>`, skipping the options argparse strips first. */
984
+ function exploreStoryAction(argv: readonly string[]): string | undefined {
985
+ const args = [...argv.slice(2)];
986
+ for (const option of ["--mirror-home", "--db-path", "--session-id"]) {
987
+ const index = args.indexOf(option);
988
+ if (index !== -1) args.splice(index, 2);
989
+ }
990
+ return args[0];
991
+ }
992
+
993
+ /** `soul harvest <action>`, skipping the options argparse strips first. */
994
+ function soulHarvestAction(argv: readonly string[]): string | undefined {
995
+ const args = [...argv.slice(2)];
996
+ for (const option of ["--mirror-home", "--db-path", "--session-id", "--journey"]) {
997
+ const index = args.indexOf(option);
998
+ if (index !== -1) args.splice(index, 2);
999
+ }
1000
+ return args[0];
1001
+ }