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,214 @@
1
+ // Claude Code hook entries (CV22.DS10.TS5 plateau 1, decision D4).
2
+ //
3
+ // Replaces the bodies of `.claude/hooks/*.sh` and the identical set under
4
+ // `plugins/mirror-mind/hooks/`, which spawned the Python interpreter for the
5
+ // core's CLI, for two hook modules, and for an inline import of the
6
+ // conversation logger's `hook_user_prompt` -- the last two reaching into
7
+ // modules that were never commands, and so never in DS7's denominator.
8
+ //
9
+ // Behavior is preserved case for case, including the cases that look like
10
+ // bugs and are not: a skill invocation is still logged (Claude's hook fires
11
+ // for every prompt and the logger decides), a missing session id still fails
12
+ // loud, and every failure is still non-fatal to the user's turn.
13
+
14
+ import { resolveDbPath } from "#frontDoor/dbPath.ts";
15
+ import { openLiveWriteDatabase } from "#frontDoor/liveBackup.ts";
16
+ import { mirrorForm } from "#mirror/mirrorForm.ts";
17
+ import { markInjected, needsInject, readMirrorState } from "./mirrorState.ts";
18
+ import { parseHookPayload, readStdin } from "./payload.ts";
19
+ import {
20
+ noteHookFailure,
21
+ operatingInstructionsFor,
22
+ runFrontDoor,
23
+ runFrontDoorQuietly,
24
+ sessionStartOutput,
25
+ } from "./runtime.ts";
26
+ import { detectHookRuntime } from "./runtimeDetection.ts";
27
+
28
+ /**
29
+ * `session-start.sh`: make sure logging is on for this session, and hand
30
+ * Claude Code the Operating Instructions unless the project it opened in is
31
+ * this same tree (US3, D13 -- `operatingInstructionsFor`). The plugin in
32
+ * Claude Code's cache delivers them this way; the checkout's own wrappers
33
+ * find the project already carrying the file and stay quiet, as before.
34
+ */
35
+ export async function claudeSessionStart(env: NodeJS.ProcessEnv = process.env): Promise<number> {
36
+ await runFrontDoorQuietly("claude:session-start", ["conversation-logger", "session-start"]);
37
+ process.stdout.write(
38
+ sessionStartOutput(operatingInstructionsFor("claude:session-start", env.CLAUDE_PROJECT_DIR)),
39
+ );
40
+ return 0;
41
+ }
42
+
43
+ /**
44
+ * `log-user-prompt.sh`: record the user's turn, under the runtime's own label.
45
+ *
46
+ * The shell piped stdin into `hook_user_prompt()`, and the front door owns
47
+ * that surface: `conversation-logger user-prompt` reads the payload itself.
48
+ * Since CV21.DS2.US8 the entry reads it first, to tell which runtime it is in
49
+ * (`runtimeDetection.ts`), and hands it to the route through the one-shot
50
+ * stdin seam, so fd 0 is read once.
51
+ */
52
+ export async function claudeUserPrompt(stdin: string): Promise<number> {
53
+ const runtime = detectHookRuntime(parseHookPayload(stdin).raw);
54
+ await runFrontDoorQuietly(
55
+ "claude:user-prompt",
56
+ ["conversation-logger", "user-prompt", "--interface", runtime],
57
+ { stdin },
58
+ );
59
+ return 0;
60
+ }
61
+
62
+ /**
63
+ * `log-assistant-reply.sh`, on Stop: record the turn's final answer beside
64
+ * its prompt (CV21.DS2.US8). Every Claude-schema runtime hands its Stop hook
65
+ * `last_assistant_message` (DS12.TS2 L11, TS4 L8; Claude Code 2.1.296's
66
+ * binary), so the reply needs no transcript.
67
+ *
68
+ * This hook prints NOTHING and exits 0, whatever happens: a Stop hook that
69
+ * printed JSON or exited 2 would keep the agent working instead of logging.
70
+ * The route's outcome line is captured in-process and never forwarded. Only a
71
+ * reply lost for no known reason -- its turn's prompt neither logged nor
72
+ * skipped as a command, the race the plan names -- is recorded in `hooks.log`,
73
+ * the fact and never the text: `runtime diagnose` reads that file as failures,
74
+ * and a command turn's reply or a session-scoped Stop is not one.
75
+ */
76
+ export async function claudeStop(stdin: string): Promise<number> {
77
+ const runtime = detectHookRuntime(parseHookPayload(stdin).raw);
78
+ const outcome = await runFrontDoorQuietly(
79
+ "claude:stop",
80
+ ["conversation-logger", "stop", "--interface", runtime],
81
+ { stdin },
82
+ );
83
+ const line = outcome.trim();
84
+ if (line.startsWith("reply skipped: nothing_waiting") || line.startsWith("reply failed")) {
85
+ noteHookFailure("claude:stop", line);
86
+ }
87
+ return 0;
88
+ }
89
+
90
+ /** `log-session-end.sh`: close the session, then take the silent backup. */
91
+ export async function claudeSessionEnd(): Promise<number> {
92
+ await runFrontDoorQuietly("claude:session-end", ["conversation-logger", "session-end"]);
93
+ await runFrontDoorQuietly("claude:session-end", ["backup", "--silent"]);
94
+ return 0;
95
+ }
96
+
97
+ /**
98
+ * `mirror-inject.sh`: inject identity context before the model answers.
99
+ *
100
+ * Two cases, exactly as the shell had them:
101
+ *
102
+ * 1. an explicit Mirror form -- `/mm:mirror [query]`, or the same skill in
103
+ * any runtime's spelling (`#mirror/mirrorForm.ts`; CV21.DS2.US7) -- load context
104
+ * for that query;
105
+ * 2. Mirror Mode already active for this session with context not yet
106
+ * injected -- load it with the session's persona and journey, then mark
107
+ * it injected so it happens once.
108
+ *
109
+ * `--context-only` matters in both: it loads context WITHOUT starting a
110
+ * conversation session, because the model does that. A hook that silently
111
+ * opened conversations would fork every session in two.
112
+ */
113
+ export async function claudeInject(stdin: string): Promise<number> {
114
+ const payload = parseHookPayload(stdin);
115
+ const sessionId = payload.sessionId;
116
+
117
+ // Case 1: the explicit invocation.
118
+ const explicit = mirrorForm(payload.prompt);
119
+ if (explicit) {
120
+ const argv = ["mirror", "load", "--context-only", "--query", explicit.query];
121
+ if (sessionId) argv.push("--session-id", sessionId);
122
+ const result = await runFrontDoor(argv);
123
+ if (result.stdout.trim()) process.stdout.write(result.stdout);
124
+ return 0;
125
+ }
126
+
127
+ // Case 2: active Mirror Mode owed an injection.
128
+ if (!sessionId) {
129
+ // Python's `mirror_state` warned on stderr rather than no-op'ing silently,
130
+ // so a hook wired without a session id is visible. Kept: it is the
131
+ // difference between "Mirror Mode is off" and "this hook is misconfigured".
132
+ process.stderr.write("warning: mirror inject hook received no session_id; no state read.\n");
133
+ return 0;
134
+ }
135
+
136
+ let dbPath: string;
137
+ try {
138
+ dbPath = resolveDbPath([], process.env);
139
+ } catch (error) {
140
+ noteHookFailure("claude:inject", error instanceof Error ? error.message : String(error));
141
+ return 0;
142
+ }
143
+
144
+ const state = readMirrorState(dbPath, sessionId);
145
+ if (!needsInject(state)) return 0;
146
+
147
+ const argv = [
148
+ "mirror",
149
+ "load",
150
+ "--context-only",
151
+ "--query",
152
+ payload.prompt,
153
+ "--session-id",
154
+ sessionId,
155
+ ];
156
+ if (state.persona) argv.push("--persona", state.persona);
157
+ if (state.journey) argv.push("--journey", state.journey);
158
+
159
+ const result = await runFrontDoor(argv);
160
+ if (!result.stdout.trim()) return 0;
161
+
162
+ process.stdout.write(result.stdout);
163
+ // Only after the context actually reached the model: the shell marked it
164
+ // inside `if [ -n "$CONTEXT" ]` for the same reason. Marking first would
165
+ // lose the injection whenever the load produced nothing.
166
+ try {
167
+ // The sanctioned live-write path: a verified pre-write snapshot, same as
168
+ // every other routed write.
169
+ //
170
+ // Python's `mark_injected` wrote UNGATED, so this adds a `VACUUM INTO`
171
+ // that the old hook did not pay. Measured on a real 53 MB database:
172
+ // **95 ms**, once per Mirror Mode session -- only the single prompt that
173
+ // owes an injection reaches this line, and that turn is already loading
174
+ // identity and memories. The ordinary turn returns above without opening
175
+ // a writable handle at all (114 ms end to end, nothing written).
176
+ //
177
+ // Paying the front door's own safety contract for a real write to a real
178
+ // user database beat inventing a second narrowed write handle inside a
179
+ // deletion story. Recorded with the number so the trade is visible.
180
+ //
181
+ // This hook runs AT THE SAME TIME as `log-user-prompt`, which writes too.
182
+ // Until finding F21 the two snapshots collided on one fixed file and one
183
+ // write was lost -- in the Navigator walk, two injections in three
184
+ // prompts. `openLiveWriteDatabase` gives each writer its own staging
185
+ // snapshot, so concurrent hooks no longer race.
186
+ const db = openLiveWriteDatabase(dbPath);
187
+ try {
188
+ markInjected(db, sessionId, new Date().toISOString());
189
+ } finally {
190
+ db.close();
191
+ }
192
+ } catch (error) {
193
+ noteHookFailure("claude:inject", error instanceof Error ? error.message : String(error));
194
+ }
195
+ return 0;
196
+ }
197
+
198
+ export async function claudeHook(name: string): Promise<number> {
199
+ switch (name) {
200
+ case "session-start":
201
+ return claudeSessionStart();
202
+ case "user-prompt":
203
+ return claudeUserPrompt(readStdin());
204
+ case "stop":
205
+ return claudeStop(readStdin());
206
+ case "session-end":
207
+ return claudeSessionEnd();
208
+ case "inject":
209
+ return claudeInject(readStdin());
210
+ default:
211
+ noteHookFailure("claude", `unknown hook: ${name}`);
212
+ return 0;
213
+ }
214
+ }
@@ -0,0 +1,45 @@
1
+ // Codex hook entries (CV22.DS10.TS5 handoff review, finding N3).
2
+ //
3
+ // Codex has no hook system, so `scripts/codex-mirror.sh` wraps the `codex`
4
+ // command itself and calls these through two generated wrappers under
5
+ // `scripts/codex-hooks/`. Until the handoff review the script called the front
6
+ // door directly: it found `node` on PATH alone, silenced every failure, and
7
+ // built its command as a string that a checkout path with a space split in
8
+ // two. Going through the wrappers gives it the same Node resolution and the
9
+ // same hooks.log record as every other runtime.
10
+ //
11
+ // The payload comes as arguments, not stdin: the wrapper, not a runtime,
12
+ // decides what to pass, and a path is safer as one quoted argument than
13
+ // embedded in hand-written JSON.
14
+
15
+ import { runFrontDoorQuietly } from "./runtime.ts";
16
+
17
+ /** Wrapper start: make sure logging is on for this session. */
18
+ export async function codexSessionStart(): Promise<number> {
19
+ await runFrontDoorQuietly("codex:session-start", ["conversation-logger", "session-start"]);
20
+ return 0;
21
+ }
22
+
23
+ /**
24
+ * Wrapper exit: import the session Codex wrote, close it, then take the silent
25
+ * backup. The transcript and its session id are both absent when Codex wrote
26
+ * no session for this directory; the backup runs either way, as it always has.
27
+ */
28
+ export async function codexSessionEnd(transcriptPath: string, sessionId: string): Promise<number> {
29
+ if (transcriptPath && sessionId) {
30
+ await runFrontDoorQuietly("codex:session-end", [
31
+ "conversation-logger",
32
+ "backfill-codex-session",
33
+ transcriptPath,
34
+ "--interface",
35
+ "codex",
36
+ ]);
37
+ await runFrontDoorQuietly("codex:session-end", [
38
+ "conversation-logger",
39
+ "session-end-pi",
40
+ sessionId,
41
+ ]);
42
+ }
43
+ await runFrontDoorQuietly("codex:session-end", ["backup", "--silent"]);
44
+ return 0;
45
+ }
@@ -0,0 +1,126 @@
1
+ // Gemini CLI hook entries (CV22.DS10.TS5 plateau 1, decision D4).
2
+ //
3
+ // These hooks already entered the TypeScript front door for every Mirror
4
+ // command -- the Python they carried was only inline interpreter one-liners
5
+ // importing `sys` and `json`, to read a field out of the payload and to print
6
+ // one back. Four spawns of an interpreter to parse JSON, in a Node process's
7
+ // hook.
8
+ //
9
+ // The Gemini contract, preserved exactly:
10
+ // stdin -- JSON: { session_id, prompt, ... }
11
+ // stdout -- JSON: { hookSpecificOutput: { additionalContext } } or {}
12
+ // stderr -- logs only; never parsed
13
+ //
14
+ // `GEMINI_SESSION_ID` still wins over the payload's `session_id`, because the
15
+ // runtime sets it and the payload is the fallback.
16
+
17
+ import { parseHookPayload } from "./payload.ts";
18
+ import {
19
+ operatingInstructionsFor,
20
+ runFrontDoor,
21
+ runFrontDoorQuietly,
22
+ sessionStartOutput,
23
+ } from "./runtime.ts";
24
+
25
+ function sessionIdFrom(payloadSessionId: string, env = process.env): string {
26
+ return env.GEMINI_SESSION_ID || payloadSessionId;
27
+ }
28
+
29
+ /**
30
+ * BeforeAgent: log the user's turn, and inject context when Mirror Mode is on.
31
+ *
32
+ * A prompt starting with `/` is a skill invocation -- a command, not a
33
+ * conversational message -- and is not logged. That rule is Gemini-specific
34
+ * and stays; Claude's hook has no equivalent because its logger decides.
35
+ */
36
+ export async function geminiLogUser(stdin: string): Promise<number> {
37
+ const payload = parseHookPayload(stdin);
38
+ const sessionId = sessionIdFrom(payload.sessionId);
39
+
40
+ if (payload.prompt.startsWith("/")) {
41
+ process.stdout.write("{}\n");
42
+ return 0;
43
+ }
44
+
45
+ if (payload.prompt && sessionId) {
46
+ await runFrontDoorQuietly("gemini:log-user", [
47
+ "conversation-logger",
48
+ "log-user",
49
+ sessionId,
50
+ payload.prompt,
51
+ "--interface",
52
+ "gemini_cli",
53
+ ]);
54
+ }
55
+
56
+ const context = await runFrontDoor([
57
+ "mirror",
58
+ "load",
59
+ "--context-only",
60
+ "--query",
61
+ payload.prompt,
62
+ "--session-id",
63
+ sessionId,
64
+ ]);
65
+
66
+ process.stdout.write(
67
+ context.stdout.trim()
68
+ ? `${JSON.stringify({ hookSpecificOutput: { additionalContext: context.stdout } })}\n`
69
+ : "{}\n",
70
+ );
71
+ return 0;
72
+ }
73
+
74
+ /** AfterAgent: record the assistant's turn. */
75
+ export async function geminiLogAssistant(stdin: string): Promise<number> {
76
+ const payload = parseHookPayload(stdin);
77
+ const sessionId = sessionIdFrom(payload.sessionId);
78
+ if (payload.response && sessionId) {
79
+ await runFrontDoorQuietly("gemini:log-assistant", [
80
+ "conversation-logger",
81
+ "log-assistant",
82
+ sessionId,
83
+ payload.response,
84
+ "--interface",
85
+ "gemini_cli",
86
+ ]);
87
+ }
88
+ process.stdout.write("{}\n");
89
+ return 0;
90
+ }
91
+
92
+ /** SessionEnd: close the conversation for this session. */
93
+ export async function geminiSessionEnd(stdin: string): Promise<number> {
94
+ const payload = parseHookPayload(stdin);
95
+ const sessionId = sessionIdFrom(payload.sessionId);
96
+ if (sessionId) {
97
+ await runFrontDoorQuietly("gemini:session-end", [
98
+ "conversation-logger",
99
+ "session-end-pi",
100
+ sessionId,
101
+ ]);
102
+ }
103
+ process.stdout.write("{}\n");
104
+ return 0;
105
+ }
106
+
107
+ /**
108
+ * SessionStart: unmute logging for the new session.
109
+ *
110
+ * Fires on startup, resume, and `/clear`. This wrapper was already free of
111
+ * Python -- it entered the front door directly -- but it is generated from the
112
+ * same template as the other eleven so it cannot drift away from them, which
113
+ * is the failure CR071 recorded for the skill copies.
114
+ */
115
+ export async function geminiSessionStart(env: NodeJS.ProcessEnv = process.env): Promise<number> {
116
+ await runFrontDoorQuietly("gemini:session-start", ["conversation-logger", "session-start"]);
117
+ // US3, D13: the Operating Instructions as SessionStart context, unless the
118
+ // project is this tree -- the checkout's GEMINI/CLAUDE.md import the file.
119
+ process.stdout.write(
120
+ sessionStartOutput(
121
+ operatingInstructionsFor("gemini:session-start", env.GEMINI_PROJECT_DIR),
122
+ "{}\n",
123
+ ),
124
+ );
125
+ return 0;
126
+ }
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+
3
+ // The single entry point every Mirror hook wrapper calls.
4
+ //
5
+ // CV22.DS10.TS5 plateau 1, decision D4. Twelve shell scripts across four
6
+ // runtimes each spawned the interpreter one to five times per event. They now
7
+ // run one Node process and name which hook they are:
8
+ //
9
+ // node ts/src/hooks/main.ts claude:inject
10
+ //
11
+ // Arguments after the name are the hook's own; only the Codex wrapper passes
12
+ // any, since Codex has no hook payload to read (`codex.ts`).
13
+ //
14
+ // One entry rather than one file per hook because the generated wrappers then
15
+ // differ only in the hook name and their depth, and `hooks.test.ts` asserts
16
+ // that -- copies that can differ in only one place cannot drift the way the
17
+ // skill copies did (CR071, eleven skills, two runtimes wrong for months).
18
+ //
19
+ // Since CV22.DS10.US3 (D3, D5) the wrappers run this file through the
20
+ // `mirror-hook` bin (`bin/mirror-hook.js`), and it reads configuration and
21
+ // silences the sqlite warning itself, in `runAsEntry()`: the `--env-file`
22
+ // and `--no-warnings` flags the wrappers used to pass could only ever point
23
+ // at a checkout's `.env`, and an installed package has none.
24
+ //
25
+ // **A hook never fails the user's turn.** Every path returns 0 and every
26
+ // failure is recorded in `<mirror-home>/hooks.log` instead. That was the
27
+ // Python posture too (`2>/dev/null || true`), with one difference that
28
+ // matters: silence there hid nothing, because `python3` is on every macOS.
29
+ // Node is often absent from a GUI-launched runtime's PATH, so silence here
30
+ // would hide a NEW failure -- which is why the wrappers write the log line
31
+ // themselves when they cannot find a Node that runs this file. That is also
32
+ // why this file must exit 0: the wrappers read any other exit as Node failing.
33
+
34
+ import { pathToFileURL } from "node:url";
35
+ import { loadConfiguration, silenceExperimentalWarnings } from "#runtime/config.ts";
36
+ import { claudeHook } from "./claude.ts";
37
+ import { codexSessionEnd, codexSessionStart } from "./codex.ts";
38
+ import {
39
+ geminiLogAssistant,
40
+ geminiLogUser,
41
+ geminiSessionEnd,
42
+ geminiSessionStart,
43
+ } from "./gemini.ts";
44
+ import { readStdin } from "./payload.ts";
45
+ import { noteHookFailure } from "./runtime.ts";
46
+
47
+ async function dispatch(name: string, args: readonly string[]): Promise<number> {
48
+ const [runtime, hook] = name.split(":");
49
+
50
+ switch (runtime) {
51
+ case "claude":
52
+ return claudeHook(hook ?? "");
53
+ case "gemini":
54
+ switch (hook) {
55
+ case "log-user":
56
+ return geminiLogUser(readStdin());
57
+ case "log-assistant":
58
+ return geminiLogAssistant(readStdin());
59
+ case "session-end":
60
+ return geminiSessionEnd(readStdin());
61
+ case "session-start":
62
+ return geminiSessionStart();
63
+ default:
64
+ break;
65
+ }
66
+ break;
67
+ case "codex":
68
+ // Called by `scripts/codex-mirror.sh` through its generated wrappers.
69
+ switch (hook) {
70
+ case "session-start":
71
+ return codexSessionStart();
72
+ case "session-end":
73
+ return codexSessionEnd(args[0] ?? "", args[1] ?? "");
74
+ default:
75
+ break;
76
+ }
77
+ break;
78
+ default:
79
+ break;
80
+ }
81
+
82
+ noteHookFailure("hooks", `unknown hook name: ${name}`);
83
+ return 0;
84
+ }
85
+
86
+ /**
87
+ * What the entry does, for the two ways it is started: this file run directly
88
+ * (a checkout), or the `bin/mirror-hook.js` shim (D15). Configuration and the
89
+ * warning filter run here and nowhere else, so importing this module does
90
+ * neither.
91
+ */
92
+ export async function runAsEntry(rawArgv = process.argv.slice(2)): Promise<number> {
93
+ silenceExperimentalWarnings();
94
+ loadConfiguration({ entryPath: import.meta.filename });
95
+ const name = rawArgv[0] ?? "";
96
+ try {
97
+ return await dispatch(name, rawArgv.slice(1));
98
+ } catch (error) {
99
+ // The last line of defense. A hook that throws must still not break the
100
+ // turn, and must still leave a trace that it happened.
101
+ noteHookFailure(name || "hooks", error instanceof Error ? error.message : String(error));
102
+ return 0;
103
+ }
104
+ }
105
+
106
+ // Run only when invoked as the entry, not when imported.
107
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
108
+ process.exitCode = await runAsEntry();
109
+ }
@@ -0,0 +1,71 @@
1
+ // Mirror Mode state for one runtime session, as the inject hook needs it.
2
+ //
3
+ // The TypeScript replacement for the Python hook module `mirror_state` (144
4
+ // lines, deleted with the core at CV22.DS10.TS5), which
5
+ // `mirror-inject.sh` invoked up to three times per prompt -- `needs-inject`,
6
+ // `get persona`, `get journey` -- each a separate interpreter start, each
7
+ // re-opening the database and re-running bootstrap.
8
+ //
9
+ // It is NOT ported as a front-door command. Its only caller was ever that one
10
+ // script, and the state it reads already belongs to `#mirror/runtimeSession.ts`;
11
+ // adding two commands whose sole consumer is a hook would widen the command
12
+ // surface this story exists to shrink.
13
+ //
14
+ // One Python behavior is deliberately kept, because it is a real safeguard:
15
+ // a missing session id FAILS LOUD on stderr rather than silently answering
16
+ // "nothing to do". A hook integration that forgets to pass one would otherwise
17
+ // look like a user who simply has Mirror Mode off.
18
+
19
+ import { openDatabaseReadOnly, type WritableDatabase } from "#db/database.ts";
20
+ import { getRuntimeSession, upsertRuntimeSession } from "#mirror/runtimeSession.ts";
21
+
22
+ export interface MirrorHookState {
23
+ readonly active: boolean;
24
+ readonly hookInjected: boolean;
25
+ readonly persona: string;
26
+ readonly journey: string;
27
+ }
28
+
29
+ const NO_STATE: MirrorHookState = {
30
+ active: false,
31
+ hookInjected: false,
32
+ persona: "",
33
+ journey: "",
34
+ };
35
+
36
+ /** Python `_load_state`: the session's Mirror fields, or empty when unknown. */
37
+ export function readMirrorState(dbPath: string, sessionId: string): MirrorHookState {
38
+ if (!sessionId) return NO_STATE;
39
+ let db: ReturnType<typeof openDatabaseReadOnly>;
40
+ try {
41
+ db = openDatabaseReadOnly(dbPath);
42
+ } catch {
43
+ return NO_STATE;
44
+ }
45
+ try {
46
+ const session = getRuntimeSession(db, sessionId);
47
+ if (!session) return NO_STATE;
48
+ return {
49
+ active: session.mirrorActive,
50
+ hookInjected: session.hookInjected,
51
+ persona: session.persona ?? "",
52
+ journey: session.journey ?? "",
53
+ };
54
+ } catch {
55
+ return NO_STATE;
56
+ } finally {
57
+ db.close();
58
+ }
59
+ }
60
+
61
+ /** Python `needs_inject`: Mirror Mode is on and context has not been injected. */
62
+ export function needsInject(state: MirrorHookState): boolean {
63
+ return state.active && !state.hookInjected;
64
+ }
65
+
66
+ /** Python `mark_injected`, over an already-open writable connection. */
67
+ export function markInjected(db: WritableDatabase, sessionId: string, nowIso: string): void {
68
+ if (!sessionId) return;
69
+ if (!getRuntimeSession(db, sessionId)) return;
70
+ upsertRuntimeSession(db, sessionId, { hookInjected: true }, nowIso);
71
+ }
@@ -0,0 +1,73 @@
1
+ // Reading a runtime's hook payload from stdin.
2
+ //
3
+ // Replaces the Python hook module `extract_prompt` (25 lines) and the four
4
+ // inline interpreter one-liners the Claude and Gemini hooks used to parse the
5
+ // same JSON. None of them was a command, so none was in
6
+ // DS7's denominator of 29 -- which is why TS5's inventory had to find them by
7
+ // reading the hooks rather than by counting commands.
8
+ //
9
+ // Python's `extract_prompt` returns "" for malformed JSON rather than raising,
10
+ // because a hook that throws on a payload it did not recognize would break the
11
+ // user's turn. That posture is preserved exactly.
12
+
13
+ import { readFileSync } from "node:fs";
14
+ import {
15
+ type HookPayloadObject,
16
+ parseHookPayloadObject,
17
+ promptOf,
18
+ sessionIdOf,
19
+ } from "#conversation/hookPayload.ts";
20
+
21
+ export interface HookPayload {
22
+ /** The user's prompt text, or "" when the payload has none. */
23
+ readonly prompt: string;
24
+ /** The runtime's session id (`session_id`, or Grok's `sessionId`), or "". */
25
+ readonly sessionId: string;
26
+ /** The assistant's response text (Gemini's `prompt_response`), or "". */
27
+ readonly response: string;
28
+ /** Everything else, for hooks that need a field these three do not cover. */
29
+ readonly raw: HookPayloadObject;
30
+ }
31
+
32
+ const EMPTY: HookPayload = { prompt: "", sessionId: "", response: "", raw: {} };
33
+
34
+ function text(value: unknown): string {
35
+ return typeof value === "string" ? value : "";
36
+ }
37
+
38
+ /**
39
+ * Parse a hook payload. Never throws: an unparseable payload is empty.
40
+ *
41
+ * The field spellings live in `#conversation/hookPayload.ts`, one reader for
42
+ * the entries and the logger (CV21.DS2.US8).
43
+ */
44
+ export function parseHookPayload(input: string): HookPayload {
45
+ const raw = parseHookPayloadObject(input);
46
+ if (raw === null) return EMPTY;
47
+ return {
48
+ prompt: promptOf(raw),
49
+ sessionId: sessionIdOf(raw),
50
+ // Gemini's AfterAgent payload names it `prompt_response`, and that is the
51
+ // field the Python hook read. The first port read three guessed names
52
+ // instead and dropped every Gemini assistant turn in silence until
53
+ // `smoke_gemini_cli.sh` caught it (CV22.DS10.TS5 plateau 3).
54
+ response: text(raw.prompt_response),
55
+ raw,
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Read stdin to the end, synchronously.
61
+ *
62
+ * Hooks are short-lived processes whose whole job is one payload, and the
63
+ * runtimes close the pipe. Reading fd 0 directly avoids an async entry point
64
+ * for every hook, and returns "" when there is no stdin at all -- which is the
65
+ * case for `session-start`, invoked with no payload.
66
+ */
67
+ export function readStdin(): string {
68
+ try {
69
+ return readFileSync(0, "utf8");
70
+ } catch {
71
+ return "";
72
+ }
73
+ }