mirror-mind 0.0.1 → 0.32.1

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 (467) hide show
  1. package/.claude/hooks/log-session-end.sh +85 -0
  2. package/.claude/hooks/log-user-prompt.sh +85 -0
  3. package/.claude/hooks/mirror-inject.sh +85 -0
  4. package/.claude/hooks/session-start.sh +85 -0
  5. package/.gemini/hooks/log-assistant.sh +85 -0
  6. package/.gemini/hooks/log-user.sh +85 -0
  7. package/.gemini/hooks/session-end.sh +85 -0
  8. package/.gemini/hooks/session-start.sh +85 -0
  9. package/.pi/extensions/mirror-logger.ts +561 -0
  10. package/.pi/skills/mm-backup/SKILL.md +21 -0
  11. package/.pi/skills/mm-build/SKILL.md +1206 -0
  12. package/.pi/skills/mm-consolidate/SKILL.md +112 -0
  13. package/.pi/skills/mm-consult/SKILL.md +70 -0
  14. package/.pi/skills/mm-conversations/SKILL.md +16 -0
  15. package/.pi/skills/mm-discard/SKILL.md +39 -0
  16. package/.pi/skills/mm-explore/SKILL.md +255 -0
  17. package/.pi/skills/mm-help/SKILL.md +74 -0
  18. package/.pi/skills/mm-identity/SKILL.md +96 -0
  19. package/.pi/skills/mm-journal/SKILL.md +16 -0
  20. package/.pi/skills/mm-journey/SKILL.md +23 -0
  21. package/.pi/skills/mm-journeys/SKILL.md +15 -0
  22. package/.pi/skills/mm-memories/SKILL.md +17 -0
  23. package/.pi/skills/mm-mirror/SKILL.md +98 -0
  24. package/.pi/skills/mm-mute/SKILL.md +18 -0
  25. package/.pi/skills/mm-new/SKILL.md +18 -0
  26. package/.pi/skills/mm-recall/SKILL.md +17 -0
  27. package/.pi/skills/mm-release-notes/SKILL.md +52 -0
  28. package/.pi/skills/mm-seed/SKILL.md +19 -0
  29. package/.pi/skills/mm-shadow/SKILL.md +117 -0
  30. package/.pi/skills/mm-soul/SKILL.md +502 -0
  31. package/.pi/skills/mm-tasks/SKILL.md +24 -0
  32. package/.pi/skills/mm-update/SKILL.md +32 -0
  33. package/.pi/skills/mm-week/SKILL.md +28 -0
  34. package/.pi/skills/mm-welcome/SKILL.md +24 -0
  35. package/AGENTS.md +188 -0
  36. package/README.md +126 -9
  37. package/bin/loader.js +67 -0
  38. package/bin/mirror-hook.js +7 -0
  39. package/bin/mirror.js +7 -0
  40. package/docs/releases/index.md +99 -0
  41. package/docs/releases/pending-cutoffs.md +510 -0
  42. package/docs/releases/v0.10.0.md +123 -0
  43. package/docs/releases/v0.10.1.md +66 -0
  44. package/docs/releases/v0.10.10.md +51 -0
  45. package/docs/releases/v0.10.11.md +39 -0
  46. package/docs/releases/v0.10.12.md +57 -0
  47. package/docs/releases/v0.10.13.md +35 -0
  48. package/docs/releases/v0.10.14.md +42 -0
  49. package/docs/releases/v0.10.15.md +53 -0
  50. package/docs/releases/v0.10.16.md +35 -0
  51. package/docs/releases/v0.10.17.md +43 -0
  52. package/docs/releases/v0.10.2.md +64 -0
  53. package/docs/releases/v0.10.3.md +78 -0
  54. package/docs/releases/v0.10.4.md +42 -0
  55. package/docs/releases/v0.10.5.md +57 -0
  56. package/docs/releases/v0.10.6.md +47 -0
  57. package/docs/releases/v0.10.7.md +53 -0
  58. package/docs/releases/v0.10.8.md +49 -0
  59. package/docs/releases/v0.10.9.md +37 -0
  60. package/docs/releases/v0.11.0.md +86 -0
  61. package/docs/releases/v0.12.0.md +67 -0
  62. package/docs/releases/v0.13.0.md +64 -0
  63. package/docs/releases/v0.14.0.md +68 -0
  64. package/docs/releases/v0.15.0.md +48 -0
  65. package/docs/releases/v0.16.0.md +50 -0
  66. package/docs/releases/v0.16.1.md +55 -0
  67. package/docs/releases/v0.16.2.md +36 -0
  68. package/docs/releases/v0.17.0.md +50 -0
  69. package/docs/releases/v0.17.1.md +58 -0
  70. package/docs/releases/v0.17.2.md +42 -0
  71. package/docs/releases/v0.17.3.md +58 -0
  72. package/docs/releases/v0.18.0.md +84 -0
  73. package/docs/releases/v0.18.1.md +51 -0
  74. package/docs/releases/v0.18.2.md +69 -0
  75. package/docs/releases/v0.19.0.md +85 -0
  76. package/docs/releases/v0.19.1.md +46 -0
  77. package/docs/releases/v0.20.0.md +85 -0
  78. package/docs/releases/v0.21.0.md +61 -0
  79. package/docs/releases/v0.21.1.md +47 -0
  80. package/docs/releases/v0.22.0.md +77 -0
  81. package/docs/releases/v0.22.1.md +42 -0
  82. package/docs/releases/v0.23.0.md +105 -0
  83. package/docs/releases/v0.24.0.md +125 -0
  84. package/docs/releases/v0.25.0.md +107 -0
  85. package/docs/releases/v0.26.0.md +139 -0
  86. package/docs/releases/v0.26.1.md +65 -0
  87. package/docs/releases/v0.26.2.md +43 -0
  88. package/docs/releases/v0.27.0.md +63 -0
  89. package/docs/releases/v0.28.0.md +59 -0
  90. package/docs/releases/v0.29.0.md +61 -0
  91. package/docs/releases/v0.29.1.md +59 -0
  92. package/docs/releases/v0.29.2.md +59 -0
  93. package/docs/releases/v0.29.3.md +93 -0
  94. package/docs/releases/v0.30.0.md +75 -0
  95. package/docs/releases/v0.30.1.md +93 -0
  96. package/docs/releases/v0.30.2.md +66 -0
  97. package/docs/releases/v0.31.0.md +122 -0
  98. package/docs/releases/v0.31.1.md +142 -0
  99. package/docs/releases/v0.31.10.md +118 -0
  100. package/docs/releases/v0.31.11.md +88 -0
  101. package/docs/releases/v0.31.12.md +105 -0
  102. package/docs/releases/v0.31.13.md +99 -0
  103. package/docs/releases/v0.31.14.md +97 -0
  104. package/docs/releases/v0.31.2.md +115 -0
  105. package/docs/releases/v0.31.3.md +142 -0
  106. package/docs/releases/v0.31.4.md +117 -0
  107. package/docs/releases/v0.31.5.md +155 -0
  108. package/docs/releases/v0.31.6.md +132 -0
  109. package/docs/releases/v0.31.7.md +132 -0
  110. package/docs/releases/v0.31.8.md +120 -0
  111. package/docs/releases/v0.31.9.md +120 -0
  112. package/docs/releases/v0.32.0.md +261 -0
  113. package/docs/releases/v0.32.1.md +59 -0
  114. package/docs/releases/v0.8.0.md +118 -0
  115. package/docs/releases/v0.9.0.md +98 -0
  116. package/docs/releases/v0.9.1.md +83 -0
  117. package/package.json +102 -6
  118. package/plugins/mirror-mind/.claude-plugin/plugin.json +13 -0
  119. package/plugins/mirror-mind/hooks/hooks.json +50 -0
  120. package/plugins/mirror-mind/hooks/log-session-end.sh +98 -0
  121. package/plugins/mirror-mind/hooks/log-user-prompt.sh +98 -0
  122. package/plugins/mirror-mind/hooks/mirror-inject.sh +98 -0
  123. package/plugins/mirror-mind/hooks/session-start.sh +98 -0
  124. package/plugins/mirror-mind/mcp/launch.sh +52 -0
  125. package/plugins/mirror-mind/skills/mm-backup/SKILL.md +27 -0
  126. package/plugins/mirror-mind/skills/mm-build/SKILL.md +1206 -0
  127. package/plugins/mirror-mind/skills/mm-consolidate/SKILL.md +112 -0
  128. package/plugins/mirror-mind/skills/mm-consult/SKILL.md +83 -0
  129. package/plugins/mirror-mind/skills/mm-conversations/SKILL.md +17 -0
  130. package/plugins/mirror-mind/skills/mm-discard/SKILL.md +40 -0
  131. package/plugins/mirror-mind/skills/mm-explore/SKILL.md +241 -0
  132. package/plugins/mirror-mind/skills/mm-help/SKILL.md +104 -0
  133. package/plugins/mirror-mind/skills/mm-identity/SKILL.md +96 -0
  134. package/plugins/mirror-mind/skills/mm-journal/SKILL.md +21 -0
  135. package/plugins/mirror-mind/skills/mm-journey/SKILL.md +37 -0
  136. package/plugins/mirror-mind/skills/mm-journeys/SKILL.md +15 -0
  137. package/plugins/mirror-mind/skills/mm-memories/SKILL.md +20 -0
  138. package/plugins/mirror-mind/skills/mm-mirror/SKILL.md +65 -0
  139. package/plugins/mirror-mind/skills/mm-mute/SKILL.md +18 -0
  140. package/plugins/mirror-mind/skills/mm-new/SKILL.md +18 -0
  141. package/plugins/mirror-mind/skills/mm-recall/SKILL.md +17 -0
  142. package/plugins/mirror-mind/skills/mm-release-notes/SKILL.md +31 -0
  143. package/plugins/mirror-mind/skills/mm-seed/SKILL.md +15 -0
  144. package/plugins/mirror-mind/skills/mm-shadow/SKILL.md +117 -0
  145. package/plugins/mirror-mind/skills/mm-soul/SKILL.md +488 -0
  146. package/plugins/mirror-mind/skills/mm-tasks/SKILL.md +36 -0
  147. package/plugins/mirror-mind/skills/mm-update/SKILL.md +30 -0
  148. package/plugins/mirror-mind/skills/mm-week/SKILL.md +46 -0
  149. package/plugins/mirror-mind/skills/mm-welcome/SKILL.md +22 -0
  150. package/scripts/codex-hooks/session-end.sh +85 -0
  151. package/scripts/codex-hooks/session-start.sh +85 -0
  152. package/scripts/codex-mirror.sh +48 -0
  153. package/templates/identity/README.md +119 -0
  154. package/templates/identity/ego/behavior.yaml +52 -0
  155. package/templates/identity/ego/constraints.yaml +15 -0
  156. package/templates/identity/ego/identity.yaml +43 -0
  157. package/templates/identity/journeys/personal-growth.yaml +39 -0
  158. package/templates/identity/organization/identity.yaml +24 -0
  159. package/templates/identity/organization/principles.yaml +17 -0
  160. package/templates/identity/personas/coach.yaml +94 -0
  161. package/templates/identity/personas/designer.yaml +93 -0
  162. package/templates/identity/personas/doctor.yaml +96 -0
  163. package/templates/identity/personas/engineer.yaml +77 -0
  164. package/templates/identity/personas/financial.yaml +97 -0
  165. package/templates/identity/personas/prompt-engineer.yaml +122 -0
  166. package/templates/identity/personas/researcher.yaml +88 -0
  167. package/templates/identity/personas/strategist.yaml +91 -0
  168. package/templates/identity/personas/teacher.yaml +95 -0
  169. package/templates/identity/personas/therapist.yaml +91 -0
  170. package/templates/identity/personas/thinker.yaml +74 -0
  171. package/templates/identity/personas/writer.yaml +76 -0
  172. package/templates/identity/self/config.yaml +16 -0
  173. package/templates/identity/self/soul.yaml +105 -0
  174. package/templates/identity/user/identity.yaml +30 -0
  175. package/ts/src/backup/staging.ts +62 -0
  176. package/ts/src/backup/zipBackup.ts +267 -0
  177. package/ts/src/backup/zipReader.ts +109 -0
  178. package/ts/src/backup/zipWriter.ts +148 -0
  179. package/ts/src/builder/activeCheckpoint.ts +134 -0
  180. package/ts/src/builder/aggregateStatus.ts +34 -0
  181. package/ts/src/builder/approve.ts +149 -0
  182. package/ts/src/builder/argv.ts +292 -0
  183. package/ts/src/builder/ariadMethod.ts +536 -0
  184. package/ts/src/builder/artifacts/artifactSurfaces.ts +106 -0
  185. package/ts/src/builder/artifacts/artifactWriter.ts +113 -0
  186. package/ts/src/builder/artifacts/closureArtifacts.ts +199 -0
  187. package/ts/src/builder/artifacts/deliveryStoryArtifacts.ts +316 -0
  188. package/ts/src/builder/artifacts/planArtifacts.ts +242 -0
  189. package/ts/src/builder/artifacts/scaffoldSections.ts +273 -0
  190. package/ts/src/builder/artifacts/scaffoldState.ts +227 -0
  191. package/ts/src/builder/artifacts/storyFiles.ts +61 -0
  192. package/ts/src/builder/artifacts/storyIndex.ts +166 -0
  193. package/ts/src/builder/cadence.ts +59 -0
  194. package/ts/src/builder/card.ts +192 -0
  195. package/ts/src/builder/checkpointRefused.ts +66 -0
  196. package/ts/src/builder/closure.ts +964 -0
  197. package/ts/src/builder/commands.ts +2053 -0
  198. package/ts/src/builder/cursorPosition.ts +176 -0
  199. package/ts/src/builder/cursorTransitions.ts +77 -0
  200. package/ts/src/builder/deliveryCursor.ts +577 -0
  201. package/ts/src/builder/deliveryStoryClosure.ts +521 -0
  202. package/ts/src/builder/deliveryStoryPlan.ts +536 -0
  203. package/ts/src/builder/deliveryStoryReady.ts +160 -0
  204. package/ts/src/builder/deliveryStoryRoadmapClosure.ts +151 -0
  205. package/ts/src/builder/expand.ts +336 -0
  206. package/ts/src/builder/flowUnit.ts +339 -0
  207. package/ts/src/builder/homeSurface.ts +122 -0
  208. package/ts/src/builder/implementationGuard.ts +125 -0
  209. package/ts/src/builder/index.ts +11 -0
  210. package/ts/src/builder/lifecycleRefusal.ts +92 -0
  211. package/ts/src/builder/lifecycleRibbon.ts +159 -0
  212. package/ts/src/builder/load.ts +313 -0
  213. package/ts/src/builder/methodAdoption.ts +113 -0
  214. package/ts/src/builder/methodDefinition.ts +429 -0
  215. package/ts/src/builder/methodInspection.ts +371 -0
  216. package/ts/src/builder/plan.ts +408 -0
  217. package/ts/src/builder/planPreauthorization.ts +265 -0
  218. package/ts/src/builder/prepare.ts +225 -0
  219. package/ts/src/builder/projectPaths.ts +64 -0
  220. package/ts/src/builder/pull.ts +356 -0
  221. package/ts/src/builder/pullCandidates.ts +463 -0
  222. package/ts/src/builder/pullCandidatesRender.ts +238 -0
  223. package/ts/src/builder/refinementField.ts +93 -0
  224. package/ts/src/builder/releaseIntent.ts +173 -0
  225. package/ts/src/builder/resumeState.ts +84 -0
  226. package/ts/src/builder/resumeSurface.ts +149 -0
  227. package/ts/src/builder/roadmapGrammar.ts +300 -0
  228. package/ts/src/builder/roadmapScan.ts +97 -0
  229. package/ts/src/builder/roadmapScope.ts +210 -0
  230. package/ts/src/builder/scopePhrases.ts +146 -0
  231. package/ts/src/builder/storyPaths.ts +196 -0
  232. package/ts/src/builder/storyPlanPreauthorization.ts +287 -0
  233. package/ts/src/builder/surfaceProtocol.ts +42 -0
  234. package/ts/src/builder/templateGeneration.ts +148 -0
  235. package/ts/src/builder/transition.ts +178 -0
  236. package/ts/src/consult/args.ts +99 -0
  237. package/ts/src/consult/core.ts +122 -0
  238. package/ts/src/consult/modelCatalog.ts +55 -0
  239. package/ts/src/consult/render.ts +47 -0
  240. package/ts/src/conversation/append.ts +414 -0
  241. package/ts/src/conversation/backfill.ts +387 -0
  242. package/ts/src/conversation/closeTail.ts +467 -0
  243. package/ts/src/conversation/extraction.ts +333 -0
  244. package/ts/src/conversation/extractionDriver.ts +130 -0
  245. package/ts/src/conversation/extractionRun.ts +70 -0
  246. package/ts/src/conversation/journeyInference.ts +165 -0
  247. package/ts/src/conversation/journeyRepair.ts +114 -0
  248. package/ts/src/conversation/lifecycleFaces.ts +197 -0
  249. package/ts/src/conversation/lifecycleWrites.ts +313 -0
  250. package/ts/src/conversation/listing.ts +77 -0
  251. package/ts/src/conversation/logger.ts +576 -0
  252. package/ts/src/conversation/loggerCli.ts +282 -0
  253. package/ts/src/conversation/loggerRuntime.ts +259 -0
  254. package/ts/src/conversation/metadataLifecycle.ts +443 -0
  255. package/ts/src/conversation/recall.ts +66 -0
  256. package/ts/src/conversation/sessionComposites.ts +258 -0
  257. package/ts/src/conversation/sessionImport.ts +90 -0
  258. package/ts/src/conversation/transcriptBackfill.ts +141 -0
  259. package/ts/src/cultivation/applyActions.ts +241 -0
  260. package/ts/src/cultivation/cluster.ts +86 -0
  261. package/ts/src/cultivation/consolidationStore.ts +281 -0
  262. package/ts/src/cultivation/promptContext.ts +62 -0
  263. package/ts/src/cultivation/propose.ts +305 -0
  264. package/ts/src/cultivation/scan.ts +155 -0
  265. package/ts/src/db/backupGate.ts +47 -0
  266. package/ts/src/db/bootstrap.ts +108 -0
  267. package/ts/src/db/bootstrapLock.ts +223 -0
  268. package/ts/src/db/copyGuard.ts +60 -0
  269. package/ts/src/db/database.ts +397 -0
  270. package/ts/src/db/decode.ts +66 -0
  271. package/ts/src/db/ftsIntegrity.ts +41 -0
  272. package/ts/src/db/migrateOnOpen.ts +214 -0
  273. package/ts/src/db/migrations.ts +576 -0
  274. package/ts/src/db/readyOnOpen.ts +35 -0
  275. package/ts/src/db/rowDecode.ts +42 -0
  276. package/ts/src/db/schema.ts +432 -0
  277. package/ts/src/db/schemaInventory.ts +225 -0
  278. package/ts/src/db/schemaInventorySnapshot.ts +2107 -0
  279. package/ts/src/db/schemaState.ts +91 -0
  280. package/ts/src/db/schemaTsDivergence.ts +114 -0
  281. package/ts/src/descriptor/descriptorRead.ts +49 -0
  282. package/ts/src/docs/docsLint.ts +265 -0
  283. package/ts/src/explorer/handoff.ts +522 -0
  284. package/ts/src/explorer/render.ts +335 -0
  285. package/ts/src/explorer/story.ts +769 -0
  286. package/ts/src/explorer/transition.ts +72 -0
  287. package/ts/src/extensions/bindings.ts +170 -0
  288. package/ts/src/extensions/catalog.ts +249 -0
  289. package/ts/src/extensions/catalogCommands.ts +513 -0
  290. package/ts/src/extensions/catalogWrites.ts +652 -0
  291. package/ts/src/extensions/contextRuntime.ts +325 -0
  292. package/ts/src/extensions/dispatch.ts +374 -0
  293. package/ts/src/extensions/errors.ts +24 -0
  294. package/ts/src/extensions/manifest.ts +356 -0
  295. package/ts/src/extensions/migrations.ts +350 -0
  296. package/ts/src/extraction/conversation.ts +304 -0
  297. package/ts/src/extraction/conversationMetadata.ts +206 -0
  298. package/ts/src/extraction/fencing.ts +106 -0
  299. package/ts/src/extraction/json.ts +17 -0
  300. package/ts/src/extraction/prompts.ts +470 -0
  301. package/ts/src/frontDoor/args.ts +30 -0
  302. package/ts/src/frontDoor/argvShape.ts +106 -0
  303. package/ts/src/frontDoor/buildLoadRuntime.ts +139 -0
  304. package/ts/src/frontDoor/buildRoute.ts +398 -0
  305. package/ts/src/frontDoor/cli.ts +1865 -0
  306. package/ts/src/frontDoor/cloneRoleGuard.ts +98 -0
  307. package/ts/src/frontDoor/consultRoute.ts +50 -0
  308. package/ts/src/frontDoor/contentTailRoute.ts +239 -0
  309. package/ts/src/frontDoor/conversationLoggerRoute.ts +73 -0
  310. package/ts/src/frontDoor/cultivationRoute.ts +210 -0
  311. package/ts/src/frontDoor/dbPath.ts +131 -0
  312. package/ts/src/frontDoor/dbSafetyToolsRoute.ts +240 -0
  313. package/ts/src/frontDoor/exploreRoute.ts +572 -0
  314. package/ts/src/frontDoor/extensionCatalogRoute.ts +252 -0
  315. package/ts/src/frontDoor/frontDoorLog.ts +57 -0
  316. package/ts/src/frontDoor/identityWrite.ts +36 -0
  317. package/ts/src/frontDoor/journeyWriteRoute.ts +26 -0
  318. package/ts/src/frontDoor/lifecycleWriteRoute.ts +110 -0
  319. package/ts/src/frontDoor/liveBackup.ts +100 -0
  320. package/ts/src/frontDoor/mirrorModeRoute.ts +163 -0
  321. package/ts/src/frontDoor/namedRefusal.ts +35 -0
  322. package/ts/src/frontDoor/nodeSupport.ts +24 -0
  323. package/ts/src/frontDoor/render/consolidate.ts +199 -0
  324. package/ts/src/frontDoor/render/conversations.ts +33 -0
  325. package/ts/src/frontDoor/render/cultivationShared.ts +21 -0
  326. package/ts/src/frontDoor/render/descriptor.ts +20 -0
  327. package/ts/src/frontDoor/render/detectPersona.ts +50 -0
  328. package/ts/src/frontDoor/render/icons.ts +13 -0
  329. package/ts/src/frontDoor/render/identity.ts +59 -0
  330. package/ts/src/frontDoor/render/identityRows.ts +17 -0
  331. package/ts/src/frontDoor/render/inspectPersona.ts +53 -0
  332. package/ts/src/frontDoor/render/journeyStatus.ts +78 -0
  333. package/ts/src/frontDoor/render/journeys.ts +79 -0
  334. package/ts/src/frontDoor/render/list.ts +41 -0
  335. package/ts/src/frontDoor/render/memories.ts +67 -0
  336. package/ts/src/frontDoor/render/recall.ts +49 -0
  337. package/ts/src/frontDoor/render/shadow.ts +155 -0
  338. package/ts/src/frontDoor/render/tasks.ts +99 -0
  339. package/ts/src/frontDoor/render/tasksImportSync.ts +63 -0
  340. package/ts/src/frontDoor/render/week.ts +113 -0
  341. package/ts/src/frontDoor/routing.ts +999 -0
  342. package/ts/src/frontDoor/runtimeRoute.ts +623 -0
  343. package/ts/src/frontDoor/searchRoute.ts +166 -0
  344. package/ts/src/frontDoor/seedPaths.ts +98 -0
  345. package/ts/src/frontDoor/soulRoute.ts +433 -0
  346. package/ts/src/frontDoor/tasksImportSyncRoute.ts +110 -0
  347. package/ts/src/frontDoor/tasksWriteRoute.ts +101 -0
  348. package/ts/src/frontDoor/usage.ts +176 -0
  349. package/ts/src/guards/claudePlugin.ts +252 -0
  350. package/ts/src/guards/packContents.ts +306 -0
  351. package/ts/src/guards/piSourcedSkills.ts +70 -0
  352. package/ts/src/guards/retiredRouteShadows.ts +57 -0
  353. package/ts/src/guards/retiredSurfaces.ts +564 -0
  354. package/ts/src/hooks/claude.ts +176 -0
  355. package/ts/src/hooks/codex.ts +45 -0
  356. package/ts/src/hooks/gemini.ts +126 -0
  357. package/ts/src/hooks/main.ts +109 -0
  358. package/ts/src/hooks/mirrorState.ts +71 -0
  359. package/ts/src/hooks/payload.ts +68 -0
  360. package/ts/src/hooks/runtime.ts +159 -0
  361. package/ts/src/identity/applyConsolidationIdentityUpdate.ts +67 -0
  362. package/ts/src/identity/descriptorGenerate.ts +162 -0
  363. package/ts/src/identity/identityEdit.ts +143 -0
  364. package/ts/src/identity/identityKey.ts +77 -0
  365. package/ts/src/identity/identityRead.ts +69 -0
  366. package/ts/src/identity/identityStore.ts +80 -0
  367. package/ts/src/identity/journeyListing.ts +53 -0
  368. package/ts/src/identity/personaInspect.ts +44 -0
  369. package/ts/src/identity/personaListing.ts +37 -0
  370. package/ts/src/identity/setIdentity.ts +64 -0
  371. package/ts/src/index.ts +231 -0
  372. package/ts/src/init/init.ts +111 -0
  373. package/ts/src/journey/journeyOptions.ts +131 -0
  374. package/ts/src/journey/journeyRemoval.ts +158 -0
  375. package/ts/src/journey/journeyStatus.ts +149 -0
  376. package/ts/src/journey/journeySyncFile.ts +87 -0
  377. package/ts/src/journey/journeyWrite.ts +203 -0
  378. package/ts/src/journey/parentJourney.ts +41 -0
  379. package/ts/src/journey/validateParentJourney.ts +55 -0
  380. package/ts/src/mcp/boundary.ts +151 -0
  381. package/ts/src/mcp/guards.ts +244 -0
  382. package/ts/src/mcp/main.ts +121 -0
  383. package/ts/src/mcp/payload.ts +141 -0
  384. package/ts/src/mcp/protocol.ts +141 -0
  385. package/ts/src/mcp/readModels.ts +241 -0
  386. package/ts/src/mcp/registry.ts +201 -0
  387. package/ts/src/mcp/serve.ts +120 -0
  388. package/ts/src/mcp/tools/deterministic.ts +137 -0
  389. package/ts/src/mcp/tools/providerCrossing.ts +187 -0
  390. package/ts/src/mcp/wire.ts +8 -0
  391. package/ts/src/memory/journal.ts +180 -0
  392. package/ts/src/memory/listing.ts +107 -0
  393. package/ts/src/memory/memoryWrite.ts +88 -0
  394. package/ts/src/memory/reinforcement.ts +35 -0
  395. package/ts/src/mirror/context.ts +161 -0
  396. package/ts/src/mirror/defaultResolution.ts +199 -0
  397. package/ts/src/mirror/orchestration.ts +364 -0
  398. package/ts/src/mirror/reception.ts +194 -0
  399. package/ts/src/mirror/render.ts +75 -0
  400. package/ts/src/mirror/runtimeSession.ts +205 -0
  401. package/ts/src/mode/operatingMode.ts +148 -0
  402. package/ts/src/observability/callOutcome.ts +115 -0
  403. package/ts/src/observability/inspectLedger.ts +329 -0
  404. package/ts/src/observability/ledgerHooks.ts +126 -0
  405. package/ts/src/observability/llmCalls.ts +154 -0
  406. package/ts/src/persona/detectPersona.ts +81 -0
  407. package/ts/src/planning/promptAssembly.ts +111 -0
  408. package/ts/src/planning/weekPending.ts +81 -0
  409. package/ts/src/planning/weekPlan.ts +183 -0
  410. package/ts/src/planning/weekSave.ts +117 -0
  411. package/ts/src/providers/config.ts +189 -0
  412. package/ts/src/providers/cost.ts +64 -0
  413. package/ts/src/providers/credits.ts +204 -0
  414. package/ts/src/providers/embedding.ts +321 -0
  415. package/ts/src/providers/familyProviders.ts +147 -0
  416. package/ts/src/providers/llm.ts +334 -0
  417. package/ts/src/providers/openrouter.ts +244 -0
  418. package/ts/src/providers/redaction.ts +110 -0
  419. package/ts/src/providers/replay.ts +15 -0
  420. package/ts/src/providers/transport.ts +410 -0
  421. package/ts/src/repair/encodingRepair.ts +253 -0
  422. package/ts/src/runtime/backup.ts +151 -0
  423. package/ts/src/runtime/channel.ts +146 -0
  424. package/ts/src/runtime/config.ts +168 -0
  425. package/ts/src/runtime/diagnose.ts +686 -0
  426. package/ts/src/runtime/git.ts +510 -0
  427. package/ts/src/runtime/installKind.ts +220 -0
  428. package/ts/src/runtime/migrate.ts +154 -0
  429. package/ts/src/runtime/packageIdentity.ts +102 -0
  430. package/ts/src/runtime/releaseNotes.ts +244 -0
  431. package/ts/src/runtime/status.ts +557 -0
  432. package/ts/src/runtime/strategies/clone.ts +200 -0
  433. package/ts/src/runtime/strategies/package.ts +328 -0
  434. package/ts/src/runtime/treeRoot.ts +30 -0
  435. package/ts/src/runtime/update.ts +445 -0
  436. package/ts/src/runtime/updateGate.ts +40 -0
  437. package/ts/src/runtime/updatePipeline.ts +159 -0
  438. package/ts/src/runtime/version.ts +36 -0
  439. package/ts/src/runtime/wiring.ts +130 -0
  440. package/ts/src/search/memorySearch.ts +338 -0
  441. package/ts/src/search/ranker.ts +154 -0
  442. package/ts/src/seed/seed.ts +256 -0
  443. package/ts/src/seed/seedYaml.ts +133 -0
  444. package/ts/src/soul/apply.ts +218 -0
  445. package/ts/src/soul/harvest.ts +267 -0
  446. package/ts/src/soul/prompts/soul_beauty_voice.md +103 -0
  447. package/ts/src/soul/prompts/soul_self_voice.md +219 -0
  448. package/ts/src/soul/prompts/soul_wisdom_voice.md +121 -0
  449. package/ts/src/soul/prompts.ts +85 -0
  450. package/ts/src/soul/render.ts +358 -0
  451. package/ts/src/soul/state.ts +212 -0
  452. package/ts/src/soul/transition.ts +37 -0
  453. package/ts/src/tasks/journeyPathParse.ts +113 -0
  454. package/ts/src/tasks/taskImportSync.ts +142 -0
  455. package/ts/src/tasks/taskStore.ts +312 -0
  456. package/ts/src/tasks/weekView.ts +120 -0
  457. package/ts/src/util/clipCodePoints.ts +26 -0
  458. package/ts/src/util/paths.ts +63 -0
  459. package/ts/src/util/program.ts +9 -0
  460. package/ts/src/util/pyGenerators.ts +144 -0
  461. package/ts/src/util/pythonText.ts +317 -0
  462. package/ts/src/util/semver.ts +45 -0
  463. package/ts/src/util/shellWord.ts +25 -0
  464. package/ts/src/util/slug.ts +64 -0
  465. package/ts/src/welcome/card.ts +339 -0
  466. package/ts/src/welcome/statusLine.ts +101 -0
  467. package/ts/src/welcome/updateCache.ts +129 -0
@@ -0,0 +1,300 @@
1
+ // CV22.DS7.US8 plateau 1 — shared parsing primitives for the roadmap grammars.
2
+ //
3
+ // Port of `src/memory/builder/roadmap_grammar.py`. Both the legacy
4
+ // `CV → Epic → Story` grammar and the Delivery Story grammar are parsed with
5
+ // these, and keeping them in one module is what stopped the heading/status
6
+ // regex drift that once made DS-grammar roadmaps unreadable while the CV
7
+ // grammar still worked.
8
+ //
9
+ // Three dialect traps, each of which a literal transcription of the Python
10
+ // patterns would walk into:
11
+ //
12
+ // 1. **`\s` is not `\s`.** In a Python `str` pattern `\s` is `str.isspace()`:
13
+ // it INCLUDES U+001C–U+001F and U+0085 and EXCLUDES U+FEFF. JavaScript's
14
+ // `\s` is the opposite on both counts. So these patterns are built from
15
+ // `PYTHON_WHITESPACE_CLASS`, the set US6 established, rather than `\s`.
16
+ // 2. **`.` diverges on line separators.** Python's `.` (no DOTALL) excludes
17
+ // only `\n`; JavaScript's excludes `\n`, `\r`, U+2028, and U+2029. A title
18
+ // containing U+2028 therefore continues in Python and stops in JavaScript.
19
+ // `[^\n]` restores Python's meaning.
20
+ // 3. **`str.strip()` is not `.trim()`**, same separator-set reason, so cell
21
+ // trimming goes through `pyStrip`.
22
+ //
23
+ // And one that cost a test failure before it was found: **`re.MULTILINE` is not
24
+ // the `m` flag.** Python's MULTILINE `^`/`$` anchor at `\n` only, while
25
+ // JavaScript's `m` also anchors at `\r`, U+2028, and U+2029. With `m`, a lazy
26
+ // title followed by `$` stops at an embedded U+2028 and silently truncates —
27
+ // which is what the `line_separator` fixture caught. So the anchors are written
28
+ // out as `(?<=^|\n)` and `(?=\n|$)` and the `m` flag is deliberately absent.
29
+ //
30
+ // `re.fullmatch` maps to anchoring the pattern, not to `.test()` on a substring.
31
+
32
+ import { clipCodePoints } from "#util/clipCodePoints.ts";
33
+ import { PYTHON_WHITESPACE_CLASS, pyStrip } from "#util/pythonText.ts";
34
+
35
+ const WS = `[${PYTHON_WHITESPACE_CLASS}]`;
36
+
37
+ /**
38
+ * `# <code> — <title>`, multiline. Codes may carry dots (`CV2.DS1`), hyphens
39
+ * (`DS-35`), dotted hyphenated children (`DS-35.US-1`), and a lowercase
40
+ * sub-story suffix (`CV21.E2.S1b`). The separator is an em dash OR a hyphen.
41
+ */
42
+ export const HEADING_RE = new RegExp(
43
+ `(?<=^|\\n)#${WS}+(?<code>[A-Za-z0-9.\\-]+)${WS}+[—-]${WS}+(?<title>[^\\n]+?)${WS}*(?=\\n|$)`,
44
+ "u",
45
+ );
46
+
47
+ /** `**Status:** <status>`. Unanchored at the start, exactly as Python's is. */
48
+ export const STATUS_RE = new RegExp(
49
+ `\\*\\*Status:\\*\\*${WS}*(?<status>[^\\n]+?)${WS}*(?=\\n|$)`,
50
+ "u",
51
+ );
52
+
53
+ /** `**Type:** <type>`. Lives beside the patterns it is parsed with. */
54
+ export const TYPE_RE = new RegExp(`\\*\\*Type:\\*\\*${WS}*(?<type>[^\\n]+?)${WS}*(?=\\n|$)`, "u");
55
+
56
+ /** A whole-cell `[label](target)` link. Anchored, to mean `re.fullmatch`. */
57
+ const MARKDOWN_LINK_RE = /^\[(?<label>[^\]]+)\]\((?<target>[^)]*)\)$/u;
58
+
59
+ export interface HeadingMatch {
60
+ code: string;
61
+ title: string;
62
+ }
63
+
64
+ /** First `# <code> — <title>` heading in a document, or `null`. */
65
+ export function matchHeading(content: string): HeadingMatch | null {
66
+ const match = HEADING_RE.exec(content);
67
+ if (!match?.groups) return null;
68
+ return { code: match.groups.code, title: match.groups.title };
69
+ }
70
+
71
+ /** First `**Status:**` value in a document, or `null`. */
72
+ export function matchStatus(content: string): string | null {
73
+ return STATUS_RE.exec(content)?.groups?.status ?? null;
74
+ }
75
+
76
+ /** First `**Type:**` value in a document, or `null`. */
77
+ export function matchType(content: string): string | null {
78
+ return TYPE_RE.exec(content)?.groups?.type ?? null;
79
+ }
80
+
81
+ /**
82
+ * Python `is_legacy_path`: true when the path sits under a `legacy/` component
83
+ * of the roadmap root. Component-wise, not a substring test — a directory named
84
+ * `non-legacy-notes` is not legacy.
85
+ *
86
+ * Python resolves both sides and returns `False` when the path lies outside the
87
+ * root (`relative_to` raises). Callers here pass components already relative to
88
+ * the root, so that case is an empty list.
89
+ */
90
+ export function isLegacyPath(relativeComponents: readonly string[]): boolean {
91
+ return relativeComponents.includes("legacy");
92
+ }
93
+
94
+ export interface MarkdownLink {
95
+ label: string;
96
+ target: string;
97
+ }
98
+
99
+ /** Python `parse_markdown_link`: a whole-cell link, or `null`. */
100
+ export function parseMarkdownLink(value: string): MarkdownLink | null {
101
+ const match = MARKDOWN_LINK_RE.exec(pyStrip(value));
102
+ if (!match?.groups) return null;
103
+ return { label: match.groups.label, target: match.groups.target };
104
+ }
105
+
106
+ /** Python `strip_markdown_link`: the link's label, or the stripped raw value. */
107
+ export function stripMarkdownLink(value: string): string {
108
+ return parseMarkdownLink(value)?.label ?? pyStrip(value);
109
+ }
110
+
111
+ /** A code span: a run of backticks up to the next run of the same length. */
112
+ const CODE_SPAN_RE = /(?<!`)(`+)(?!`)[\s\S]*?(?<!`)\1(?!`)/gu;
113
+
114
+ /** An inline link or image with a label, not escaped. The target may carry a quoted title. */
115
+ const INLINE_LINK_RE = /(?<!\\)!?\[([^\]\n]+)\]\([^)\n]*\)/gu;
116
+
117
+ /**
118
+ * A title with every inline Markdown link or image reduced to its label (CR018).
119
+ *
120
+ * A relative link is true only in the file it was written in, and Ariad carries a title
121
+ * into terminal cards, into files at other depths, and into folder names. So a title
122
+ * enters Ariad this way, from the roadmap and from `--item-title` alike. What
123
+ * `stripMarkdownLink` does for a cell that is one whole link is a case of this rule;
124
+ * that function stays for codes.
125
+ *
126
+ * A link inside a code span is code and stays as written, as does a link with an empty
127
+ * label, which has nothing to keep. Reference links (`[label][ref]`) and labels with
128
+ * nested brackets are left alone.
129
+ */
130
+ export function linkFreeTitle(title: string): string {
131
+ let result = "";
132
+ let last = 0;
133
+ for (const span of title.matchAll(CODE_SPAN_RE)) {
134
+ result += title.slice(last, span.index).replace(INLINE_LINK_RE, "$1");
135
+ result += span[0];
136
+ last = span.index + span[0].length;
137
+ }
138
+ return result + title.slice(last).replace(INLINE_LINK_RE, "$1");
139
+ }
140
+
141
+ /** The most a status clause takes on a card row; the widest status on this roadmap. */
142
+ export const STATUS_CLAUSE_WIDTH = 24;
143
+
144
+ /** The separators after which a status line stops being its status (CR103). */
145
+ const STATUS_SEPARATOR_RE = /\s(?:\u2014|\u2013|-|\u00b7)\s|\s\(|[;,.:]\s/u;
146
+
147
+ /** `**`, `__`, and the backticks of a code span: Markdown a card cannot render. */
148
+ const STATUS_MARKS_RE = /\*\*|__|`/gu;
149
+
150
+ /**
151
+ * A status line as the status its author declared: its first clause, Markdown
152
+ * reduced, bounded to `STATUS_CLAUSE_WIDTH` (CR103).
153
+ *
154
+ * Several roadmaps write a package's `**Status:**` line as a running changelog, and
155
+ * every Builder reader read it whole: the rows printed a hundred card lines of it, and
156
+ * the classifiers found a status word in its prose. The clause is what a row prints
157
+ * and what a classifier reads, so the two cannot disagree. It is applied where a status
158
+ * leaves a reader, so every `PullCandidate`, `RoadmapSnapshotItem`, `AuthoredPackage`,
159
+ * and `CandidateChild` carries a clause by construction; `matchStatus` still returns the
160
+ * line, for the reader that wants it.
161
+ *
162
+ * In order: `pyStrip`; links and images to their labels, then `**`, `__`, and backticks
163
+ * out, so a separator inside a link's target cannot cut, a label's words cut like the
164
+ * author's own, and a `**` that closes after the separator still goes; the cut before the first separator, never at the
165
+ * start; trailing punctuation off, and an empty clause gives way to the reduced line;
166
+ * then the clip, by the card's rule.
167
+ */
168
+ export function statusClause(status: string): string {
169
+ const reduced = pyStrip(linkFreeTitle(pyStrip(status)).replace(STATUS_MARKS_RE, ""));
170
+ const cut = reduced.search(STATUS_SEPARATOR_RE);
171
+ const clause = pyStrip((cut > 0 ? reduced.slice(0, cut) : reduced).replace(/[.:;,]+$/u, ""));
172
+ return clipCodePoints(clause === "" ? reduced : clause, STATUS_CLAUSE_WIDTH);
173
+ }
174
+
175
+ /**
176
+ * The cells of a Markdown table row, each `pyStrip`ped, with GFM's escape honored
177
+ * (CR107): `\|` is a pipe inside the cell, a code span included, and `\\` is a backslash,
178
+ * so `\\|` is a backslash and a border. Every other character is itself, as every other
179
+ * reader leaves it. The row's leading pipes go, and its trailing unescaped ones, as
180
+ * Python's `strip("|")` took them; an escaped trailing pipe is content.
181
+ *
182
+ * Three readers split on every pipe before this: the roadmap index's CV and DS tables,
183
+ * a Delivery Story's candidate table, and the Done preflight's status tables. A cell
184
+ * with an escape split in two and shifted every column after it, so Expand recommended
185
+ * a fragment, the index read a status from the wrong cell, and the preflight refused a
186
+ * Done row. One reader now, and `tableCell` is its inverse.
187
+ */
188
+ export function tableRowCells(line: string): string[] {
189
+ let start = 0;
190
+ while (start < line.length && line[start] === "|") start += 1;
191
+ let end = line.length;
192
+ while (end > start && line[end - 1] === "|" && !escapedAt(line, end - 1)) end -= 1;
193
+ const cells: string[] = [];
194
+ let cell = "";
195
+ for (let at = start; at < end; at += 1) {
196
+ const char = line[at];
197
+ const next = line[at + 1];
198
+ if (char === "\\" && at + 1 < end && (next === "|" || next === "\\")) {
199
+ cell += next;
200
+ at += 1;
201
+ continue;
202
+ }
203
+ if (char === "|") {
204
+ cells.push(pyStrip(cell));
205
+ cell = "";
206
+ continue;
207
+ }
208
+ cell += char;
209
+ }
210
+ cells.push(pyStrip(cell));
211
+ return cells;
212
+ }
213
+
214
+ /** Whether the pipe at `index` is escaped: preceded by an odd run of backslashes. */
215
+ function escapedAt(line: string, index: number): boolean {
216
+ let backslashes = 0;
217
+ for (let at = index - 1; at >= 0 && line[at] === "\\"; at -= 1) backslashes += 1;
218
+ return backslashes % 2 === 1;
219
+ }
220
+
221
+ /**
222
+ * `text` as a table cell: every backslash doubled, then every pipe escaped, so
223
+ * `tableRowCells` gives the text back exactly (CR107). The Delivery Story scaffold writes
224
+ * a title into two cells through it; nothing else Ariad writes is a table cell.
225
+ */
226
+ export function tableCell(text: string): string {
227
+ return text.replace(/\\/gu, "\\\\").replace(/\|/gu, "\\|");
228
+ }
229
+
230
+ /** Python `_CandidateChild`. Its `status` is the cell's clause (CR103). */
231
+ export interface CandidateChild {
232
+ readonly code: string;
233
+ readonly title: string;
234
+ readonly level: string;
235
+ readonly status: string;
236
+ }
237
+
238
+ const REQUIRED_COLUMNS = ["code", "story", "type", "status"] as const;
239
+
240
+ /**
241
+ * Python `_parse_candidate_stories`: a Delivery Story's `## Candidate Stories` table,
242
+ * header-driven. Expand reads its children with it, and Pull asks it whether a
243
+ * Delivery Story names a story (CR113), so the two read the table by one rule.
244
+ *
245
+ * Line-by-line rather than block-by-block, and the details matter:
246
+ *
247
+ * * a non-table line BREAKS the scan once a header was found, but is SKIPPED
248
+ * before — so the first canonical table wins and nothing after it is read;
249
+ * * `line.strip("|")` removes every leading and trailing pipe, not one;
250
+ * * duplicate header names keep the LAST index, because Python builds the map
251
+ * with a dict comprehension over `enumerate`;
252
+ * * a row shorter than the widest required column is skipped, not an error,
253
+ * which is how a ragged authored table degrades instead of blocking.
254
+ */
255
+ export function parseCandidateStories(content: string): CandidateChild[] {
256
+ const children: CandidateChild[] = [];
257
+ let columns: Map<string, number> | null = null;
258
+
259
+ for (const rawLine of content.split("\n")) {
260
+ const line = pyStrip(rawLine);
261
+ if (!line.startsWith("|")) {
262
+ if (columns !== null) break;
263
+ continue;
264
+ }
265
+ const cells = tableRowCells(line);
266
+
267
+ if (columns === null) {
268
+ // `cell.lower()` — ASCII header names in practice; Python's `str.lower()`
269
+ // and JavaScript's `toLowerCase()` differ only on characters no canonical
270
+ // header uses (`İ`, `ẞ`), and a divergence there fails closed by not
271
+ // matching the required set.
272
+ const lowered = cells.map((cell) => cell.toLowerCase());
273
+ if (REQUIRED_COLUMNS.every((name) => lowered.includes(name))) {
274
+ columns = new Map();
275
+ lowered.forEach((name, index) => {
276
+ columns?.set(name, index);
277
+ });
278
+ }
279
+ continue;
280
+ }
281
+
282
+ if (line.startsWith("|---")) continue;
283
+ if (cells.every((cell) => cell === "")) continue;
284
+ if (cells.every((cell) => [...cell].every((character) => character === "-"))) continue;
285
+
286
+ const widest = Math.max(...[...columns.values()]);
287
+ if (cells.length <= widest) continue;
288
+
289
+ const code = stripMarkdownLink(cells[columns.get("code") ?? 0] ?? "");
290
+ if (!code) continue;
291
+ const typeText = (cells[columns.get("type") ?? 0] ?? "").toLowerCase();
292
+ children.push({
293
+ code,
294
+ title: linkFreeTitle(cells[columns.get("story") ?? 0] ?? ""),
295
+ level: typeText.includes("technical") ? "technical_story" : "user_story",
296
+ status: statusClause(cells[columns.get("status") ?? 0] ?? ""),
297
+ });
298
+ }
299
+ return children;
300
+ }
@@ -0,0 +1,97 @@
1
+ // CV22.DS7.US8 plateau 1 — the one filesystem scan every roadmap reader shares.
2
+ //
3
+ // Python has three call sites that all do `sorted(roadmap_root.rglob("index.md"))`
4
+ // and then skip `legacy/`: `pull_candidates.inspect_pull_candidates`,
5
+ // `roadmap_position.resolve_roadmap_position`, and
6
+ // `story_paths._group_roadmap_headings`. They are one function here for the same
7
+ // reason `roadmap_grammar.py` exists in Python — three copies of a scan is three
8
+ // chances for the ordering to drift. CR002 deleted the position reader: a
9
+ // journey's position now comes from its cursor (`roadmapScope.ts`), not from the
10
+ // first file whose status reads "Active".
11
+ //
12
+ // The ordering is the whole point. `sorted()` over `Path` objects compares
13
+ // COMPONENT LISTS, so `a/b` precedes `a-x/c`; a JavaScript `sort()` over joined
14
+ // paths puts `a-x/c` first because `-` (0x2D) sorts before `/` (0x2F). That
15
+ // order decides which candidate `_recommend` returns and the order of the
16
+ // rendered Backlog, so a naive sort changes what the Navigator is told to pull
17
+ // next. Measured: Python 3.10 and 3.12 agree with each other, and on this
18
+ // project's real 352-file tree the two orders happen to coincide today — which
19
+ // is why the fixture stages the collision deliberately rather than trusting an
20
+ // accident of directory naming.
21
+
22
+ import { type Dirent, readdirSync, readFileSync } from "node:fs";
23
+ import { relative, sep } from "node:path";
24
+ import { comparePathComponents } from "#util/pythonText.ts";
25
+ import { isLegacyPath } from "./roadmapGrammar.ts";
26
+
27
+ export interface RoadmapFile {
28
+ /** Path components relative to the roadmap root, ending in `index.md`. */
29
+ components: string[];
30
+ /** POSIX path relative to the roadmap root. */
31
+ relativePath: string;
32
+ /** Absolute path on disk. */
33
+ absolutePath: string;
34
+ }
35
+
36
+ export interface RoadmapScanOptions {
37
+ /**
38
+ * Keep `legacy/` in the result. Default `false`, which is what the three
39
+ * grammar call sites need.
40
+ *
41
+ * It is an OPTION rather than a second function because Python has a fourth
42
+ * call site that does the same walk and does NOT skip the archive:
43
+ * `delivery_story_roadmap_closure.inspect_authored_closure`, whose status scan
44
+ * reads every `index.md` under the roadmap. So an archived table row for a known
45
+ * code blocks a Delivery Story's Done. Reusing this scan with its default would
46
+ * let TypeScript close a Delivery Story that Python refuses — the exact defect
47
+ * shape the plateau-5 panel stopped, and `authored_closure_reads_legacy_rows`
48
+ * fails a port that does it.
49
+ */
50
+ readonly includeLegacy?: boolean;
51
+ }
52
+
53
+ /**
54
+ * Python `sorted(roadmap_root.rglob("index.md"))`, minus `legacy/` unless asked.
55
+ *
56
+ * A missing or unreadable root yields an empty list, matching Python's
57
+ * `is_dir()` guard at every call site.
58
+ */
59
+ export function scanRoadmapIndexFiles(
60
+ roadmapRoot: string,
61
+ options: RoadmapScanOptions = {},
62
+ ): RoadmapFile[] {
63
+ let entries: Dirent<string>[];
64
+ try {
65
+ entries = readdirSync(roadmapRoot, { recursive: true, withFileTypes: true });
66
+ } catch {
67
+ return [];
68
+ }
69
+ const found: string[][] = [];
70
+ for (const entry of entries) {
71
+ if (entry.name !== "index.md") continue;
72
+ if (!entry.isFile()) continue;
73
+ const parents = relative(roadmapRoot, entry.parentPath).split(sep).filter(Boolean);
74
+ found.push([...parents, entry.name]);
75
+ }
76
+ found.sort(comparePathComponents);
77
+ return found
78
+ .filter((components) => options.includeLegacy === true || !isLegacyPath(components))
79
+ .map((components) => ({
80
+ components,
81
+ relativePath: components.join("/"),
82
+ absolutePath: [roadmapRoot, ...components].join(sep),
83
+ }));
84
+ }
85
+
86
+ /**
87
+ * Python's `try: path.read_text(encoding="utf-8") except OSError: continue`.
88
+ * An unreadable file is skipped, not fatal — a permission error in one package
89
+ * must not empty the whole roadmap.
90
+ */
91
+ export function readRoadmapFile(absolutePath: string): string | null {
92
+ try {
93
+ return readFileSync(absolutePath, "utf8");
94
+ } catch {
95
+ return null;
96
+ }
97
+ }
@@ -0,0 +1,210 @@
1
+ // CR002 — the journey's roadmap scope, derived from its delivery cursor.
2
+ //
3
+ // Every Builder surface that answers "where are we" or "what next" reads this
4
+ // scope instead of the whole project. Before CR002 two readers did: one returned
5
+ // the first `index.md` whose status contained "Active", the other recommended
6
+ // across every candidate in the tree. Neither knew which journey was asking, so a
7
+ // project holding several journeys' work told each of them the same answer, and
8
+ // usually a wrong one.
9
+ //
10
+ // The only roadmap fact a journey owns is the active item in its delivery cursor.
11
+ // Pull is the one cursor writer that sets it and `sync-cursor` the one that clears
12
+ // it — every other writer carries it forward — so "unscoped" always means no item
13
+ // has been pulled since the cursor was last synchronized.
14
+ //
15
+ // Identity is the authored heading code, never a folder. A package can live under
16
+ // any directory a human chose (the rule `storyPaths.ts` states), so the CV is the
17
+ // active item's code up to the first `.`, its package is found by its heading, and
18
+ // membership is strict descent by code. Strict, because the CV is the position,
19
+ // not a candidate: `recommend()` falls back to its first candidate at ANY level,
20
+ // so a CV still marked Active with nothing left under it would otherwise be
21
+ // recommended as its own next pull.
22
+
23
+ import { pyStrip } from "#util/pythonText.ts";
24
+ import {
25
+ type PullCandidate,
26
+ type PullCandidatesReport,
27
+ recommend,
28
+ roadmapPaths,
29
+ } from "./pullCandidates.ts";
30
+ import { linkFreeTitle, matchHeading, matchStatus, statusClause } from "./roadmapGrammar.ts";
31
+ import { readRoadmapFile } from "./roadmapScan.ts";
32
+ import { roadmapHeadingDirectories } from "./storyPaths.ts";
33
+
34
+ export type UnscopedReason = "no_cursor" | "no_active_item";
35
+
36
+ /** An authored roadmap package, as its own `index.md` describes it. */
37
+ export interface AuthoredPackage {
38
+ readonly code: string;
39
+ readonly title: string;
40
+ /** The clause of the package's `**Status:**` line (CR103), or `""` when it has none. */
41
+ readonly status: string;
42
+ /** POSIX, relative to the project root, ending in `index.md`. */
43
+ readonly path: string;
44
+ }
45
+
46
+ /** Where the journey stands, as far as the authored roadmap can say. */
47
+ export type ScopePosition =
48
+ /** The CV's own package. */
49
+ | { readonly kind: "cv_package"; readonly package: AuthoredPackage }
50
+ /** The CV has no package; the active item's own stands in for it. */
51
+ | { readonly kind: "item_package"; readonly package: AuthoredPackage }
52
+ /** Neither the CV nor the active item has an authored package. */
53
+ | { readonly kind: "no_package" }
54
+ /** Two or more packages claim `code` (the CV's, or the item's when the CV has none). */
55
+ | { readonly kind: "ambiguous"; readonly code: string; readonly paths: readonly string[] }
56
+ /** The journey has no project path, so there is no roadmap to read. */
57
+ | { readonly kind: "no_project" };
58
+
59
+ export type RoadmapScope =
60
+ | { readonly kind: "unscoped"; readonly reason: UnscopedReason }
61
+ | {
62
+ readonly kind: "active_item";
63
+ readonly activeItem: string;
64
+ readonly cvCode: string;
65
+ readonly position: ScopePosition;
66
+ };
67
+
68
+ /** The one cursor field the scope reads; every cursor view in the builder has it. */
69
+ export interface ScopeCursor {
70
+ readonly activeItem: string | null;
71
+ }
72
+
73
+ /** The candidate list a journey sees, and the one recommendation it may get. */
74
+ export interface ScopedPullCandidates {
75
+ readonly journey: string;
76
+ readonly method: string;
77
+ readonly scope: RoadmapScope;
78
+ /** Unscoped: every candidate. Scoped: the CV's strict descendants, minus the active item. */
79
+ readonly shown: readonly PullCandidate[];
80
+ /** Scoped: candidates outside the CV, the CV itself not counted. Unscoped: 0. */
81
+ readonly outsideCount: number;
82
+ /** Scoped: `recommend(shown)`. Unscoped: always `null`. */
83
+ readonly recommended: PullCandidate | null;
84
+ }
85
+
86
+ /** The CV a roadmap code belongs to: everything before the first `.`. */
87
+ export function cvCodeOf(code: string): string {
88
+ return code.split(".", 1)[0] ?? code;
89
+ }
90
+
91
+ /** Strict descent: `CV2.DS1` is inside `CV2`; `CV2` and `CV20.DS1` are not. */
92
+ export function isInsideCv(code: string, cvCode: string): boolean {
93
+ return code.startsWith(`${cvCode}.`);
94
+ }
95
+
96
+ /** The code one level up: everything before the last `.`. A CV has no parent. */
97
+ export function parentCodeOf(code: string): string | null {
98
+ const cut = code.lastIndexOf(".");
99
+ return cut === -1 ? null : code.slice(0, cut);
100
+ }
101
+
102
+ /**
103
+ * The active item's siblings: the other items with the same parent, in the order
104
+ * given. Its ancestors, its cousins, and its parent's own siblings never qualify,
105
+ * and exact comparison keeps `CV1.DS1` from claiming `CV1.DS10`'s children (CR019).
106
+ */
107
+ export function siblingsOf<T extends { readonly code: string }>(
108
+ items: readonly T[],
109
+ activeItem: string,
110
+ ): T[] {
111
+ const parent = parentCodeOf(activeItem);
112
+ if (parent === null) return [];
113
+ return items.filter((item) => item.code !== activeItem && parentCodeOf(item.code) === parent);
114
+ }
115
+
116
+ /** The scope a journey's cursor implies. Never throws for a roadmap defect. */
117
+ export function resolveRoadmapScope(
118
+ projectRoot: string | null,
119
+ cursor: ScopeCursor | null,
120
+ ): RoadmapScope {
121
+ if (cursor === null) return { kind: "unscoped", reason: "no_cursor" };
122
+ const activeItem = cursor.activeItem;
123
+ if (!activeItem) return { kind: "unscoped", reason: "no_active_item" };
124
+ const cvCode = cvCodeOf(activeItem);
125
+ return {
126
+ kind: "active_item",
127
+ activeItem,
128
+ cvCode,
129
+ position: resolvePosition(projectRoot, activeItem, cvCode),
130
+ };
131
+ }
132
+
133
+ function resolvePosition(
134
+ projectRoot: string | null,
135
+ activeItem: string,
136
+ cvCode: string,
137
+ ): ScopePosition {
138
+ if (projectRoot === null) return { kind: "no_project" };
139
+ const claims = roadmapHeadingDirectories(projectRoot);
140
+ const cv = claimFor(projectRoot, claims, cvCode);
141
+ if (cv.kind === "one") return { kind: "cv_package", package: cv.package };
142
+ if (cv.kind === "many") return { kind: "ambiguous", code: cvCode, paths: cv.paths };
143
+ if (activeItem === cvCode) return { kind: "no_package" };
144
+ const item = claimFor(projectRoot, claims, activeItem);
145
+ if (item.kind === "one") return { kind: "item_package", package: item.package };
146
+ if (item.kind === "many") return { kind: "ambiguous", code: activeItem, paths: item.paths };
147
+ return { kind: "no_package" };
148
+ }
149
+
150
+ type Claim =
151
+ | { readonly kind: "none" }
152
+ | { readonly kind: "one"; readonly package: AuthoredPackage }
153
+ | { readonly kind: "many"; readonly paths: readonly string[] };
154
+
155
+ function claimFor(
156
+ projectRoot: string,
157
+ claims: ReadonlyMap<string, readonly string[]>,
158
+ code: string,
159
+ ): Claim {
160
+ const directories = claims.get(code) ?? [];
161
+ if (directories.length > 1) return { kind: "many", paths: directories.map(indexPath) };
162
+ const only = directories[0];
163
+ if (only === undefined) return { kind: "none" };
164
+ const authored = readPackage(projectRoot, only);
165
+ return authored === null ? { kind: "none" } : { kind: "one", package: authored };
166
+ }
167
+
168
+ /** A roadmap-relative package directory as the project-relative path of its `index.md`. */
169
+ function indexPath(directory: string): string {
170
+ return directory ? `docs/project/roadmap/${directory}/index.md` : "docs/project/roadmap/index.md";
171
+ }
172
+
173
+ function readPackage(projectRoot: string, directory: string): AuthoredPackage | null {
174
+ const { roadmapRoot } = roadmapPaths(projectRoot);
175
+ const file = directory ? `${roadmapRoot}/${directory}/index.md` : `${roadmapRoot}/index.md`;
176
+ const content = readRoadmapFile(file);
177
+ if (content === null) return null;
178
+ const heading = matchHeading(content);
179
+ if (heading === null) return null;
180
+ const status = matchStatus(content);
181
+ return {
182
+ code: pyStrip(heading.code),
183
+ title: linkFreeTitle(pyStrip(heading.title)),
184
+ status: status === null ? "" : statusClause(status),
185
+ path: indexPath(directory),
186
+ };
187
+ }
188
+
189
+ /**
190
+ * The candidate list scoped to the journey. Unscoped, every candidate is shown and
191
+ * nothing is recommended: there is no journey position to recommend from, and a
192
+ * project-wide ranking is exactly the answer CR002 removed.
193
+ */
194
+ export function scopePullCandidates(
195
+ report: PullCandidatesReport,
196
+ scope: RoadmapScope,
197
+ ): ScopedPullCandidates {
198
+ const { journey, method, candidates } = report;
199
+ if (scope.kind === "unscoped") {
200
+ return { journey, method, scope, shown: candidates, outsideCount: 0, recommended: null };
201
+ }
202
+ const { cvCode, activeItem } = scope;
203
+ const shown = candidates.filter(
204
+ (candidate) => isInsideCv(candidate.code, cvCode) && candidate.code !== activeItem,
205
+ );
206
+ const outsideCount = candidates.filter(
207
+ (candidate) => candidate.code !== cvCode && !isInsideCv(candidate.code, cvCode),
208
+ ).length;
209
+ return { journey, method, scope, shown, outsideCount, recommended: recommend(shown) };
210
+ }