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,312 @@
1
+ // Task read/write model parity port (CV22.DS7.US2).
2
+ //
3
+ // A faithful TypeScript port of `TaskStore` (`src/memory/storage/tasks.py`) and
4
+ // `TaskService.list_tasks` (`src/memory/services/tasks.py`). Following the
5
+ // database-seam philosophy established in DS2/DS4, ordering and filtering are
6
+ // pushed down to SQLite exactly as Python's SQL does (including `NULLS LAST`,
7
+ // supported by the bundled `node:sqlite` engine) — these functions are query
8
+ // builders + row mappers, not a re-implementation of SQLite's sort.
9
+ //
10
+ // `resolveTaskByIdOrPrefix` is the ONE shared prefix resolver behind
11
+ // `done`/`doing`/`block`/`delete` (the `kebab_slug` writer/locator lesson: two
12
+ // commands must not grow two copies of the same scan). Python itself has no
13
+ // such shared function — each CLI command inlines its own scan and prints its
14
+ // own message, and the two commands disagree on the ambiguous case
15
+ // (`status_change` reports "Ambiguous ID"; `delete` folds it into "not found").
16
+ // This resolver reproduces that fact honestly: it returns what happened
17
+ // (found/ambiguous/not_found), and each front-door command decides how to
18
+ // render the ambiguous case — so the asymmetry lives in presentation, where
19
+ // Python's own asymmetry actually lives, not baked into the resolver's logic.
20
+
21
+ import type { Database, SqlValue, WritableDatabase } from "#db/database.ts";
22
+ import { optionalString, requireString } from "#db/rowDecode.ts";
23
+
24
+ /** A task row, mirroring the Python `Task` model exactly. */
25
+ export interface Task {
26
+ id: string;
27
+ journey: string | null;
28
+ title: string;
29
+ status: string; // 'todo' | 'doing' | 'done' | 'blocked'
30
+ due_date: string | null;
31
+ scheduled_at: string | null;
32
+ time_hint: string | null;
33
+ stage: string | null;
34
+ context: string | null;
35
+ source: string; // 'manual' | 'journey_path' | 'conversation' | 'week_plan' | 'sync'
36
+ created_at: string;
37
+ updated_at: string;
38
+ completed_at: string | null;
39
+ metadata: string | null;
40
+ }
41
+
42
+ const TASK_COLUMNS =
43
+ "id, journey, title, status, due_date, scheduled_at, time_hint, stage, context, " +
44
+ "source, created_at, updated_at, completed_at, metadata";
45
+
46
+ function toTask(row: Record<string, unknown>): Task {
47
+ return {
48
+ id: requireString(row, "id"),
49
+ journey: optionalString(row, "journey"),
50
+ title: requireString(row, "title"),
51
+ status: requireString(row, "status"),
52
+ due_date: optionalString(row, "due_date"),
53
+ scheduled_at: optionalString(row, "scheduled_at"),
54
+ time_hint: optionalString(row, "time_hint"),
55
+ stage: optionalString(row, "stage"),
56
+ context: optionalString(row, "context"),
57
+ source: requireString(row, "source"),
58
+ created_at: requireString(row, "created_at"),
59
+ updated_at: requireString(row, "updated_at"),
60
+ completed_at: optionalString(row, "completed_at"),
61
+ metadata: optionalString(row, "metadata"),
62
+ };
63
+ }
64
+
65
+ // --- Reads -----------------------------------------------------------------
66
+
67
+ /** Port of `Store.get_task`. */
68
+ export function getTaskById(db: Database, id: string): Task | null {
69
+ const row = db.prepare(`SELECT ${TASK_COLUMNS} FROM tasks WHERE id = ?`).get(id);
70
+ return row === undefined ? null : toTask(row);
71
+ }
72
+
73
+ /** Port of `Store.get_all_tasks`: `ORDER BY status, due_date ASC NULLS LAST, created_at ASC`. */
74
+ export function getAllTasks(db: Database): Task[] {
75
+ return db
76
+ .prepare(
77
+ `SELECT ${TASK_COLUMNS} FROM tasks ORDER BY status, due_date ASC NULLS LAST, created_at ASC`,
78
+ )
79
+ .all()
80
+ .map(toTask);
81
+ }
82
+
83
+ /** Port of `Store.get_open_tasks`: status IN ('todo','doing','blocked'), optional journey filter. */
84
+ export function getOpenTasks(db: Database, journey: string | null = null): Task[] {
85
+ const base = `SELECT ${TASK_COLUMNS} FROM tasks WHERE status IN ('todo', 'doing', 'blocked')`;
86
+ const order = "ORDER BY due_date ASC NULLS LAST, created_at ASC";
87
+ if (journey) {
88
+ return db.prepare(`${base} AND journey = ? ${order}`).all(journey).map(toTask);
89
+ }
90
+ return db.prepare(`${base} ${order}`).all().map(toTask);
91
+ }
92
+
93
+ /** Port of `Store.get_tasks_by_status`. */
94
+ export function getTasksByStatus(db: Database, status: string): Task[] {
95
+ return db
96
+ .prepare(
97
+ `SELECT ${TASK_COLUMNS} FROM tasks WHERE status = ? ORDER BY due_date ASC NULLS LAST, created_at ASC`,
98
+ )
99
+ .all(status)
100
+ .map(toTask);
101
+ }
102
+
103
+ /** Port of `Store.get_tasks_by_journey`. */
104
+ export function getTasksByJourney(db: Database, journey: string): Task[] {
105
+ return db
106
+ .prepare(
107
+ `SELECT ${TASK_COLUMNS} FROM tasks WHERE journey = ? ORDER BY due_date ASC NULLS LAST, created_at ASC`,
108
+ )
109
+ .all(journey)
110
+ .map(toTask);
111
+ }
112
+
113
+ /** Port of `Store.find_tasks_by_title`: `LIKE %fragment%`, optional journey filter. */
114
+ export function findTasksByTitle(
115
+ db: Database,
116
+ titleFragment: string,
117
+ journey: string | null = null,
118
+ ): Task[] {
119
+ const pattern = `%${titleFragment}%`;
120
+ if (journey) {
121
+ return db
122
+ .prepare(
123
+ `SELECT ${TASK_COLUMNS} FROM tasks WHERE title LIKE ? AND journey = ? ORDER BY created_at DESC`,
124
+ )
125
+ .all(pattern, journey)
126
+ .map(toTask);
127
+ }
128
+ return db
129
+ .prepare(`SELECT ${TASK_COLUMNS} FROM tasks WHERE title LIKE ? ORDER BY created_at DESC`)
130
+ .all(pattern)
131
+ .map(toTask);
132
+ }
133
+
134
+ /**
135
+ * Build the parameterized week-range query, mirroring Python's builder exactly:
136
+ * `due_date` in range OR `scheduled_at` in range (both bounds inclusive), the
137
+ * `scheduled_at` upper bound extended to `T23:59` so a same-day scheduled item
138
+ * is not excluded by a bare date string, ordered `due_date ASC NULLS LAST,
139
+ * scheduled_at ASC NULLS LAST`. `NULLS LAST` is honored by the bundled SQLite
140
+ * engine (>= 3.30), same as Python's.
141
+ */
142
+ export function buildTasksForWeekQuery(
143
+ startDate: string,
144
+ endDate: string,
145
+ ): { sql: string; params: SqlValue[] } {
146
+ return {
147
+ sql:
148
+ `SELECT ${TASK_COLUMNS} FROM tasks ` +
149
+ "WHERE (due_date >= ? AND due_date <= ?) OR (scheduled_at >= ? AND scheduled_at <= ?) " +
150
+ "ORDER BY due_date ASC NULLS LAST, scheduled_at ASC NULLS LAST",
151
+ params: [startDate, endDate, startDate, `${endDate}T23:59`],
152
+ };
153
+ }
154
+
155
+ /** Port of `Store.get_tasks_for_week`. */
156
+ export function getTasksForWeek(db: Database, startDate: string, endDate: string): Task[] {
157
+ const { sql, params } = buildTasksForWeekQuery(startDate, endDate);
158
+ return db
159
+ .prepare(sql)
160
+ .all(...params)
161
+ .map(toTask);
162
+ }
163
+
164
+ /** Filters for `listTasks`, mirroring `TaskService.list_tasks`'s parameters. */
165
+ export interface ListTasksFilters {
166
+ journey?: string | null;
167
+ status?: string | null;
168
+ openOnly?: boolean;
169
+ }
170
+
171
+ /**
172
+ * Port of `TaskService.list_tasks`: branch order is open_only -> status ->
173
+ * journey -> all, matching Python exactly (an `openOnly` request ignores
174
+ * `status`, and a `status` request applies `journey` as a post-filter over the
175
+ * status read rather than its own query, exactly as Python does).
176
+ */
177
+ export function listTasks(db: Database, filters: ListTasksFilters = {}): Task[] {
178
+ const { journey = null, status = null, openOnly = false } = filters;
179
+ if (openOnly) return getOpenTasks(db, journey);
180
+ if (status) {
181
+ const tasks = getTasksByStatus(db, status);
182
+ return journey ? tasks.filter((t) => t.journey === journey) : tasks;
183
+ }
184
+ if (journey) return getTasksByJourney(db, journey);
185
+ return getAllTasks(db);
186
+ }
187
+
188
+ // --- Prefix resolution -------------------------------------------------------
189
+
190
+ /** The outcome of resolving a task id or id-prefix, naming every real branch. */
191
+ export type ResolveTaskResult =
192
+ | { kind: "found"; task: Task }
193
+ | { kind: "ambiguous"; matches: Task[] }
194
+ | { kind: "not_found" };
195
+
196
+ /**
197
+ * Resolve a task by exact id, falling back to a unique-prefix scan over ALL
198
+ * tasks (`Task.id.startswith(idOrPrefix)`), matching the inline logic
199
+ * duplicated in Python's `cmd_status_change`/`cmd_delete`. Callers render the
200
+ * `ambiguous` case differently on purpose (see module doc); this function only
201
+ * reports the fact.
202
+ */
203
+ export function resolveTaskByIdOrPrefix(db: Database, idOrPrefix: string): ResolveTaskResult {
204
+ const exact = getTaskById(db, idOrPrefix);
205
+ if (exact) return { kind: "found", task: exact };
206
+
207
+ const matches = getAllTasks(db).filter((t) => t.id.startsWith(idOrPrefix));
208
+ if (matches.length === 1) return { kind: "found", task: matches[0] };
209
+ if (matches.length > 1) return { kind: "ambiguous", matches };
210
+ return { kind: "not_found" };
211
+ }
212
+
213
+ // --- Writes ------------------------------------------------------------------
214
+
215
+ /** Fields accepted when creating a task, mirroring `TaskService.add_task`'s parameters. */
216
+ export interface CreateTaskInput {
217
+ title: string;
218
+ journey?: string | null;
219
+ dueDate?: string | null;
220
+ scheduledAt?: string | null;
221
+ timeHint?: string | null;
222
+ stage?: string | null;
223
+ context?: string | null;
224
+ source?: string;
225
+ }
226
+
227
+ /**
228
+ * Port of `TaskService.add_task` + `Store.create_task`. `id` and `nowIso` are
229
+ * injected by the caller (front door), the same determinism-injection idiom
230
+ * DS4 established: `created_at`/`updated_at` both take the single injected
231
+ * `nowIso`, matching a frozen-clock Python run where every `_now()` call
232
+ * returns the same value. New tasks always start `status: "todo"`,
233
+ * `completed_at: null` — Python's `Task` defaults, never overridden here.
234
+ */
235
+ export function createTask(
236
+ db: WritableDatabase,
237
+ input: CreateTaskInput,
238
+ id: string,
239
+ nowIso: string,
240
+ ): Task {
241
+ const task: Task = {
242
+ id,
243
+ journey: input.journey ?? null,
244
+ title: input.title,
245
+ status: "todo",
246
+ due_date: input.dueDate ?? null,
247
+ scheduled_at: input.scheduledAt ?? null,
248
+ time_hint: input.timeHint ?? null,
249
+ stage: input.stage ?? null,
250
+ context: input.context ?? null,
251
+ source: input.source ?? "manual",
252
+ created_at: nowIso,
253
+ updated_at: nowIso,
254
+ completed_at: null,
255
+ metadata: null,
256
+ };
257
+ db.prepare(
258
+ `INSERT INTO tasks (${TASK_COLUMNS}) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
259
+ ).run(
260
+ task.id,
261
+ task.journey,
262
+ task.title,
263
+ task.status,
264
+ task.due_date,
265
+ task.scheduled_at,
266
+ task.time_hint,
267
+ task.stage,
268
+ task.context,
269
+ task.source,
270
+ task.created_at,
271
+ task.updated_at,
272
+ task.completed_at,
273
+ task.metadata,
274
+ );
275
+ return task;
276
+ }
277
+
278
+ /**
279
+ * Port of `Store.update_task(task_id, status=...)`, the primitive behind
280
+ * `doing`/`block`. Always bumps `updated_at` to the injected `nowIso`,
281
+ * matching Python's `Store.update_task`, which sets `updated_at = _now()` on
282
+ * every call regardless of which other fields are passed.
283
+ */
284
+ export function updateTaskStatus(
285
+ db: WritableDatabase,
286
+ id: string,
287
+ status: string,
288
+ nowIso: string,
289
+ ): void {
290
+ db.prepare("UPDATE tasks SET status = ?, updated_at = ? WHERE id = ?").run(status, nowIso, id);
291
+ }
292
+
293
+ /**
294
+ * Port of `TaskService.complete_task` + the `Store.update_task` it calls:
295
+ * sets `status = 'done'`, `completed_at`, and `updated_at` to the single
296
+ * injected `nowIso` (a frozen-clock run returns the same value for both of
297
+ * Python's two independent `_now()` calls here).
298
+ */
299
+ export function completeTask(db: WritableDatabase, id: string, nowIso: string): void {
300
+ db.prepare("UPDATE tasks SET status = 'done', completed_at = ?, updated_at = ? WHERE id = ?").run(
301
+ nowIso,
302
+ nowIso,
303
+ id,
304
+ );
305
+ }
306
+
307
+ /** Port of `Store.delete_task`: returns whether a row was actually removed. */
308
+ export function deleteTaskById(db: WritableDatabase, id: string): boolean {
309
+ const before = getTaskById(db, id);
310
+ db.prepare("DELETE FROM tasks WHERE id = ?").run(id);
311
+ return before !== null;
312
+ }
@@ -0,0 +1,120 @@
1
+ // `week view` business logic parity port (CV22.DS7.US2 slice 3b).
2
+ //
3
+ // A faithful port of the non-printing logic inside `cmd_view`
4
+ // (`src/memory/cli/week.py`): computing the Monday-Sunday week window around
5
+ // "now", filtering out tasks scheduled in the past that aren't done, and
6
+ // grouping/sorting the rest by calendar day. Rendering (icons, columns,
7
+ // headers) is a separate concern in `frontDoor/render/week.ts`.
8
+ //
9
+ // `now` is always injected as a `Date` constructed via the LOCAL constructor
10
+ // form (`new Date(y, m, d, h, mi)`), never `Date.UTC`/an ISO `Z` string --
11
+ // Python's `datetime.now()` is naive local time, and JS's local constructor
12
+ // stores the same literal calendar/clock fields with no timezone conversion,
13
+ // so the two agree on any machine regardless of its system timezone. The same
14
+ // applies to parsing `scheduled_at` ("YYYY-MM-DDTHH:MM", no offset): per the
15
+ // JS Date spec, a date-TIME string without a designator parses as local time
16
+ // (unlike a bare date string, which parses as UTC midnight -- a real
17
+ // asymmetry, avoided here because `scheduled_at` always carries the `T`).
18
+
19
+ import type { Task } from "./taskStore.ts";
20
+
21
+ export interface WeekRange {
22
+ /** ISO date (YYYY-MM-DD), Monday of the week containing `now`. */
23
+ start: string;
24
+ /** ISO date (YYYY-MM-DD), the Sunday six days after `start`. */
25
+ end: string;
26
+ }
27
+
28
+ function pad2(n: number): string {
29
+ return String(n).padStart(2, "0");
30
+ }
31
+
32
+ /** Format y/m(1-based)/d as an ISO date string. */
33
+ export function formatIsoDate(year: number, month: number, day: number): string {
34
+ return `${String(year).padStart(4, "0")}-${pad2(month)}-${pad2(day)}`;
35
+ }
36
+
37
+ /** Add `days` (may be negative) to an ISO date string, DST-safe (calendar-only, no time component). */
38
+ export function addIsoDays(isoDate: string, days: number): string {
39
+ const [y, m, d] = isoDate.split("-").map(Number);
40
+ const shifted = new Date(y, m - 1, d + days);
41
+ return formatIsoDate(shifted.getFullYear(), shifted.getMonth() + 1, shifted.getDate());
42
+ }
43
+
44
+ /** The local calendar date (YYYY-MM-DD) of a `Date`, ignoring its time-of-day. */
45
+ export function toIsoDate(date: Date): string {
46
+ return formatIsoDate(date.getFullYear(), date.getMonth() + 1, date.getDate());
47
+ }
48
+
49
+ /**
50
+ * The Monday-Sunday week containing `now`, mirroring Python's
51
+ * `start = today - timedelta(days=today.weekday())`. JS `getDay()` is
52
+ * Sunday=0..Saturday=6; Python's `weekday()` is Monday=0..Sunday=6, so the
53
+ * conversion is `(getDay() + 6) % 7`.
54
+ */
55
+ export function computeWeekRange(now: Date): WeekRange {
56
+ const pythonWeekday = (now.getDay() + 6) % 7;
57
+ const todayIso = toIsoDate(now);
58
+ const start = addIsoDays(todayIso, -pythonWeekday);
59
+ const end = addIsoDays(start, 6);
60
+ return { start, end };
61
+ }
62
+
63
+ /**
64
+ * Drop tasks that are scheduled in the past and not done, matching Python's
65
+ * `if t.scheduled_at and t.status != "done": ... if sched < now: continue`.
66
+ * An unparseable `scheduled_at` is NOT excluded (Python's `except ValueError:
67
+ * pass` falls through to keep the task) -- reproduced here by only excluding
68
+ * on a successfully-parsed, strictly-earlier instant.
69
+ */
70
+ export function filterVisibleTasks(tasks: readonly Task[], now: Date): Task[] {
71
+ const visible: Task[] = [];
72
+ for (const t of tasks) {
73
+ if (t.scheduled_at && t.status !== "done") {
74
+ const schedMs = new Date(t.scheduled_at).getTime();
75
+ if (!Number.isNaN(schedMs) && schedMs < now.getTime()) {
76
+ continue;
77
+ }
78
+ }
79
+ visible.push(t);
80
+ }
81
+ return visible;
82
+ }
83
+
84
+ /**
85
+ * Sort key mirroring Python's `(scheduled_at or "99", time_hint or "zz",
86
+ * title)` tuple: a scheduled task sorts before any unscheduled one (any real
87
+ * ISO datetime string is lexically less than the "99" sentinel), then by
88
+ * time-hint, then alphabetically by title.
89
+ */
90
+ function compareByDaySortKey(a: Task, b: Task): number {
91
+ const aKey = [a.scheduled_at ?? "99", a.time_hint ?? "zz", a.title];
92
+ const bKey = [b.scheduled_at ?? "99", b.time_hint ?? "zz", b.title];
93
+ for (let i = 0; i < aKey.length; i += 1) {
94
+ if (aKey[i] !== bKey[i]) return aKey[i] < bKey[i] ? -1 : 1;
95
+ }
96
+ return 0;
97
+ }
98
+
99
+ /**
100
+ * Group tasks by calendar day (`due_date`, falling back to `scheduled_at`'s
101
+ * date portion), sorting each day's bucket by the (scheduled_at, time_hint,
102
+ * title) key. A task with neither `due_date` nor `scheduled_at` has no day
103
+ * and is dropped -- matching Python's `if day:` guard (in practice this never
104
+ * happens for a task `getTasksForWeek` actually returned, since its WHERE
105
+ * clause already requires one of the two to be in range).
106
+ */
107
+ export function groupTasksByDay(tasks: readonly Task[]): Map<string, Task[]> {
108
+ const byDay = new Map<string, Task[]>();
109
+ for (const t of tasks) {
110
+ const day = t.due_date || (t.scheduled_at ? t.scheduled_at.slice(0, 10) : null);
111
+ if (!day) continue;
112
+ const bucket = byDay.get(day);
113
+ if (bucket) bucket.push(t);
114
+ else byDay.set(day, [t]);
115
+ }
116
+ for (const bucket of byDay.values()) {
117
+ bucket.sort(compareByDaySortKey);
118
+ }
119
+ return byDay;
120
+ }
@@ -0,0 +1,26 @@
1
+ // CR018 — a cut that says so.
2
+ //
3
+ // The card primitives cut silently, as Python's did, and a Navigator reading a title
4
+ // cut mid-word had no way to know it went on. This rule marks the cut with `…`, and
5
+ // backs a cut that would split a word up to the word's start, because a code cut short
6
+ // reads as another code: `(per D12)` must not become `(per D1…`. It began as `card.ts`'s
7
+ // own; CR103 moved it here so a status clause can be bounded by the same rule before it
8
+ // reaches any card.
9
+
10
+ /**
11
+ * `text` cut to fit `width` code points, with `…` in the last place when anything was
12
+ * cut. A cut that would split a word backs up to the word's start, but only while at
13
+ * least half the width stays; past that, the word is cut. A space the cut leaves at the
14
+ * end goes, so the mark sits on the word.
15
+ */
16
+ export function clipCodePoints(text: string, width: number): string {
17
+ const points = Array.from(text);
18
+ if (points.length <= width) return text;
19
+ let kept = points.slice(0, width - 1);
20
+ const next = points[width - 1] ?? "";
21
+ if (!/\s/u.test(next)) {
22
+ const wordStart = kept.lastIndexOf(" ");
23
+ if (wordStart >= Math.ceil(width / 2)) kept = kept.slice(0, wordStart);
24
+ }
25
+ return `${kept.join("").trimEnd()}…`;
26
+ }
@@ -0,0 +1,63 @@
1
+ // Filesystem path helpers for the front door.
2
+ //
3
+ // normalizeProjectPath is the parity crux of routing `journey set-path`: it must
4
+ // canonicalize a path the way Python's _normalize_project_path does
5
+ // (`Path(value).expanduser().resolve()`).
6
+
7
+ import { accessSync, constants, realpathSync } from "node:fs";
8
+ import { homedir } from "node:os";
9
+ import { delimiter, join, resolve } from "node:path";
10
+
11
+ /**
12
+ * Expand a leading `~` or `~/` to the home directory. A `~user` path passes
13
+ * through unchanged (Python's expanduser resolves known users; for the front
14
+ * door's purposes pass-through matches the unknown-user behavior and never
15
+ * fabricates a wrong path — pre-CR007 this mangled `~user/x` into home+`ser/x`).
16
+ */
17
+ export function expandHome(path: string): string {
18
+ if (path === "~") return homedir();
19
+ if (path.startsWith("~/")) return join(homedir(), path.slice(2));
20
+ return path;
21
+ }
22
+
23
+ /** Can this process execute the file at `path`? */
24
+ export function isExecutable(path: string): boolean {
25
+ try {
26
+ accessSync(path, constants.X_OK);
27
+ return true;
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Where `command` resolves on `env.PATH`, or null: what a shell's `command -v`
35
+ * answers, for the PATH this process was given. The one rule for "is `mirror`
36
+ * reachable", "which runtimes are installed", and the hook wrappers' Node.
37
+ */
38
+ export function commandOnPath(
39
+ command: string,
40
+ env: NodeJS.ProcessEnv,
41
+ canExecute: (path: string) => boolean = isExecutable,
42
+ ): string | null {
43
+ for (const dir of (env.PATH ?? "").split(delimiter).filter(Boolean)) {
44
+ const candidate = join(dir, command);
45
+ if (canExecute(candidate)) return candidate;
46
+ }
47
+ return null;
48
+ }
49
+
50
+ /**
51
+ * Canonicalize a project path like Python's `Path(value).expanduser().resolve()`:
52
+ * expand `~`, make it absolute, and resolve symlinks. Node's `realpathSync` throws
53
+ * on a missing path, so fall back to the absolute (non-symlink) path — Python
54
+ * resolves non-strict, but `set-path` targets a real directory in the normal case.
55
+ */
56
+ export function normalizeProjectPath(value: string): string {
57
+ const absolute = resolve(expandHome(value));
58
+ try {
59
+ return realpathSync(absolute);
60
+ } catch {
61
+ return absolute;
62
+ }
63
+ }
@@ -0,0 +1,9 @@
1
+ // The name the product gives itself in every command it tells a user to run
2
+ // (CV22.DS10.TS5, decisions D2 and D12).
3
+ //
4
+ // `mirror` is not yet a command on anyone's PATH: the npm `bin` is
5
+ // CV22.DS10.US3's to name. Until TS5 the usage lines and hints named the Python
6
+ // program instead, in both its bare and its `uv`-wrapped form, and that program
7
+ // no longer exists. Every one of them reads this constant now, so when US3
8
+ // names the `bin`, this is the one place that changes.
9
+ export const PROGRAM = "mirror";
@@ -0,0 +1,144 @@
1
+ // Deterministic identifier + timestamp generation matching the Python core.
2
+ //
3
+ // During DS4 parity, `id` and `now` were INJECTED so the TS write matched the
4
+ // oracle. For live front-door writes (US4) the TS core must GENERATE them the way
5
+ // Python does: `_uuid()` = uuid4().hex[:8] (8 lowercase hex chars) and `_now()` =
6
+ // datetime.now(timezone.utc).isoformat().replace("+00:00","Z") (microsecond ISO-Z).
7
+
8
+ import { randomUUID } from "node:crypto";
9
+ import { compareByCodePoint } from "./pythonText.ts";
10
+
11
+ /** Match Python `_uuid()` = uuid4().hex[:8]: 8 lowercase hex chars. */
12
+ /**
13
+ * Serialize like Python `json.dumps(value, ensure_ascii=False)`: item
14
+ * separator `", "` and key separator `": "`. Scoped to JSON-decodable values
15
+ * (the shapes Mirror stores in TEXT metadata columns); byte-parity of stored
16
+ * metadata is part of the DS7 golden contract.
17
+ */
18
+ export function pythonJsonDumps(value: unknown): string {
19
+ if (Array.isArray(value)) {
20
+ return `[${value.map((item) => pythonJsonDumps(item)).join(", ")}]`;
21
+ }
22
+ if (typeof value === "object" && value !== null) {
23
+ const entries = Object.entries(value as Record<string, unknown>).map(
24
+ ([key, item]) => `${JSON.stringify(key)}: ${pythonJsonDumps(item)}`,
25
+ );
26
+ return `{${entries.join(", ")}}`;
27
+ }
28
+ return JSON.stringify(value);
29
+ }
30
+
31
+ /**
32
+ * Serialize like Python's DEFAULT `json.dumps(value)` -- same `", "`/`": "`
33
+ * separators as above, but with `ensure_ascii=True`, so non-ASCII characters
34
+ * are escaped as `\uXXXX`.
35
+ *
36
+ * Mirror uses both variants, and which one applies is a per-call-site fact, not
37
+ * a preference: conversation `metadata`/`tags` are written with
38
+ * `ensure_ascii=False` (raw UTF-8), while memory `tags` go through
39
+ * `services/memory.py`'s plain `json.dumps(tags)` and are escaped. Storing
40
+ * `["café"]` where Python stored `["caf\u00e9"]` is a silent byte divergence in
41
+ * a column both cores read.
42
+ *
43
+ * JavaScript strings are UTF-16, so astral characters are already surrogate
44
+ * pairs and escaping each unit reproduces Python's surrogate-pair output.
45
+ */
46
+ export function pythonJsonDumpsEnsureAscii(value: unknown): string {
47
+ return escapeNonAscii(pythonJsonDumps(value));
48
+ }
49
+
50
+ /**
51
+ * Serialize like Python `json.dumps(value, indent=2, sort_keys=True)` -- the
52
+ * shape of a file written to be read back by BOTH cores.
53
+ *
54
+ * `ensure_ascii` is left at Python's default (True), because the call site this
55
+ * mirrors leaves it there too: the welcome's update cache is written with a
56
+ * plain `json.dumps(payload, indent=2, sort_keys=True)`. A release title
57
+ * carrying an em dash must land as `\u2014`, not as raw UTF-8, or the two
58
+ * cores write different bytes for the same state and thrash each other's TTL.
59
+ */
60
+ export function pythonJsonDumpsIndented(value: unknown, indent = 2): string {
61
+ return escapeNonAscii(JSON.stringify(sortKeysDeep(value), null, indent));
62
+ }
63
+
64
+ /**
65
+ * Serialize like Python `json.dumps(value, indent=2)` -- indented and
66
+ * ASCII-escaped, but in INSERTION order.
67
+ *
68
+ * Separate from `pythonJsonDumpsIndented` because that one carries
69
+ * `sort_keys=True`, and this call site does not: the extension runtime catalog
70
+ * is written key by key as `schema_version, runtime, target_root,
71
+ * generated_at, extensions`. Sorting would hoist `extensions` to the top and
72
+ * change every byte of a file both cores read and rewrite.
73
+ */
74
+ export function pythonJsonDumpsIndentedOrdered(value: unknown, indent = 2): string {
75
+ return escapeNonAscii(JSON.stringify(value, null, indent));
76
+ }
77
+
78
+ /**
79
+ * Serialize like Python `json.dumps(value, ensure_ascii=True,
80
+ * separators=(",", ":"), sort_keys=True)` -- the canonical form a HASH is taken
81
+ * over.
82
+ *
83
+ * Separate from `pythonJsonDumpsIndented` because the bytes differ in three ways
84
+ * at once (no indent, no spaces after separators, escaped non-ASCII), and this one
85
+ * feeds `scope_fingerprint`. A fingerprint is an equality test on authority: if the
86
+ * two engines canonicalize differently, a receipt Python recorded cannot be
87
+ * consumed by TypeScript and the Navigator is asked to approve a Plan they already
88
+ * authorized.
89
+ *
90
+ * Keys are ordered by CODE POINT, not by JavaScript's default UTF-16 comparison.
91
+ * The fingerprint payload has fixed ASCII keys so the two orders coincide today;
92
+ * using the correct comparator means a future key cannot silently diverge.
93
+ */
94
+ export function pythonJsonDumpsCanonical(value: unknown): string {
95
+ return escapeNonAscii(JSON.stringify(sortKeysByCodePoint(value)));
96
+ }
97
+
98
+ function sortKeysByCodePoint(value: unknown): unknown {
99
+ if (Array.isArray(value)) return value.map(sortKeysByCodePoint);
100
+ if (typeof value !== "object" || value === null) return value;
101
+ const sorted: Record<string, unknown> = {};
102
+ for (const key of Object.keys(value as Record<string, unknown>).sort(compareByCodePoint)) {
103
+ sorted[key] = sortKeysByCodePoint((value as Record<string, unknown>)[key]);
104
+ }
105
+ return sorted;
106
+ }
107
+
108
+ /** Python's `sort_keys=True`, applied at every level. */
109
+ function sortKeysDeep(value: unknown): unknown {
110
+ if (Array.isArray(value)) return value.map(sortKeysDeep);
111
+ if (typeof value !== "object" || value === null) return value;
112
+ const sorted: Record<string, unknown> = {};
113
+ for (const key of Object.keys(value as Record<string, unknown>).sort()) {
114
+ sorted[key] = sortKeysDeep((value as Record<string, unknown>)[key]);
115
+ }
116
+ return sorted;
117
+ }
118
+
119
+ function escapeNonAscii(text: string): string {
120
+ return text.replace(/[\u0080-\uffff]/g, (character) => {
121
+ const code = character.charCodeAt(0).toString(16).padStart(4, "0");
122
+ return `\\u${code}`;
123
+ });
124
+ }
125
+
126
+ export function newId(): string {
127
+ return randomUUID().replace(/-/g, "").slice(0, 8);
128
+ }
129
+
130
+ /**
131
+ * Format a Date the way Python's `_now()` does: an ISO-8601 UTC string with a `Z`
132
+ * suffix and 6-digit microseconds. JS Date resolves only to milliseconds, so the
133
+ * final three digits are zero-padded. This keeps the stored string the same length
134
+ * and lexicographic ordering as Python's timestamps — a millisecond `.123Z` would
135
+ * otherwise sort *after* a microsecond `.123456Z`, corrupting recency ordering.
136
+ */
137
+ export function toMicrosecondIso(date: Date): string {
138
+ return date.toISOString().replace(/\.(\d{3})Z$/, ".$1000Z");
139
+ }
140
+
141
+ /** Match Python `_now()`: the current UTC time as a microsecond ISO-`Z` string. */
142
+ export function nowIso(): string {
143
+ return toMicrosecondIso(new Date());
144
+ }