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,305 @@
1
+ // Port of `propose_consolidation` (`intelligence/consolidate.py`) and
2
+ // `propose_shadow_observations` (`intelligence/shadow.py`) -- the LLM
3
+ // orchestration half of `consolidate scan` / `shadow scan` (CV22.DS7.US3
4
+ // Slice B; live since CV22.DS8.TS2).
5
+ //
6
+ // The prompt sent to the provider is Python's real template
7
+ // (`CONSOLIDATION_PROMPT` / `SHADOW_SCAN_PROMPT`: task statement, identity or
8
+ // dedup context, the "## Untrusted input" guard, and the JSON output
9
+ // contract), assembled through `pyFormat` around the same fenced block Python
10
+ // fences. The assembled bytes are graded against a golden captured from the
11
+ // real Python builders and digest-pinned, because this file's history is the
12
+ // cautionary tale: from DS7.US3 to DS8.TS2 it sent a fenced memory dump with
13
+ // no instructions, and replay -- which resolves by role and never reads the
14
+ // prompt -- could not tell. Live, the model would have answered prose,
15
+ // `parseJsonResponse` would have failed, and every scan would have reported
16
+ // "no proposals" while the ledger showed paid calls.
17
+
18
+ import { fenceUntrusted } from "#extraction/fencing.ts";
19
+ import { parseJsonResponse } from "#extraction/json.ts";
20
+ import { CONSOLIDATION_PROMPT, SHADOW_SCAN_PROMPT } from "#extraction/prompts.ts";
21
+ import { classifyProviderError, type OnProviderCallOutcome } from "#observability/callOutcome.ts";
22
+ import type { ChatLedgerHook } from "#observability/ledgerHooks.ts";
23
+ import { resolveExtractionModel } from "#providers/config.ts";
24
+ import type { LlmProvider } from "#providers/llm.ts";
25
+ import { pyFormat } from "#util/pythonText.ts";
26
+ import type { ConsolidationRow, CultivationMemory } from "./consolidationStore.ts";
27
+
28
+ /** Mirrors Python's action allowlist check in `propose_consolidation`. */
29
+ const CONSOLIDATION_ACTIONS: ReadonlySet<string> = new Set([
30
+ "merge",
31
+ "identity_update",
32
+ "shadow_candidate",
33
+ ]);
34
+
35
+ /** Port of `_format_cluster`: one Markdown section per memory, in input order. */
36
+ export function formatCluster(cluster: readonly CultivationMemory[]): string {
37
+ const lines: string[] = [];
38
+ cluster.forEach((memory, index) => {
39
+ lines.push(`### Memory ${index + 1}`);
40
+ lines.push(`**Type:** ${memory.memory_type} | **Layer:** ${memory.layer}`);
41
+ if (memory.journey) lines.push(`**Journey:** ${memory.journey}`);
42
+ lines.push(`**Created:** ${memory.created_at.slice(0, 10)}`);
43
+ lines.push(`**Title:** ${memory.title}`);
44
+ lines.push(`**Content:** ${memory.content}`);
45
+ if (memory.context) lines.push(`**Context:** ${memory.context}`);
46
+ lines.push("");
47
+ });
48
+ return lines.join("\n");
49
+ }
50
+
51
+ /** Port of `_format_shadow_memories`. */
52
+ export function formatShadowMemories(memories: readonly CultivationMemory[]): string {
53
+ if (memories.length === 0) return "(no shadow-candidate memories found)";
54
+ const lines: string[] = [];
55
+ for (const memory of memories) {
56
+ lines.push(`### [${memory.id.slice(0, 8)}] ${memory.title}`);
57
+ lines.push(
58
+ `**Type:** ${memory.memory_type} | **Layer:** ${memory.layer} | ` +
59
+ `**State:** ${memory.readiness_state} | **Date:** ${memory.created_at.slice(0, 10)}`,
60
+ );
61
+ lines.push(`**Content:** ${memory.content}`);
62
+ if (memory.context) lines.push(`**Context:** ${memory.context}`);
63
+ lines.push("");
64
+ }
65
+ return lines.join("\n");
66
+ }
67
+
68
+ /** A structural shadow identity entry, the minimal projection `_format_shadow_structure` needs. */
69
+ export interface ShadowStructureEntry {
70
+ key: string;
71
+ content: string;
72
+ }
73
+
74
+ /** Port of `_format_shadow_structure`. */
75
+ export function formatShadowStructure(entries: readonly ShadowStructureEntry[]): string {
76
+ if (entries.length === 0) return "(no structural shadow content yet)";
77
+ return entries.map((entry) => `### ${entry.key}\n${entry.content}`).join("\n\n");
78
+ }
79
+
80
+ /**
81
+ * Exactly what `propose_consolidation` sends: `CONSOLIDATION_PROMPT.format(...)`
82
+ * with only the user-derived cluster fenced -- `identity_context` is
83
+ * system-side. One pass, so a `{placeholder}` literal inside memory content
84
+ * is never expanded a second time.
85
+ */
86
+ export function buildConsolidationPrompt(
87
+ cluster: readonly CultivationMemory[],
88
+ userName: string,
89
+ identityContext: string,
90
+ ): string {
91
+ return pyFormat(CONSOLIDATION_PROMPT, {
92
+ user_name: userName,
93
+ identity_context: identityContext,
94
+ cluster_text: fenceUntrusted("cluster", formatCluster(cluster)),
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Exactly what `propose_shadow_observations` sends: `SHADOW_SCAN_PROMPT.format(...)`
100
+ * with only `shadow_memories` fenced -- `shadow_structure` is structural
101
+ * identity content, not user-injected, and Python leaves it unfenced.
102
+ */
103
+ export function buildShadowScanPrompt(
104
+ memories: readonly CultivationMemory[],
105
+ shadowEntries: readonly ShadowStructureEntry[],
106
+ userName: string,
107
+ ): string {
108
+ return pyFormat(SHADOW_SCAN_PROMPT, {
109
+ user_name: userName,
110
+ shadow_structure: formatShadowStructure(shadowEntries),
111
+ shadow_memories: fenceUntrusted("shadow_memories", formatShadowMemories(memories)),
112
+ });
113
+ }
114
+
115
+ export interface ProposeConsolidationOptions {
116
+ id: string;
117
+ nowIso: string;
118
+ /** `cmd_scan`'s `_user_name(mem)`; see `promptContext.ts`. Required: the fallback lives in the resolver, as in Python. */
119
+ userName: string;
120
+ /** `cmd_scan`'s `_identity_context(mem)`; see `promptContext.ts`. */
121
+ identityContext: string;
122
+ /**
123
+ * Python passes `on_llm_call=build_llm_logger(store, role="consolidation")`
124
+ * from `consolidate_cmd`. TypeScript logged nothing, which was accurate
125
+ * while every call was replayed and free, and stops being accurate the
126
+ * moment this reaches a live provider (CV22.DS8.US3).
127
+ */
128
+ onLlmCall?: ChatLedgerHook;
129
+ /**
130
+ * What happened on this call, category only. Every `null` below is Python's
131
+ * swallow, and without this they are indistinguishable from each other and
132
+ * from an honest "no proposal" (CV22.DS8.US3).
133
+ */
134
+ onOutcome?: OnProviderCallOutcome;
135
+ }
136
+
137
+ /**
138
+ * Port of `propose_consolidation`: fence the cluster, call the LLM
139
+ * (role `'consolidation'`), and parse the response into a pending
140
+ * `ConsolidationRow`, or `null` on any failure (provider rejection, unparsable
141
+ * JSON, an action outside the allowlist, or empty proposed content) --
142
+ * mirroring Python's `except Exception: return None` / `return None` branches
143
+ * exactly. Deliberately does NOT gate `target_layer`/`target_key` here --
144
+ * this only stores the proposal as `pending`; the identity-write allowlist
145
+ * gate runs later, at `apply` time (`applyIdentityUpdate`), which is the
146
+ * adversarial-containment property this story proves.
147
+ */
148
+ export async function proposeConsolidation(
149
+ provider: LlmProvider,
150
+ cluster: readonly CultivationMemory[],
151
+ options: ProposeConsolidationOptions,
152
+ ): Promise<ConsolidationRow | null> {
153
+ const prompt = buildConsolidationPrompt(cluster, options.userName, options.identityContext);
154
+ let content: string;
155
+ try {
156
+ const response = await provider.complete({
157
+ role: "consolidation",
158
+ prompt,
159
+ model: resolveExtractionModel(),
160
+ temperature: 0.1,
161
+ });
162
+ // Python logs AFTER a successful call and BEFORE parsing, so a response
163
+ // the parser rejects still leaves a row: the call was made and paid for.
164
+ options.onLlmCall?.(response, prompt);
165
+ content = response.content;
166
+ } catch (error) {
167
+ options.onOutcome?.({ outcome: "transport_failed", kind: classifyProviderError(error) });
168
+ return null;
169
+ }
170
+
171
+ const data = parseJsonResponse(content);
172
+ // A response the parser cannot use is a PROMPT-layer signal, not a transport
173
+ // one: the call succeeded and was paid for.
174
+ if (!isRecord(data)) {
175
+ options.onOutcome?.({ outcome: "parse_failed" });
176
+ return null;
177
+ }
178
+
179
+ // The model answered and was understood; we rejected what it proposed. That
180
+ // is "nothing to do", not a failure.
181
+ const action = typeof data.action === "string" ? data.action.toLowerCase() : "";
182
+ if (!CONSOLIDATION_ACTIONS.has(action)) {
183
+ options.onOutcome?.({ outcome: "empty" });
184
+ return null;
185
+ }
186
+
187
+ const proposedContent =
188
+ typeof data.proposed_content === "string" ? data.proposed_content.trim() : "";
189
+ if (!proposedContent) {
190
+ options.onOutcome?.({ outcome: "empty" });
191
+ return null;
192
+ }
193
+
194
+ options.onOutcome?.({ outcome: "answered" });
195
+
196
+ const targetLayer =
197
+ typeof data.target_layer === "string" && data.target_layer ? data.target_layer : null;
198
+ const targetKey = typeof data.target_key === "string" && data.target_key ? data.target_key : null;
199
+ const rationaleRaw = typeof data.rationale === "string" ? data.rationale.trim() : "";
200
+
201
+ return {
202
+ id: options.id,
203
+ action,
204
+ proposal: proposedContent,
205
+ result: null,
206
+ source_memory_ids: JSON.stringify(cluster.map((memory) => memory.id)),
207
+ target_layer: targetLayer,
208
+ target_key: targetKey,
209
+ rationale: rationaleRaw || null,
210
+ status: "pending",
211
+ created_at: options.nowIso,
212
+ reviewed_at: null,
213
+ };
214
+ }
215
+
216
+ export interface ProposeShadowObservationsOptions {
217
+ /** Called once per emitted observation, matching Python's per-item `_uuid()`/`_now()`. */
218
+ id: () => string;
219
+ nowIso: () => string;
220
+ /** `cmd_scan`'s `_user_name(mem)`; see `promptContext.ts`. */
221
+ userName: string;
222
+ /** Python's `build_llm_logger(store, role="shadow_scan")`; see above. */
223
+ onLlmCall?: ChatLedgerHook;
224
+ /** What happened on the one call, category only; see `ProposeConsolidationOptions`. */
225
+ onOutcome?: OnProviderCallOutcome;
226
+ }
227
+
228
+ /**
229
+ * Port of `propose_shadow_observations`: a single LLM call over the FULL
230
+ * candidate pool (not per-cluster) so cross-cluster patterns can surface,
231
+ * fenced under `'shadow_memories'`. Returns `[]` on any failure (no
232
+ * memories, provider rejection, unparsable JSON, or a non-array response) --
233
+ * mirroring Python's early-return branches exactly. Each valid item becomes
234
+ * its own pending `Consolidation` with `action: 'shadow_observation'`,
235
+ * `target_layer: 'shadow'`, `target_key: 'profile'` -- ALWAYS this pair,
236
+ * never read from the LLM response (Python's `Consolidation(...,
237
+ * target_layer="shadow", target_key="profile", ...)` is a hardcoded literal,
238
+ * not derived from `item`).
239
+ */
240
+ export async function proposeShadowObservations(
241
+ provider: LlmProvider,
242
+ memories: readonly CultivationMemory[],
243
+ shadowEntries: readonly ShadowStructureEntry[],
244
+ options: ProposeShadowObservationsOptions,
245
+ ): Promise<ConsolidationRow[]> {
246
+ if (memories.length === 0) return [];
247
+
248
+ const prompt = buildShadowScanPrompt(memories, shadowEntries, options.userName);
249
+
250
+ let content: string;
251
+ try {
252
+ const response = await provider.complete({
253
+ role: "shadow_scan",
254
+ prompt,
255
+ model: resolveExtractionModel(),
256
+ temperature: 0.1,
257
+ });
258
+ options.onLlmCall?.(response, prompt);
259
+ content = response.content;
260
+ } catch (error) {
261
+ options.onOutcome?.({ outcome: "transport_failed", kind: classifyProviderError(error) });
262
+ return [];
263
+ }
264
+
265
+ const data = parseJsonResponse(content);
266
+ if (!Array.isArray(data)) {
267
+ options.onOutcome?.({ outcome: "parse_failed" });
268
+ return [];
269
+ }
270
+
271
+ const results: ConsolidationRow[] = [];
272
+ for (const item of data) {
273
+ if (!isRecord(item)) continue;
274
+ const observation = typeof item.observation === "string" ? item.observation.trim() : "";
275
+ if (!observation) continue;
276
+ const title = typeof item.title === "string" ? item.title.trim() : "Shadow observation";
277
+ const memoryIds = Array.isArray(item.memory_ids)
278
+ ? item.memory_ids.map((value) => String(value))
279
+ : [];
280
+ const evidenceNote = typeof item.evidence_note === "string" ? item.evidence_note.trim() : "";
281
+
282
+ const parts = [`**${title}**\n\n${observation}`];
283
+ if (evidenceNote) parts.push(`*Evidence: ${evidenceNote}*`);
284
+
285
+ results.push({
286
+ id: options.id(),
287
+ action: "shadow_observation",
288
+ proposal: parts.join("\n\n"),
289
+ result: null,
290
+ source_memory_ids: JSON.stringify(memoryIds),
291
+ target_layer: "shadow",
292
+ target_key: "profile",
293
+ rationale: title,
294
+ status: "pending",
295
+ created_at: options.nowIso(),
296
+ reviewed_at: null,
297
+ });
298
+ }
299
+ options.onOutcome?.({ outcome: results.length > 0 ? "answered" : "empty" });
300
+ return results;
301
+ }
302
+
303
+ function isRecord(value: unknown): value is Record<string, unknown> {
304
+ return typeof value === "object" && value !== null && !Array.isArray(value);
305
+ }
@@ -0,0 +1,155 @@
1
+ // `consolidate scan` / `shadow scan` orchestration (CV22.DS7.US3 Slice B):
2
+ // cluster (deterministic) -> propose (LLM call, replay-gated) -> store.
3
+ // Ports `cli/consolidate_cmd.py:cmd_scan` and `cli/shadow_cmd.py:cmd_scan`'s
4
+ // non-printing logic -- rendering is the front door's concern.
5
+
6
+ import type { WritableDatabase } from "#db/database.ts";
7
+ import { listIdentityByLayer } from "#identity/identityRead.ts";
8
+ import type { OnProviderCallOutcome } from "#observability/callOutcome.ts";
9
+ import type { ChatLedgerHook } from "#observability/ledgerHooks.ts";
10
+ import type { LlmProvider } from "#providers/llm.ts";
11
+ import { clusterMemories, DEFAULT_CLUSTER_THRESHOLD } from "./cluster.ts";
12
+ import {
13
+ type ConsolidationRow,
14
+ type CultivationMemoryWithEmbedding,
15
+ createConsolidation,
16
+ getMemoriesWithEmbeddingsForCultivation,
17
+ getShadowCandidateMemories,
18
+ } from "./consolidationStore.ts";
19
+ import { consolidationIdentityContext, cultivationUserName } from "./promptContext.ts";
20
+ import { proposeConsolidation, proposeShadowObservations } from "./propose.ts";
21
+
22
+ /** Mirrors `consolidate scan`'s `--limit` default (max proposals per run). */
23
+ export const DEFAULT_CONSOLIDATE_SCAN_LIMIT = 5;
24
+
25
+ /** Mirrors `shadow scan`'s `--limit` default (max shadow-candidate memories considered). */
26
+ export const DEFAULT_SHADOW_SCAN_LIMIT = 50;
27
+
28
+ export interface ConsolidateScanOptions {
29
+ journey?: string | null;
30
+ layer?: string | null;
31
+ /** Max proposals to generate (caps the cluster list before proposing, matching Python). */
32
+ limit?: number;
33
+ threshold?: number;
34
+ provider: LlmProvider;
35
+ /** Called once per cluster considered, matching Python's per-Consolidation `_uuid()`/`_now()`. */
36
+ id: () => string;
37
+ nowIso: () => string;
38
+ /** Ledger hook for the one `consolidation` call per cluster. */
39
+ onLlmCall?: ChatLedgerHook;
40
+ /** Per-cluster outcome, category only; see `observability/callOutcome.ts`. */
41
+ onOutcome?: OnProviderCallOutcome;
42
+ }
43
+
44
+ /** One attempted cluster's outcome, in scan order -- `proposal: null` is
45
+ * Python's "LLM returned no valid proposal for cluster of N memories" branch. */
46
+ export interface ConsolidateScanClusterOutcome {
47
+ cluster: CultivationMemoryWithEmbedding[];
48
+ proposal: ConsolidationRow | null;
49
+ }
50
+
51
+ export interface ConsolidateScanResult {
52
+ memoriesScanned: number;
53
+ /** Capped at `limit` (Python slices `clusters[:limit]` BEFORE proposing), in order. */
54
+ results: ConsolidateScanClusterOutcome[];
55
+ }
56
+
57
+ /** Convenience projection: only the clusters that actually produced a stored proposal. */
58
+ export function createdProposals(result: ConsolidateScanResult): ConsolidationRow[] {
59
+ return result.results.flatMap((r) => (r.proposal ? [r.proposal] : []));
60
+ }
61
+
62
+ /**
63
+ * Port of `consolidate_cmd.cmd_scan`'s non-printing logic: read
64
+ * embedded memories (filtered), cluster them, cap at `limit`, propose one
65
+ * Consolidation per cluster, and persist every non-null proposal. A cluster
66
+ * whose proposal comes back `null` (LLM failure, unparsable JSON, disallowed
67
+ * action, or empty content) is reported as `proposal: null` -- exactly like
68
+ * Python's "LLM returned no valid proposal" branch, which does not store
69
+ * anything for that cluster and moves on, but still renders a line for it.
70
+ */
71
+ export async function consolidateScan(
72
+ db: WritableDatabase,
73
+ options: ConsolidateScanOptions,
74
+ ): Promise<ConsolidateScanResult> {
75
+ const memories = getMemoriesWithEmbeddingsForCultivation(db, {
76
+ journey: options.journey,
77
+ layer: options.layer,
78
+ });
79
+ if (memories.length === 0) {
80
+ return { memoriesScanned: 0, results: [] };
81
+ }
82
+
83
+ const allClusters = clusterMemories(memories, options.threshold ?? DEFAULT_CLUSTER_THRESHOLD);
84
+ const clusters = allClusters.slice(0, options.limit ?? DEFAULT_CONSOLIDATE_SCAN_LIMIT);
85
+
86
+ // Resolved once per scan, after the cluster cap and before the loop --
87
+ // where `cmd_scan` resolves them (CV22.DS8.TS2).
88
+ const userName = cultivationUserName(db);
89
+ const identityContext = consolidationIdentityContext(db);
90
+
91
+ const results: ConsolidateScanClusterOutcome[] = [];
92
+ for (const cluster of clusters) {
93
+ const proposal = await proposeConsolidation(options.provider, cluster, {
94
+ id: options.id(),
95
+ nowIso: options.nowIso(),
96
+ userName,
97
+ identityContext,
98
+ onLlmCall: options.onLlmCall,
99
+ onOutcome: options.onOutcome,
100
+ });
101
+ if (proposal === null) {
102
+ results.push({ cluster, proposal: null });
103
+ continue;
104
+ }
105
+ results.push({ cluster, proposal: createConsolidation(db, proposal) });
106
+ }
107
+
108
+ return { memoriesScanned: memories.length, results };
109
+ }
110
+
111
+ export interface ShadowScanOptions {
112
+ limit?: number;
113
+ provider: LlmProvider;
114
+ /** Ledger hook for the one `shadow_scan` call; Python always passes one. */
115
+ onLlmCall?: ChatLedgerHook;
116
+ /** Outcome of the one call, category only. */
117
+ onOutcome?: OnProviderCallOutcome;
118
+ /** Called once per emitted observation, matching Python's per-item `_uuid()`/`_now()`. */
119
+ id: () => string;
120
+ nowIso: () => string;
121
+ }
122
+
123
+ export interface ShadowScanResult {
124
+ candidatesConsidered: number;
125
+ proposalsCreated: ConsolidationRow[];
126
+ }
127
+
128
+ /**
129
+ * Port of `shadow_cmd.cmd_scan`'s non-printing logic: read shadow-candidate
130
+ * memories and the current structural shadow layer, propose observations over
131
+ * the FULL pool in one call, and persist every emitted proposal.
132
+ */
133
+ export async function shadowScan(
134
+ db: WritableDatabase,
135
+ options: ShadowScanOptions,
136
+ ): Promise<ShadowScanResult> {
137
+ const memories = getShadowCandidateMemories(db, {
138
+ limit: options.limit ?? DEFAULT_SHADOW_SCAN_LIMIT,
139
+ });
140
+ const shadowEntries = listIdentityByLayer(db, "shadow").map((row) => ({
141
+ key: row.key,
142
+ content: row.content,
143
+ }));
144
+
145
+ const proposals = await proposeShadowObservations(options.provider, memories, shadowEntries, {
146
+ id: options.id,
147
+ nowIso: options.nowIso,
148
+ userName: cultivationUserName(db),
149
+ onLlmCall: options.onLlmCall,
150
+ onOutcome: options.onOutcome,
151
+ });
152
+
153
+ const proposalsCreated = proposals.map((proposal) => createConsolidation(db, proposal));
154
+ return { candidatesConsidered: memories.length, proposalsCreated };
155
+ }
@@ -0,0 +1,47 @@
1
+ // Backup gate for destructive write-parity runs.
2
+ //
3
+ // The copy-only guard (`copyGuard.ts`) keeps writes off the live database; this
4
+ // gate adds the second half of the discipline: a hash-verified backup of the
5
+ // source must exist before any destructive apply. The harness refuses to run if
6
+ // the recorded backup is missing or its bytes have changed, so a parity run is
7
+ // always recoverable.
8
+
9
+ import { createHash } from "node:crypto";
10
+ import { lstatSync, readFileSync } from "node:fs";
11
+
12
+ /** Raised when the required pre-write backup is missing or altered. */
13
+ export class BackupGateError extends Error {}
14
+
15
+ /** A recorded backup: a file path and the sha256 of its bytes when recorded. */
16
+ export interface BackupRecord {
17
+ path: string;
18
+ sha256: string;
19
+ }
20
+
21
+ /** Hash a file's bytes as lowercase-hex sha256. */
22
+ export function sha256File(path: string): string {
23
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
24
+ }
25
+
26
+ /**
27
+ * Require a present, hash-verified backup before any destructive apply. The
28
+ * recorded path must be a regular file — a symlinked backup could be
29
+ * redirected after verification, so indirection is refused (CR030).
30
+ */
31
+ export function requireBackup(backup: BackupRecord | undefined): void {
32
+ if (!backup?.path) {
33
+ throw new BackupGateError("no backup recorded before a destructive write");
34
+ }
35
+ let isRegularFile: boolean;
36
+ try {
37
+ isRegularFile = lstatSync(backup.path).isFile();
38
+ } catch {
39
+ throw new BackupGateError(`recorded backup is missing: ${backup.path}`);
40
+ }
41
+ if (!isRegularFile) {
42
+ throw new BackupGateError(`recorded backup is not a regular file: ${backup.path}`);
43
+ }
44
+ if (sha256File(backup.path) !== backup.sha256) {
45
+ throw new BackupGateError("recorded backup hash does not match the backup file");
46
+ }
47
+ }
@@ -0,0 +1,108 @@
1
+ // Bootstrap authority — CV22.DS6.TS3.
2
+ //
3
+ // TS equivalent of Python's `src/memory/db/connection.py::get_connection`.
4
+ // Composes TS1's `createSchema` and TS2's `runMigrations` behind the
5
+ // cross-process bootstrap lock (`bootstrapLock.ts`) and Python's exact pragma
6
+ // ordering, so a TS-only process can create and migrate `memory.db` safely —
7
+ // including under concurrent bootstrap attempts from multiple processes.
8
+ //
9
+ // Pragma ordering matches Python precisely and is load-bearing:
10
+ // connect -> busy_timeout -> foreign_keys -> WAL -> run_migrations -> SCHEMA
11
+ // `journal_mode=WAL` must be set outside any transaction and before
12
+ // migrations run (each migration commits via `withTransaction`; switching
13
+ // journal mode inside a transaction is refused by SQLite).
14
+
15
+ import { chmodSync, existsSync, mkdirSync } from "node:fs";
16
+ import { dirname } from "node:path";
17
+
18
+ import { acquireBootstrapLock, type BootstrapLockOptions } from "./bootstrapLock.ts";
19
+ import { type OpenOptions, openDatabaseForBootstrap, type WritableDatabase } from "./database.ts";
20
+ import { runMigrations } from "./migrations.ts";
21
+ import { createSchema } from "./schema.ts";
22
+
23
+ export interface BootstrapOptions extends OpenOptions, BootstrapLockOptions {}
24
+
25
+ function ensureWalMode(db: WritableDatabase): void {
26
+ const row = db.prepare("PRAGMA journal_mode").get() as { journal_mode?: string } | undefined;
27
+ const current = (row?.journal_mode ?? "").toLowerCase();
28
+ if (current !== "wal") {
29
+ db.exec("PRAGMA journal_mode=WAL");
30
+ }
31
+ }
32
+
33
+ /** Owner-only posture (0600) for the database file and its WAL sidecars. A
34
+ * missing sidecar (e.g. no writes have happened yet) is not an error. */
35
+ function chmodOwnerOnly(dbPath: string): void {
36
+ for (const path of [dbPath, `${dbPath}-wal`, `${dbPath}-shm`]) {
37
+ try {
38
+ chmodSync(path, 0o600);
39
+ } catch {
40
+ // Sidecar absent, or filesystem does not support POSIX modes — Python's
41
+ // own posture is best-effort here too (`contextlib.suppress(OSError)`).
42
+ }
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Bootstrap `dbPath`: create the parent directory (owner-only if we create
48
+ * it — an existing directory is never mutated, matching Python), acquire the
49
+ * cross-process lock, apply pragma discipline, run migrations, and create any
50
+ * missing schema. Idempotent and safe to call repeatedly — every step it
51
+ * composes (pragmas, `runMigrations`, `createSchema`) is a no-op against an
52
+ * already-current database.
53
+ *
54
+ * Returns the open, bootstrapped connection — callers should use it directly
55
+ * rather than reopening, exactly as Python's `get_connection` returns the
56
+ * connection it just bootstrapped.
57
+ */
58
+ export function bootstrapDatabase(
59
+ dbPath: string,
60
+ options: BootstrapOptions = {},
61
+ ): WritableDatabase {
62
+ const parentDir = dirname(dbPath);
63
+ const parentPreexisting = existsSync(parentDir);
64
+ mkdirSync(parentDir, { recursive: true });
65
+ if (!parentPreexisting) {
66
+ try {
67
+ chmodSync(parentDir, 0o700);
68
+ } catch {
69
+ // Best-effort, matching Python's `contextlib.suppress(OSError)`.
70
+ }
71
+ }
72
+
73
+ const lock = acquireBootstrapLock(dbPath, options);
74
+ let db: WritableDatabase;
75
+ try {
76
+ db = openDatabaseForBootstrap(dbPath, options);
77
+ // busy_timeout + foreign_keys are already applied by openDatabaseForBootstrap
78
+ // (shared `applyConnectionPragmas`); WAL is bootstrap-specific because it
79
+ // must run outside a transaction and only needs setting once per file.
80
+ ensureWalMode(db);
81
+ runMigrations(db);
82
+ createSchema(db);
83
+ } finally {
84
+ lock.release();
85
+ }
86
+
87
+ chmodOwnerOnly(dbPath);
88
+ return db;
89
+ }
90
+
91
+ /**
92
+ * Ensure a bootstrapped database exists at `dbPath` (CV22.DS6.TS4). When the
93
+ * file is absent, create and migrate it through {@link bootstrapDatabase} under
94
+ * the cross-process lock, then close the connection so the caller can reopen in
95
+ * the exact mode it needs — read-only serving, or the backup-gated live-write
96
+ * seam. When the file already exists this is a cheap `existsSync` no-op, so the
97
+ * per-command hot path never pays for a lock acquire.
98
+ *
99
+ * This is the front door's first-run seam: it replaces the DS2/DS3 stopgap that
100
+ * delegated a missing database to Python to bootstrap. Concurrency-safe by
101
+ * construction — the underlying bootstrap is lock-guarded and idempotent, so
102
+ * even if the file appears between the check here and the call, the work is a
103
+ * safe no-op rather than a double-apply.
104
+ */
105
+ export function bootstrapDatabaseIfMissing(dbPath: string, options: BootstrapOptions = {}): void {
106
+ if (existsSync(dbPath)) return;
107
+ bootstrapDatabase(dbPath, options).close();
108
+ }