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,111 @@
1
+ /**
2
+ * Prompt assembly for the CV22.DS7.US11 content & planning tail.
3
+ *
4
+ * Each function reproduces, byte for byte, the string Python sends for its
5
+ * role. US10 established why this matters: replay resolves a fixture by role
6
+ * alone, so a TS prompt that drifted from the oracle would replay happily and
7
+ * the divergence would only surface at the DS8 live cutover, against real
8
+ * users. `ts/test/goldens/prompt-assembly.golden.json` pins a SHA-256 of each
9
+ * assembled prompt, generated from the Python source.
10
+ *
11
+ * Temperatures live here too, unused under replay, because DS8's live mode
12
+ * must inherit them rather than rediscover them: journal 0.3, week plan 0.2,
13
+ * descriptor 0.2.
14
+ */
15
+
16
+ import {
17
+ DESCRIPTOR_PROMPT,
18
+ JOURNAL_CLASSIFICATION_PROMPT,
19
+ WEEK_PLAN_PROMPT,
20
+ } from "#extraction/prompts.ts";
21
+ import { pyFormat, sliceCodePoints } from "#util/pythonText.ts";
22
+
23
+ /** Python's `temperature=` per role, carried for the DS8 live cutover. */
24
+ export const JOURNAL_TEMPERATURE = 0.3;
25
+ export const WEEK_PLAN_TEMPERATURE = 0.2;
26
+ export const DESCRIPTOR_TEMPERATURE = 0.2;
27
+
28
+ /** `JOURNAL_CLASSIFICATION_PROMPT + content` (`classify_journal_entry`). */
29
+ export function buildJournalClassificationPrompt(content: string): string {
30
+ return JOURNAL_CLASSIFICATION_PROMPT + content;
31
+ }
32
+
33
+ /** `DESCRIPTOR_PROMPT.format(layer=, key=) + content` (`generate_descriptor`). */
34
+ export function buildDescriptorPrompt(content: string, layer: string, key: string): string {
35
+ return pyFormat(DESCRIPTOR_PROMPT, { layer, key }) + content;
36
+ }
37
+
38
+ export interface WeekPlanJourneyContext {
39
+ /** The journey slug. */
40
+ slug: string;
41
+ /**
42
+ * The journey's identity content, ALREADY truncated to 200 code points by
43
+ * `TaskService.ingest_week_plan`. This function truncates again to 100, as
44
+ * `extract_week_plan` does -- the description passes through two different
45
+ * caps and only the second one reaches the model.
46
+ */
47
+ description: string;
48
+ }
49
+
50
+ /**
51
+ * Python's `journeys_text`: one `- **slug**: description[:100]` line per
52
+ * journey, or the literal `(no active journeys)` when the list is empty.
53
+ *
54
+ * The slice is by CODE POINT (`sliceCodePoints`), not UTF-16 unit -- the divergence
55
+ * class US10 found in `generateTitle`. A journey description with an emoji or
56
+ * a non-BMP character before position 100 would otherwise cut differently in
57
+ * the two engines and silently change the prompt bytes.
58
+ */
59
+ export function buildWeekPlanJourneysText(journeys: readonly WeekPlanJourneyContext[]): string {
60
+ if (journeys.length === 0) return "(no active journeys)";
61
+ return journeys.map((j) => `- **${j.slug}**: ${sliceCodePoints(j.description, 100)}`).join("\n");
62
+ }
63
+
64
+ export interface WeekPlanClock {
65
+ /** `now.strftime("%Y-%m-%d")`. */
66
+ today: string;
67
+ /** `["Monday", ... "Sunday"][now.weekday()]` — Monday-first, unlike JS. */
68
+ weekday: string;
69
+ }
70
+
71
+ const PY_WEEKDAYS = [
72
+ "Monday",
73
+ "Tuesday",
74
+ "Wednesday",
75
+ "Thursday",
76
+ "Friday",
77
+ "Saturday",
78
+ "Sunday",
79
+ ] as const;
80
+
81
+ /**
82
+ * Python's reference date, from an injected clock.
83
+ *
84
+ * `datetime.now()` is LOCAL time and `weekday()` is Monday-indexed, while
85
+ * JavaScript's `getDay()` is Sunday-indexed. Both are reproduced here rather
86
+ * than approximated, and the clock is injected so the golden can freeze it --
87
+ * the prompt embeds the date, so an assembly that read the real clock would
88
+ * fail the digest on any day but the one the corpus was generated.
89
+ */
90
+ export function weekPlanClock(now: Date): WeekPlanClock {
91
+ const year = String(now.getFullYear()).padStart(4, "0");
92
+ const month = String(now.getMonth() + 1).padStart(2, "0");
93
+ const day = String(now.getDate()).padStart(2, "0");
94
+ const pythonWeekday = (now.getDay() + 6) % 7;
95
+ return { today: `${year}-${month}-${day}`, weekday: PY_WEEKDAYS[pythonWeekday] };
96
+ }
97
+
98
+ /** `WEEK_PLAN_PROMPT.format(today=, weekday=, journeys=) + text` (`extract_week_plan`). */
99
+ export function buildWeekPlanPrompt(
100
+ text: string,
101
+ journeys: readonly WeekPlanJourneyContext[],
102
+ clock: WeekPlanClock,
103
+ ): string {
104
+ return (
105
+ pyFormat(WEEK_PLAN_PROMPT, {
106
+ today: clock.today,
107
+ weekday: clock.weekday,
108
+ journeys: buildWeekPlanJourneysText(journeys),
109
+ }) + text
110
+ );
111
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The `week plan` → `week save` pending-file contract (CV22.DS7.US11).
3
+ *
4
+ * Python writes proposed weekly items to a fixed path in the system temp
5
+ * directory and `week save` consumes them. During the transition either engine
6
+ * may write the file and the other may read it, so the shape here is the
7
+ * contract, not an implementation detail — `ts/parity/week_pending_cross_engine.py`
8
+ * proves both directions on a copy.
9
+ *
10
+ * Parity notes:
11
+ * - Python: `Path(tempfile.gettempdir()) / "mm_week_pending.json"`, resolved
12
+ * at import time. Node's `os.tmpdir()` resolves the same TMPDIR on macOS
13
+ * and Linux; the smoke sets TMPDIR explicitly so both engines agree.
14
+ * - Python writes `json.dumps(pending, ensure_ascii=False, indent=2)` with NO
15
+ * trailing newline. `writePendingItems` matches, because the cross-engine
16
+ * proof compares bytes.
17
+ * - Every field except `title` and `due_date` is optional and defaults to
18
+ * null, mirroring `ExtractedWeekItem`.
19
+ */
20
+
21
+ import { readFileSync, rmSync, writeFileSync } from "node:fs";
22
+ import { tmpdir } from "node:os";
23
+ import { join } from "node:path";
24
+
25
+ export interface WeekPendingItem {
26
+ title: string;
27
+ due_date: string;
28
+ scheduled_at: string | null;
29
+ time_hint: string | null;
30
+ journey: string | null;
31
+ context: string | null;
32
+ }
33
+
34
+ /** Python's module-level `PENDING_FILE`, resolved the same way. */
35
+ export function defaultPendingPath(): string {
36
+ return join(tmpdir(), "mm_week_pending.json");
37
+ }
38
+
39
+ function optionalString(value: unknown): string | null {
40
+ return typeof value === "string" ? value : null;
41
+ }
42
+
43
+ /**
44
+ * Read the pending file, or `null` when it does not exist.
45
+ *
46
+ * Python distinguishes "no file" (prints `No pending items to save.`) from "an
47
+ * empty list" (prints `✅ 0 items saved:` and still unlinks), so the absent
48
+ * case must stay distinguishable from the empty one.
49
+ */
50
+ export function readPendingItems(path: string): WeekPendingItem[] | null {
51
+ let raw: string;
52
+ try {
53
+ raw = readFileSync(path, "utf8");
54
+ } catch (error) {
55
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
56
+ throw error;
57
+ }
58
+ const parsed: unknown = JSON.parse(raw);
59
+ if (!Array.isArray(parsed)) return [];
60
+ return parsed.map((entry) => {
61
+ const record = (entry ?? {}) as Record<string, unknown>;
62
+ return {
63
+ title: typeof record.title === "string" ? record.title : "",
64
+ due_date: typeof record.due_date === "string" ? record.due_date : "",
65
+ scheduled_at: optionalString(record.scheduled_at),
66
+ time_hint: optionalString(record.time_hint),
67
+ journey: optionalString(record.journey),
68
+ context: optionalString(record.context),
69
+ };
70
+ });
71
+ }
72
+
73
+ /** Write the pending file in Python's exact serialization (no trailing newline). */
74
+ export function writePendingItems(path: string, items: WeekPendingItem[]): void {
75
+ writeFileSync(path, JSON.stringify(items, null, 2), "utf8");
76
+ }
77
+
78
+ /** Python's `PENDING_FILE.unlink(missing_ok=True)`. */
79
+ export function removePendingFile(path: string): void {
80
+ rmSync(path, { force: true });
81
+ }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * `week plan` — CV22.DS7.US11 plateau 4.
3
+ *
4
+ * Extracts temporal items from free text through the model, warns about
5
+ * near-duplicates already in `tasks`, writes the pending file `week save`
6
+ * consumes, and prints a JSON report.
7
+ *
8
+ * Graded by `ts/test/goldens/week-plan.golden.json`. Three Python behaviours
9
+ * the corpus pins, each of which a tidier implementation would get wrong:
10
+ *
11
+ * - **The similarity query is `LIKE '%fragment%'`** — a CONTAINS match over
12
+ * the first 20 code points of the title, with NO wildcard escaping and the
13
+ * journey argument NOT passed. A `%` or `_` in a title is therefore a live
14
+ * wildcard that over-matches. The US11 Plan review corrected an earlier
15
+ * description of this as a prefix query; the SQL is ported as written.
16
+ * - **A malformed item is skipped, not fatal.** Python wraps
17
+ * `ExtractedWeekItem(**item_data)` in `try/except Exception: continue`, so
18
+ * one bad entry does not lose its siblings.
19
+ * - **The report and the pending file are different shapes.** The report may
20
+ * carry a `warning` key; the pending file never does, and its bytes are
21
+ * `json.dumps(..., ensure_ascii=False, indent=2)` with no trailing newline.
22
+ */
23
+
24
+ import type { Database, WritableDatabase } from "#db/database.ts";
25
+ import {
26
+ buildWeekPlanPrompt,
27
+ type WeekPlanClock,
28
+ type WeekPlanJourneyContext,
29
+ } from "#planning/promptAssembly.ts";
30
+ import { type WeekPendingItem, writePendingItems } from "#planning/weekPending.ts";
31
+ import { sliceCodePoints } from "#util/pythonText.ts";
32
+
33
+ export interface ExtractedWeekItem {
34
+ title: string;
35
+ due_date: string;
36
+ scheduled_at: string | null;
37
+ time_hint: string | null;
38
+ journey: string | null;
39
+ context: string | null;
40
+ }
41
+
42
+ /** Python's `ExtractedWeekItem(**item_data)` inside `try/except: continue`. */
43
+ export function parseExtractedWeekItems(parsed: unknown): ExtractedWeekItem[] {
44
+ if (!Array.isArray(parsed)) return [];
45
+ const items: ExtractedWeekItem[] = [];
46
+ for (const entry of parsed) {
47
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) continue;
48
+ const record = entry as Record<string, unknown>;
49
+ // `title` and `due_date` have no defaults on the dataclass, so their
50
+ // absence raises and the item is skipped. Every other field defaults to
51
+ // None. An UNKNOWN key also raises (unexpected keyword argument).
52
+ const known = new Set(["title", "due_date", "scheduled_at", "time_hint", "journey", "context"]);
53
+ if (Object.keys(record).some((key) => !known.has(key))) continue;
54
+ if (typeof record.title !== "string" || typeof record.due_date !== "string") continue;
55
+ // `ExtractedWeekItem` is a pydantic model with `extra="forbid"` and no
56
+ // str coercion, so a PRESENT optional field of the wrong type raises and
57
+ // the whole item is skipped -- it does not fall back to null.
58
+ const optionalKeys = ["scheduled_at", "time_hint", "journey", "context"] as const;
59
+ const badOptional = optionalKeys.some(
60
+ (key) =>
61
+ key in record &&
62
+ record[key] !== null &&
63
+ record[key] !== undefined &&
64
+ typeof record[key] !== "string",
65
+ );
66
+ if (badOptional) continue;
67
+ const optional = (value: unknown): string | null => (typeof value === "string" ? value : null);
68
+ items.push({
69
+ title: record.title,
70
+ due_date: record.due_date,
71
+ scheduled_at: optional(record.scheduled_at),
72
+ time_hint: optional(record.time_hint),
73
+ journey: optional(record.journey),
74
+ context: optional(record.context),
75
+ });
76
+ }
77
+ return items;
78
+ }
79
+
80
+ export interface ExistingTask {
81
+ id: string;
82
+ title: string;
83
+ due_date: string | null;
84
+ status: string;
85
+ }
86
+
87
+ /**
88
+ * Python `find_tasks_by_title(title_fragment)` — `LIKE '%fragment%'`, ordered
89
+ * by `created_at DESC`, journey filter unused by this caller.
90
+ *
91
+ * The fragment is NOT escaped, so `%` and `_` inside a title behave as SQL
92
+ * wildcards. Reproduced exactly: this is the query the oracle runs, and the
93
+ * golden pins its over-matching.
94
+ */
95
+ export function findTasksByTitle(db: Database, fragment: string): ExistingTask[] {
96
+ return db
97
+ .prepare(
98
+ "SELECT id, title, due_date, status FROM tasks WHERE title LIKE ? ORDER BY created_at DESC",
99
+ )
100
+ .all(`%${fragment}%`) as unknown as ExistingTask[];
101
+ }
102
+
103
+ /** Python: same `due_date`, status not `done`. The FIRST match names the warning. */
104
+ export function similarExistingTasks(db: Database, item: ExtractedWeekItem): ExistingTask[] {
105
+ const fragment = sliceCodePoints(item.title, 20);
106
+ return findTasksByTitle(db, fragment).filter(
107
+ (task) => task.due_date === item.due_date && task.status !== "done",
108
+ );
109
+ }
110
+
111
+ export interface WeekPlanDeps {
112
+ /** Returns the model's raw response body for the assembled prompt. */
113
+ complete: (prompt: string) => string;
114
+ /** Python's `_parse_json_response`. */
115
+ parseJson: (raw: string) => unknown;
116
+ clock: WeekPlanClock;
117
+ pendingPath: string;
118
+ print?: (text: string) => void;
119
+ }
120
+
121
+ /** Journey context exactly as `ingest_week_plan` builds it: `content[:200]`. */
122
+ export function buildJourneyContext(db: Database): WeekPlanJourneyContext[] {
123
+ const rows = db
124
+ .prepare("SELECT key, content FROM identity WHERE layer = 'journey' ORDER BY rowid")
125
+ .all() as unknown as { key: string; content: string | null }[];
126
+ return rows.map((row) => ({
127
+ slug: row.key,
128
+ description: row.content ? sliceCodePoints(row.content, 200) : "",
129
+ }));
130
+ }
131
+
132
+ export interface WeekPlanResult {
133
+ itemCount: number;
134
+ wrotePendingFile: boolean;
135
+ }
136
+
137
+ export function runWeekPlan(
138
+ db: WritableDatabase,
139
+ text: string,
140
+ deps: WeekPlanDeps,
141
+ ): WeekPlanResult {
142
+ const print = deps.print ?? ((value: string) => process.stdout.write(value));
143
+
144
+ const journeys = buildJourneyContext(db);
145
+ const prompt = buildWeekPlanPrompt(text, journeys, deps.clock);
146
+ const items = parseExtractedWeekItems(deps.parseJson(deps.complete(prompt)));
147
+
148
+ if (items.length === 0) {
149
+ print("No temporal items found in the text.\n");
150
+ return { itemCount: 0, wrotePendingFile: false };
151
+ }
152
+
153
+ const pending: WeekPendingItem[] = [];
154
+ const reportItems: Record<string, unknown>[] = [];
155
+
156
+ for (const item of items) {
157
+ const payload: WeekPendingItem = {
158
+ title: item.title,
159
+ due_date: item.due_date,
160
+ scheduled_at: item.scheduled_at,
161
+ time_hint: item.time_hint,
162
+ journey: item.journey,
163
+ context: item.context,
164
+ };
165
+ pending.push(payload);
166
+
167
+ const reportItem: Record<string, unknown> = { ...payload };
168
+ const similar = similarExistingTasks(db, item);
169
+ if (similar.length > 0) {
170
+ reportItem.warning = `Similar item already exists: '${similar[0].title}'`;
171
+ }
172
+ reportItems.push(reportItem);
173
+ }
174
+
175
+ writePendingItems(deps.pendingPath, pending);
176
+ // Python: `json.dumps(output, ensure_ascii=False, indent=2)` -- NO
177
+ // sort_keys, so insertion order is the contract, and non-ASCII stays raw.
178
+ // `pythonJsonDumpsIndented` sorts keys and escapes non-ASCII, so it is the
179
+ // wrong helper here despite the name.
180
+ print(`${JSON.stringify({ items: reportItems, pending_file: deps.pendingPath }, null, 2)}\n`);
181
+
182
+ return { itemCount: items.length, wrotePendingFile: true };
183
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * `week save` — CV22.DS7.US11 plateau 1.
3
+ *
4
+ * Reads the pending file `week plan` wrote, creates one task per item with
5
+ * `source="week_plan"`, unlinks the file, and prints Python's receipt.
6
+ *
7
+ * This leaf crosses NO provider seam. CR068 found it recorded in `routing.ts`
8
+ * as "LLM-gated and reassigned to US5", which is false: `save_week_items`
9
+ * reads the file and calls `add_task`, already ported in DS7.US2. It therefore
10
+ * flips ungated, unlike its `week plan` sibling.
11
+ *
12
+ * Parity notes (pinned by `ts/test/goldens/week-save.golden.json`):
13
+ * - the time suffix is `" at HH:MM"` from `scheduled_at`, ELSE
14
+ * `" (time_hint)"`, else empty — `scheduled_at` wins when both are set;
15
+ * - an unparseable `scheduled_at` hits Python's `except ValueError` and
16
+ * yields NO suffix, while the row still stores the unparseable string;
17
+ * - the file is unlinked AFTER the loop, so a mid-loop failure leaves
18
+ * partial tasks and the file in place. Preserved deliberately;
19
+ * - `✅ {n} items saved:` is printed even when n is 0 (empty list), but the
20
+ * absent file prints `No pending items to save.` and writes nothing.
21
+ */
22
+
23
+ import type { WritableDatabase } from "#db/database.ts";
24
+ import {
25
+ defaultPendingPath,
26
+ readPendingItems,
27
+ removePendingFile,
28
+ type WeekPendingItem,
29
+ } from "#planning/weekPending.ts";
30
+ import { createTask } from "#tasks/taskStore.ts";
31
+ import { newId, nowIso } from "#util/pyGenerators.ts";
32
+
33
+ export interface WeekSaveOptions {
34
+ pendingPath?: string;
35
+ print?: (line: string) => void;
36
+ }
37
+
38
+ export interface WeekSaveResult {
39
+ savedCount: number;
40
+ hadPendingFile: boolean;
41
+ }
42
+
43
+ /**
44
+ * Python's `datetime.fromisoformat(scheduled_at).strftime("%H:%M")`, guarded by
45
+ * `except ValueError`. Node's Date parser is more permissive than
46
+ * `fromisoformat`, so this validates the shape explicitly rather than trusting
47
+ * `new Date(...)` — otherwise TS would render a time where Python renders none.
48
+ */
49
+ function timeSuffix(item: WeekPendingItem): string {
50
+ if (item.scheduled_at) {
51
+ const match = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?$/.exec(
52
+ item.scheduled_at,
53
+ );
54
+ if (match) {
55
+ const [, year, month, day, hour, minute, second] = match;
56
+ const y = Number(year);
57
+ const mo = Number(month);
58
+ const d = Number(day);
59
+ const h = Number(hour);
60
+ const mi = Number(minute);
61
+ const sec = second === undefined ? 0 : Number(second);
62
+ const valid =
63
+ mo >= 1 &&
64
+ mo <= 12 &&
65
+ d >= 1 &&
66
+ d <= new Date(Date.UTC(y, mo, 0)).getUTCDate() &&
67
+ h <= 23 &&
68
+ mi <= 59 &&
69
+ sec <= 59;
70
+ if (valid) return ` at ${hour}:${minute}`;
71
+ }
72
+ return "";
73
+ }
74
+ if (item.time_hint) return ` (${item.time_hint})`;
75
+ return "";
76
+ }
77
+
78
+ export function runWeekSave(db: WritableDatabase, options: WeekSaveOptions = {}): WeekSaveResult {
79
+ const pendingPath = options.pendingPath ?? defaultPendingPath();
80
+ const print = options.print ?? ((line: string) => console.log(line));
81
+
82
+ const pending = readPendingItems(pendingPath);
83
+ if (pending === null) {
84
+ print("No pending items to save.");
85
+ return { savedCount: 0, hadPendingFile: false };
86
+ }
87
+
88
+ const created = pending.map((item) =>
89
+ createTask(
90
+ db,
91
+ {
92
+ title: item.title,
93
+ journey: item.journey ?? undefined,
94
+ dueDate: item.due_date ?? undefined,
95
+ scheduledAt: item.scheduled_at ?? undefined,
96
+ timeHint: item.time_hint ?? undefined,
97
+ context: item.context ?? undefined,
98
+ source: "week_plan",
99
+ },
100
+ newId(),
101
+ nowIso(),
102
+ ),
103
+ );
104
+
105
+ removePendingFile(pendingPath);
106
+
107
+ print(`✅ ${created.length} items saved:`);
108
+ created.forEach((task, index) => {
109
+ const item = pending[index];
110
+ const journey = task.journey ? ` [${task.journey}]` : "";
111
+ print(
112
+ ` ○ \`${task.id.slice(0, 8)}\` ${task.title} - ${task.due_date}${timeSuffix(item)}${journey}`,
113
+ );
114
+ });
115
+
116
+ return { savedCount: created.length, hadPendingFile: true };
117
+ }
@@ -0,0 +1,189 @@
1
+ export type ProviderName = "openrouter";
2
+
3
+ export interface ProviderConfig {
4
+ provider: ProviderName;
5
+ apiKey: string;
6
+ baseUrl: string;
7
+ }
8
+
9
+ export interface ProviderConfigOptions {
10
+ env?: Record<string, string | undefined>;
11
+ argv?: readonly string[];
12
+ }
13
+
14
+ const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
15
+ const ARG_SECRET_PATTERN =
16
+ /^(?:--)?(?:api[-_]?key|openrouter[-_]?api[-_]?key|token|secret)(?:=|$)/i;
17
+
18
+ // Model pins (AI-06, CV9.E2.S12): a deprecated pinned model would fail every
19
+ // extraction/embedding call, and those paths fail soft -- so the pins must be
20
+ // env-overridable to repoint a deployed instance without a release.
21
+ export const DEFAULT_EXTRACTION_MODEL = "google/gemini-2.5-flash-lite";
22
+ export const DEFAULT_EMBEDDING_MODEL = "openai/text-embedding-3-small";
23
+
24
+ export interface ModelPinOptions {
25
+ env?: Record<string, string | undefined>;
26
+ }
27
+
28
+ /**
29
+ * Mirrors Python's `EXTRACTION_MODEL = os.getenv("MEMORY_EXTRACTION_MODEL", ...)`.
30
+ * Every extraction-family LLM call (extract, task-extract, curate, summary)
31
+ * uses this single pin -- Python has no per-role model, so TS does not invent one.
32
+ *
33
+ * Uses `??`, not `||`: matches `os.getenv(name, default)` precisely -- only
34
+ * absence triggers the default, an empty-string override is a real value.
35
+ */
36
+ export function resolveExtractionModel(options: ModelPinOptions = {}): string {
37
+ const env = options.env ?? process.env;
38
+ return env.MEMORY_EXTRACTION_MODEL ?? DEFAULT_EXTRACTION_MODEL;
39
+ }
40
+
41
+ /**
42
+ * Mirrors Python's `EMBEDDING_MODEL = os.getenv("MEMORY_EMBEDDING_MODEL", ...)`.
43
+ * CR039 deliberately shipped no resolver here -- EmbeddingProvider.embed(text)
44
+ * had no model parameter to wire it into, and an unconsumed function would
45
+ * have been dead code. CR043 gives it a real consumer: embedding provenance
46
+ * (recording which model produced a stored vector) needs the *configured*
47
+ * embedding pin, not the extraction pin -- a different constant entirely.
48
+ */
49
+ export function resolveEmbeddingModel(options: ModelPinOptions = {}): string {
50
+ const env = options.env ?? process.env;
51
+ return env.MEMORY_EMBEDDING_MODEL ?? DEFAULT_EMBEDDING_MODEL;
52
+ }
53
+
54
+ // Observability (AI-09, CV9.E2.S13): mirrors Python's MEMORY_LOG_LLM_CALLS
55
+ // resolution exactly -- off | metadata | full, default metadata (never a
56
+ // silent full -- bodies are opt-in only). Legacy "1" maps to "full" for
57
+ // back-compat with Python's original boolean-flag meaning.
58
+ export type LogLlmCallsMode = "off" | "metadata" | "full";
59
+
60
+ export function resolveLogLlmCallsMode(options: ModelPinOptions = {}): LogLlmCallsMode {
61
+ const env = options.env ?? process.env;
62
+ const raw = (env.MEMORY_LOG_LLM_CALLS ?? "").trim().toLowerCase();
63
+ if (raw === "" || raw === "metadata") return "metadata";
64
+ if (raw === "1" || raw === "full") return "full";
65
+ return "off";
66
+ }
67
+
68
+ export class ProviderConfigError extends Error {
69
+ constructor(message: string) {
70
+ super(message);
71
+ this.name = "ProviderConfigError";
72
+ }
73
+ }
74
+
75
+ export function resolveProviderConfig(
76
+ provider: ProviderName,
77
+ options: ProviderConfigOptions = {},
78
+ ): ProviderConfig {
79
+ rejectArgvSecrets(options.argv ?? []);
80
+ const env = options.env ?? process.env;
81
+ const apiKey = env.OPENROUTER_API_KEY?.trim();
82
+ if (!apiKey) {
83
+ throw new ProviderConfigError("OPENROUTER_API_KEY is not configured.");
84
+ }
85
+ return { provider, apiKey, baseUrl: OPENROUTER_BASE_URL };
86
+ }
87
+
88
+ function rejectArgvSecrets(argv: readonly string[]): void {
89
+ for (let index = 0; index < argv.length; index += 1) {
90
+ const arg = argv[index] ?? "";
91
+ if (ARG_SECRET_PATTERN.test(arg)) {
92
+ throw new ProviderConfigError("API keys must come from env/config, not argv-like inputs.");
93
+ }
94
+ const previous = argv[index - 1] ?? "";
95
+ if (ARG_SECRET_PATTERN.test(previous) && arg.trim().length > 0) {
96
+ throw new ProviderConfigError("API keys must come from env/config, not argv-like inputs.");
97
+ }
98
+ }
99
+ }
100
+
101
+ // Extraction-pipeline switches (CV22.DS7.US10 slice F). Python reads each once
102
+ // at import; the front door resolves them per invocation from the same names
103
+ // so a session-end under TypeScript makes the same calls Python would.
104
+
105
+ /** Python `SUMMARIZE_ENABLED = os.getenv("MEMORY_SUMMARIZE", "") == "1"`. */
106
+ export function resolveSummarizeEnabled(options: ModelPinOptions = {}): boolean {
107
+ const env = options.env ?? process.env;
108
+ return env.MEMORY_SUMMARIZE === "1";
109
+ }
110
+
111
+ /** Python `TWO_PASS_ENABLED = os.getenv("MEMORY_TWO_PASS", "") == "1"`. */
112
+ export function resolveTwoPassEnabled(options: ModelPinOptions = {}): boolean {
113
+ const env = options.env ?? process.env;
114
+ return env.MEMORY_TWO_PASS === "1";
115
+ }
116
+
117
+ /** Python `int(os.getenv("MEMORY_MAINTENANCE_MAX_EXTRACTIONS", "10"))` (AI-05). */
118
+ export function resolveMaintenanceMaxExtractions(options: ModelPinOptions = {}): number {
119
+ const env = options.env ?? process.env;
120
+ return parsePythonInt(env.MEMORY_MAINTENANCE_MAX_EXTRACTIONS, 10);
121
+ }
122
+
123
+ /** Python `int(os.getenv("MEMORY_EXTRACTION_MAX_ATTEMPTS", "3"))` (CV9.E2.S7). */
124
+ export function resolveExtractionMaxAttempts(options: ModelPinOptions = {}): number {
125
+ const env = options.env ?? process.env;
126
+ return parsePythonInt(env.MEMORY_EXTRACTION_MAX_ATTEMPTS, 3);
127
+ }
128
+
129
+ /**
130
+ * Python `int(os.getenv(name, default))`: absence yields the default; a
131
+ * present value that is not an integer makes Python's import fail, so the
132
+ * front door fails the same way rather than silently substituting.
133
+ */
134
+ function parsePythonInt(raw: string | undefined, fallback: number): number {
135
+ if (raw === undefined) return fallback;
136
+ if (!/^\s*[+-]?\d+\s*$/.test(raw)) {
137
+ throw new Error(`invalid literal for int() with base 10: '${raw}'`);
138
+ }
139
+ return Number.parseInt(raw, 10);
140
+ }
141
+
142
+ /**
143
+ * Python `float(os.getenv(name, default))`. Same contract as `parsePythonInt`:
144
+ * only absence takes the default, and a present non-numeric value fails loudly
145
+ * instead of silently reverting to a value the operator did not ask for.
146
+ */
147
+ function parsePythonFloat(raw: string | undefined, fallback: number): number {
148
+ if (raw === undefined) return fallback;
149
+ const parsed = Number(raw);
150
+ if (raw.trim() === "" || Number.isNaN(parsed)) {
151
+ throw new Error(`could not convert string to float: '${raw}'`);
152
+ }
153
+ return parsed;
154
+ }
155
+
156
+ // Per-role call bounds (AI-18, CV22.DS8.US1). Python bounds every model call at
157
+ // client construction (`config.py` LLM_TIMEOUT_*, LLM_MAX_RETRIES) so a hung
158
+ // provider connection cannot stall a session hook or the interactive path --
159
+ // the OpenAI SDK default is 600s. The TS live transport carries the same bounds
160
+ // under the same env names; only the unit changes (Python seconds -> ms for
161
+ // AbortSignal.timeout).
162
+
163
+ /** The three call classes Python bounds separately. */
164
+ export type LlmTimeoutRole = "extraction" | "reception" | "embedding";
165
+
166
+ const LLM_TIMEOUT_ENV: Readonly<Record<LlmTimeoutRole, { name: string; defaultSeconds: number }>> =
167
+ {
168
+ extraction: { name: "MEMORY_LLM_TIMEOUT_EXTRACTION", defaultSeconds: 60 },
169
+ reception: { name: "MEMORY_LLM_TIMEOUT_RECEPTION", defaultSeconds: 10 },
170
+ embedding: { name: "MEMORY_LLM_TIMEOUT_EMBEDDING", defaultSeconds: 15 },
171
+ };
172
+
173
+ /**
174
+ * Milliseconds to bound one provider call of `role`, from Python's per-role
175
+ * second-valued pins. All three ship together even though CV22.DS8.US1 only
176
+ * uses `embedding`: the config surface is the same shape for US2/US3, and a
177
+ * half-ported timeout table is how per-role bounds quietly become one bound.
178
+ */
179
+ export function resolveLlmTimeoutMs(role: LlmTimeoutRole, options: ModelPinOptions = {}): number {
180
+ const env = options.env ?? process.env;
181
+ const { name, defaultSeconds } = LLM_TIMEOUT_ENV[role];
182
+ return parsePythonFloat(env[name], defaultSeconds) * 1000;
183
+ }
184
+
185
+ /** Python `LLM_MAX_RETRIES = int(os.getenv("MEMORY_LLM_MAX_RETRIES", "2"))`. */
186
+ export function resolveLlmMaxRetries(options: ModelPinOptions = {}): number {
187
+ const env = options.env ?? process.env;
188
+ return parsePythonInt(env.MEMORY_LLM_MAX_RETRIES, 2);
189
+ }