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,463 @@
1
+ // CV22.DS7.US8 plateau 1 — roadmap snapshot and pull candidates.
2
+ //
3
+ // Port of `src/memory/builder/pull_candidates.py` (the inspection half; the
4
+ // three rendered surfaces live in `pullCandidatesRender.ts`).
5
+ //
6
+ // Three snapshot grammars with precedence, and they are not interchangeable:
7
+ //
8
+ // * `cvTableItems` reads `| Code | Capability Value | Status |` and STOPS at
9
+ // the first non-empty, non-table line;
10
+ // * `dsTableItems` reads `| Code | Delivery Story | Status |` and merely
11
+ // leaves table mode at such a line, so it ACCUMULATES across
12
+ // `## Chapter N —` sections;
13
+ // * `cvHeadingItems` reads `## CV<n>: <title>` followed by `**Status:**`.
14
+ //
15
+ // The first grammar that yields anything wins, so a file carrying a CV table
16
+ // and a DS table returns only the CV rows. A port that merges them, or that
17
+ // gives the DS table the CV table's termination rule, reads a different roadmap.
18
+
19
+ import { PYTHON_WHITESPACE_CLASS, pySplitLines, pyStrip } from "#util/pythonText.ts";
20
+ import {
21
+ linkFreeTitle,
22
+ matchHeading,
23
+ matchStatus,
24
+ matchType,
25
+ statusClause,
26
+ stripMarkdownLink,
27
+ tableRowCells,
28
+ } from "./roadmapGrammar.ts";
29
+ import { readRoadmapFile, scanRoadmapIndexFiles } from "./roadmapScan.ts";
30
+
31
+ // Every line walk below is Python `content.splitlines()` via `pySplitLines`, not
32
+ // `split("\n")`: Python knows ELEVEN line boundaries (including \v, \f, U+001C
33
+ // \u2028, and \u2029), and a roadmap file carrying a form feed would otherwise
34
+ // parse as one long line here and as two lines there. Likewise every `.strip()`
35
+ // is `pyStrip`, because Python's whitespace set includes U+001C-U+001F and
36
+ // U+0085 and excludes U+FEFF — the exact opposite of `String.prototype.trim()`
37
+ // on both counts.
38
+
39
+ /** Python `_CANDIDATE_STATUSES`: substring markers, not an enum. */
40
+ const CANDIDATE_STATUSES = ["Planned", "Active", "Blocked", "Candidate"] as const;
41
+
42
+ const CV_TABLE_HEADER = "| Code | Capability Value | Status |";
43
+ const DS_TABLE_HEADER = "| Code | Delivery Story | Status |";
44
+
45
+ /** Python `_TOP_LEVEL_DS_RE`: a bare `DS-35`, anchored. */
46
+ const TOP_LEVEL_DS_RE = /^DS-\d+$/u;
47
+
48
+ // These three are Python `re.match` against an ALREADY-STRIPPED single line, so
49
+ // they anchor at the start and end of that line. `\s` is spelled out as Python's
50
+ // set for the same reason as in `roadmapGrammar.ts`, and `.` as `[^\n]`.
51
+ const WS = `[${PYTHON_WHITESPACE_CLASS}]`;
52
+
53
+ /** Python's `^##\s+(CV\d+):\s+(.+?)\s*$`. */
54
+ const CV_HEADING_RE = new RegExp(`^##${WS}+(CV\\d+):${WS}+([^\\n]+?)${WS}*$`, "u");
55
+
56
+ /** Python's `^\*\*Status:\*\*\s*(.+?)\s*$`. */
57
+ const LINE_STATUS_RE = new RegExp(`^\\*\\*Status:\\*\\*${WS}*([^\\n]+?)${WS}*$`, "u");
58
+
59
+ /** Python's `^-\s+(DS\d+)\s+(.+?)\.?$`. */
60
+ const DS_BULLET_RE = new RegExp(`^-${WS}+(DS\\d+)${WS}+([^\\n]+?)\\.?$`, "u");
61
+
62
+ export interface RoadmapSnapshotItem {
63
+ code: string;
64
+ title: string;
65
+ /** The status cell's clause, never the cell (CR103). */
66
+ status: string;
67
+ }
68
+
69
+ export interface RoadmapSnapshotReport {
70
+ journey: string;
71
+ method: string;
72
+ items: RoadmapSnapshotItem[];
73
+ source: string | null;
74
+ }
75
+
76
+ export type CandidateLevel = "cv" | "delivery_story" | "user_story" | "technical_story";
77
+
78
+ export interface PullCandidate {
79
+ code: string;
80
+ title: string;
81
+ level: string;
82
+ /** The status line's clause, never the line (CR103). */
83
+ status: string;
84
+ /** POSIX path relative to the project root, as Python's `relative_to(root)`. */
85
+ path: string;
86
+ }
87
+
88
+ /**
89
+ * The raw project scan. It carries no recommendation: since CR002 a
90
+ * recommendation exists only for a journey's scope (`scopePullCandidates` in
91
+ * `roadmapScope.ts`), never for the project as a whole.
92
+ */
93
+ export interface PullCandidatesReport {
94
+ journey: string;
95
+ method: string;
96
+ candidates: PullCandidate[];
97
+ }
98
+
99
+ export interface ProjectPaths {
100
+ /** Absolute project root. */
101
+ root: string;
102
+ /** Absolute `<root>/docs/project/roadmap`. */
103
+ roadmapRoot: string;
104
+ }
105
+
106
+ /** `<root>/docs/project/roadmap`, the only roadmap location Python looks in. */
107
+ export function roadmapPaths(root: string): ProjectPaths {
108
+ return { root, roadmapRoot: `${root}/docs/project/roadmap` };
109
+ }
110
+
111
+ function hasCandidateStatus(status: string): boolean {
112
+ return CANDIDATE_STATUSES.some((marker) => status.includes(marker));
113
+ }
114
+
115
+ /**
116
+ * Python `inspect_roadmap_snapshot`: the compact snapshot from
117
+ * `docs/project/roadmap/index.md` alone — not the whole tree.
118
+ *
119
+ * `source` is the project-relative path of that one file, or `null` when the
120
+ * project path is absent, the file is missing, or it cannot be read.
121
+ */
122
+ export function inspectRoadmapSnapshot(
123
+ root: string | null,
124
+ options: { journey: string; method: string },
125
+ ): RoadmapSnapshotReport {
126
+ const empty: RoadmapSnapshotReport = {
127
+ journey: options.journey,
128
+ method: options.method,
129
+ items: [],
130
+ source: null,
131
+ };
132
+ if (root === null) return empty;
133
+ const indexPath = `${root}/docs/project/roadmap/index.md`;
134
+ const content = readRoadmapFile(indexPath);
135
+ if (content === null) return empty;
136
+ return {
137
+ journey: options.journey,
138
+ method: options.method,
139
+ items: snapshotItemsFromContent(content),
140
+ source: "docs/project/roadmap/index.md",
141
+ };
142
+ }
143
+
144
+ export function snapshotItemsFromContent(content: string): RoadmapSnapshotItem[] {
145
+ const cv = cvTableItems(content);
146
+ if (cv.length > 0) return cv;
147
+ const ds = dsTableItems(content);
148
+ if (ds.length > 0) return ds;
149
+ return cvHeadingItems(content);
150
+ }
151
+
152
+ /**
153
+ * Python `_cv_table_items`. Note the termination: any non-empty line that does
154
+ * not start with `|` BREAKS the scan, so only the first CV table is read.
155
+ */
156
+ function cvTableItems(content: string): RoadmapSnapshotItem[] {
157
+ const items: RoadmapSnapshotItem[] = [];
158
+ let inTable = false;
159
+ for (const rawLine of pySplitLines(content)) {
160
+ const line = pyStrip(rawLine);
161
+ if (line === CV_TABLE_HEADER) {
162
+ inTable = true;
163
+ continue;
164
+ }
165
+ if (inTable && line.startsWith("|---")) continue;
166
+ if (inTable && line.startsWith("|")) {
167
+ const parts = tableRowCells(line);
168
+ if (parts.length >= 3) {
169
+ items.push({
170
+ code: stripMarkdownLink(parts[0] ?? ""),
171
+ title: linkFreeTitle(parts[1] ?? ""),
172
+ status: statusClause(parts[2] ?? ""),
173
+ });
174
+ }
175
+ continue;
176
+ }
177
+ if (inTable && line) break;
178
+ }
179
+ return items;
180
+ }
181
+
182
+ /**
183
+ * Python `_ds_table_items`. Same shape as the CV table with one crucial
184
+ * difference: a non-table line only leaves table mode (`in_table = False`), so
185
+ * a later `| Code | Delivery Story | Status |` header resumes and the rows
186
+ * ACCUMULATE across chapters.
187
+ */
188
+ function dsTableItems(content: string): RoadmapSnapshotItem[] {
189
+ const items: RoadmapSnapshotItem[] = [];
190
+ let inTable = false;
191
+ for (const rawLine of pySplitLines(content)) {
192
+ const line = pyStrip(rawLine);
193
+ if (line === DS_TABLE_HEADER) {
194
+ inTable = true;
195
+ continue;
196
+ }
197
+ if (inTable && line.startsWith("|---")) continue;
198
+ if (inTable && line.startsWith("|")) {
199
+ const parts = tableRowCells(line);
200
+ if (parts.length >= 3) {
201
+ items.push({
202
+ code: stripMarkdownLink(parts[0] ?? ""),
203
+ title: linkFreeTitle(parts[1] ?? ""),
204
+ status: statusClause(parts[2] ?? ""),
205
+ });
206
+ }
207
+ continue;
208
+ }
209
+ if (inTable) inTable = false;
210
+ }
211
+ return items;
212
+ }
213
+
214
+ /**
215
+ * Python `_cv_heading_items`: `## CV<n>: <title>` then the next `**Status:**`.
216
+ * The pairing is consumed — `current_cv` is cleared after a status matches — so
217
+ * a second status line without an intervening heading is ignored.
218
+ */
219
+ function cvHeadingItems(content: string): RoadmapSnapshotItem[] {
220
+ const items: RoadmapSnapshotItem[] = [];
221
+ let currentCv: { code: string; title: string } | null = null;
222
+ for (const rawLine of pySplitLines(content)) {
223
+ const line = pyStrip(rawLine);
224
+ const cvMatch = CV_HEADING_RE.exec(line);
225
+ if (cvMatch) {
226
+ currentCv = { code: cvMatch[1] ?? "", title: linkFreeTitle(pyStrip(cvMatch[2] ?? "")) };
227
+ continue;
228
+ }
229
+ const statusMatch = LINE_STATUS_RE.exec(line);
230
+ if (statusMatch && currentCv) {
231
+ items.push({
232
+ code: currentCv.code,
233
+ title: currentCv.title,
234
+ status: statusClause(statusMatch[1] ?? ""),
235
+ });
236
+ currentCv = null;
237
+ }
238
+ }
239
+ return items;
240
+ }
241
+
242
+ /**
243
+ * Python `inspect_pull_candidates`: scan every non-legacy `index.md`, collect
244
+ * the package's own candidate plus any `Candidate Delivery Stories:` bullets,
245
+ * then drop everything a `done.md` covers.
246
+ */
247
+ export function inspectPullCandidates(
248
+ root: string | null,
249
+ options: { journey: string; method: string },
250
+ ): PullCandidatesReport {
251
+ if (root === null) {
252
+ return { journey: options.journey, method: options.method, candidates: [] };
253
+ }
254
+ const { roadmapRoot } = roadmapPaths(root);
255
+ const files = scanRoadmapIndexFiles(roadmapRoot);
256
+
257
+ // Python's path is relative to the PROJECT root, not the roadmap root.
258
+ const projectPrefix = "docs/project/roadmap/";
259
+ const raw: PullCandidate[] = [];
260
+ const contents = new Map<string, string>();
261
+ for (const file of files) {
262
+ const content = readRoadmapFile(file.absolutePath);
263
+ if (content === null) continue;
264
+ contents.set(file.relativePath, content);
265
+ const projectRelative = `${projectPrefix}${file.relativePath}`;
266
+ const own = candidateFromIndexContent(projectRelative, content);
267
+ if (own !== null) raw.push(own);
268
+ raw.push(...candidateDeliveryStoriesFromContent(projectRelative, content));
269
+ }
270
+
271
+ const candidates = raw.filter(
272
+ (candidate) => !candidateHasDoneArtifact(root, candidate, files, contents),
273
+ );
274
+ return { journey: options.journey, method: options.method, candidates };
275
+ }
276
+
277
+ function candidateFromIndexContent(
278
+ projectRelativePath: string,
279
+ content: string,
280
+ ): PullCandidate | null {
281
+ const heading = matchHeading(content);
282
+ const status = matchStatus(content);
283
+ if (!heading || status === null) return null;
284
+ const statusText = statusClause(status);
285
+ if (!hasCandidateStatus(statusText)) return null;
286
+ const code = pyStrip(heading.code);
287
+ return {
288
+ code,
289
+ title: linkFreeTitle(pyStrip(heading.title)),
290
+ level: levelFor(code, content),
291
+ status: statusText,
292
+ path: projectRelativePath,
293
+ };
294
+ }
295
+
296
+ /**
297
+ * Python `_candidate_delivery_stories_from_content`: the `Candidate Delivery
298
+ * Stories:` bullet list under a `## CV<n>:` heading.
299
+ *
300
+ * Each Delivery Story carries its own title. Python prefixed the CV's as
301
+ * `"<CV title> / <DS title>"`, and five readers cut every title at a `/` to undo
302
+ * it, so any title with a slash of its own reached the Navigator as a fragment.
303
+ * CR018 removed the prefix and the cuts together; a surface that needs the CV's
304
+ * title reads the CV's own row.
305
+ */
306
+ function candidateDeliveryStoriesFromContent(
307
+ projectRelativePath: string,
308
+ content: string,
309
+ ): PullCandidate[] {
310
+ const candidates: PullCandidate[] = [];
311
+ let currentCv: string | null = null;
312
+ let currentStatus: string | null = null;
313
+ let inCandidateDeliveryStories = false;
314
+
315
+ for (const rawLine of pySplitLines(content)) {
316
+ const line = pyStrip(rawLine);
317
+ const cvMatch = CV_HEADING_RE.exec(line);
318
+ if (cvMatch) {
319
+ currentCv = cvMatch[1] ?? null;
320
+ currentStatus = null;
321
+ inCandidateDeliveryStories = false;
322
+ continue;
323
+ }
324
+ const statusMatch = LINE_STATUS_RE.exec(line);
325
+ if (statusMatch && currentCv) {
326
+ currentStatus = statusClause(statusMatch[1] ?? "");
327
+ continue;
328
+ }
329
+ if (line === "Candidate Delivery Stories:") {
330
+ inCandidateDeliveryStories = true;
331
+ continue;
332
+ }
333
+ if (inCandidateDeliveryStories && line.startsWith("- ") && currentCv) {
334
+ const dsMatch = DS_BULLET_RE.exec(line);
335
+ if (!dsMatch) continue;
336
+ const status = currentStatus || "Candidate";
337
+ if (!hasCandidateStatus(status)) continue;
338
+ candidates.push({
339
+ code: `${currentCv}.${dsMatch[1]}`,
340
+ title: linkFreeTitle(pyStrip(dsMatch[2] ?? "")),
341
+ level: "delivery_story",
342
+ status,
343
+ path: projectRelativePath,
344
+ });
345
+ continue;
346
+ }
347
+ if (inCandidateDeliveryStories && line && !line.startsWith("- ")) {
348
+ inCandidateDeliveryStories = false;
349
+ }
350
+ }
351
+ return candidates;
352
+ }
353
+
354
+ /**
355
+ * Python `_candidate_has_done_artifact`. Two-step, and the second step is the
356
+ * one a port drops: when the candidate's own file does not claim its code (a
357
+ * bullet-derived candidate, whose path is the CV index), Python searches the
358
+ * WHOLE tree for a package whose heading claims that code, then checks for a
359
+ * `done.md` at that package or any ancestor up to the roadmap root.
360
+ *
361
+ * So a child package with no `done.md` of its own is still filtered out when an
362
+ * ancestor has one.
363
+ */
364
+ function candidateHasDoneArtifact(
365
+ root: string,
366
+ candidate: PullCandidate,
367
+ files: readonly { components: string[]; relativePath: string; absolutePath: string }[],
368
+ contents: ReadonlyMap<string, string>,
369
+ ): boolean {
370
+ const projectPrefix = "docs/project/roadmap/";
371
+ const candidateRelative = candidate.path.startsWith(projectPrefix)
372
+ ? candidate.path.slice(projectPrefix.length)
373
+ : null;
374
+
375
+ let searchRelatives: string[] = [];
376
+ if (candidateRelative !== null) {
377
+ const ownContent = contents.get(candidateRelative);
378
+ const ownHeading = ownContent ? matchHeading(ownContent) : null;
379
+ const ownCode = ownHeading ? pyStrip(ownHeading.code) : undefined;
380
+ if (ownCode === candidate.code) searchRelatives = [candidateRelative];
381
+ }
382
+ if (searchRelatives.length === 0) {
383
+ searchRelatives = files
384
+ .filter((file) => {
385
+ const heading = matchHeading(contents.get(file.relativePath) ?? "");
386
+ return heading !== null && pyStrip(heading.code) === candidate.code;
387
+ })
388
+ .map((file) => file.relativePath);
389
+ }
390
+
391
+ for (const relativePath of searchRelatives) {
392
+ const packageComponents = relativePath.split("/").slice(0, -1);
393
+ if (hasDoneArtifact(root, packageComponents)) return true;
394
+ }
395
+ return false;
396
+ }
397
+
398
+ /** Python `_has_done_artifact`: walk up to (and including) the roadmap root. */
399
+ function hasDoneArtifact(root: string, packageComponents: readonly string[]): boolean {
400
+ const { roadmapRoot } = roadmapPaths(root);
401
+ for (let depth = packageComponents.length; depth >= 0; depth -= 1) {
402
+ const current = [roadmapRoot, ...packageComponents.slice(0, depth)].join("/");
403
+ if (readRoadmapFile(`${current}/done.md`) !== null) return true;
404
+ }
405
+ return false;
406
+ }
407
+
408
+ /**
409
+ * Python `_level_for`: `**Type:**` decides by SUBSTRING, so "Technical Story"
410
+ * and "a technical spike" both mean `technical_story`. Falls back to the code
411
+ * shape.
412
+ */
413
+ export function levelFor(code: string, content: string): string {
414
+ const type = matchType(content);
415
+ if (type !== null) {
416
+ const storyType = pyStrip(type).toLowerCase();
417
+ if (storyType.includes("technical")) return "technical_story";
418
+ if (storyType.includes("user")) return "user_story";
419
+ }
420
+ if (code.includes(".DS") || TOP_LEVEL_DS_RE.test(code)) return "delivery_story";
421
+ return "cv";
422
+ }
423
+
424
+ /**
425
+ * Python `_recommend`: status is the OUTER loop and level the inner one, so a
426
+ * Planned delivery story beats a Candidate user story. Falls back to the first
427
+ * candidate in scan order — which is why the scan order matters.
428
+ */
429
+ export function recommend(candidates: readonly PullCandidate[]): PullCandidate | null {
430
+ for (const preferredStatus of ["Planned", "Candidate", "Active", "Blocked"]) {
431
+ for (const preferredLevel of ["user_story", "technical_story", "delivery_story"]) {
432
+ for (const candidate of candidates) {
433
+ if (candidate.level === preferredLevel && candidate.status.includes(preferredStatus)) {
434
+ return candidate;
435
+ }
436
+ }
437
+ }
438
+ }
439
+ return candidates[0] ?? null;
440
+ }
441
+
442
+ /**
443
+ * Python `_status_marker`. Order matters: Active is tested before Candidate. Given a
444
+ * clause (CR103), so its fallback prints at most `○ ` and `STATUS_CLAUSE_WIDTH`, and a
445
+ * Blocked CV in focus keeps its title on the snapshot's focus row.
446
+ */
447
+ export function statusMarker(status: string): string {
448
+ if (status.includes("Active") || status.includes("In Progress")) return "◉ active";
449
+ if (status.includes("Candidate")) return "◉ candidate";
450
+ if (status.includes("Planned")) return "○ planned";
451
+ if (status.includes("Done")) return "✓ done";
452
+ return `○ ${status.toLowerCase()}`;
453
+ }
454
+
455
+ /** Python `_format_candidate`, given a candidate whose status is its clause (CR103). */
456
+ export function formatCandidate(candidate: PullCandidate): string {
457
+ return `${candidate.code} — ${candidate.title} [${candidate.level}] ${candidate.status} (${candidate.path})`;
458
+ }
459
+
460
+ /** Python `_candidate_lines`. */
461
+ export function candidateLines(candidates: readonly PullCandidate[]): string[] {
462
+ return candidates.map(formatCandidate);
463
+ }
@@ -0,0 +1,238 @@
1
+ // CV22.DS7.US8 plateau 1 — the three roadmap-inspection surfaces.
2
+ //
3
+ // Port of the renderers in `src/memory/builder/pull_candidates.py`:
4
+ // `PROJECT POSITION`, `ROADMAP SNAPSHOT`, and `PULL CANDIDATES`. All three are
5
+ // `transport=verbatim`, so what matters is bytes, not layout intent.
6
+ //
7
+ // The hardcoded frame lines in Python are RAGGED, measured in code points of
8
+ // their inner width:
9
+ //
10
+ // ╭…╮ / ╰…╯ and the blank rows 56 (what `cardText` also produces)
11
+ // `🧭 PROJECT POSITION` 54 (two short)
12
+ // `🟪■ PULL CANDIDATES` 55 (one short)
13
+ //
14
+ // That is not a defect to fix here. The author padded them by eye against a
15
+ // terminal, where an astral-plane glyph occupies two columns but one code point,
16
+ // so the literals are visually aligned and numerically inconsistent. A port that
17
+ // normalizes them to 56 produces a prettier card and fails parity. They are
18
+ // copied verbatim and the golden grades them. Two more, the empty `roadmap field`
19
+ // and `Backlog` rows, left with CR002: an unscoped snapshot now states the
20
+ // scope in `cardLine`'s own width instead.
21
+ //
22
+ // CR002: "where are we" and "what next" come from the journey's scoped view
23
+ // (`roadmapScope.ts`), never from the whole project. Unscoped, a surface names no
24
+ // focus and no recommendation, labels its list project-wide, and gives the literal
25
+ // Pull command; the words are `scopePhrases.ts`'s.
26
+ //
27
+ // CR103: every status these surfaces print or mark is a clause, read once by the
28
+ // roadmap readers. The focus row and the progress line mark it; the candidate rows
29
+ // print it. A Blocked CV in focus once printed its whole status on the right of the
30
+ // focus row and pushed the border off the card.
31
+
32
+ import { cardClipped, cardLine, cardPrefixed, cardText, cardWrapped } from "./card.ts";
33
+ import {
34
+ candidateLines,
35
+ formatCandidate,
36
+ type PullCandidate,
37
+ type RoadmapSnapshotItem,
38
+ type RoadmapSnapshotReport,
39
+ statusMarker,
40
+ } from "./pullCandidates.ts";
41
+ import type { ScopedPullCandidates } from "./roadmapScope.ts";
42
+ import {
43
+ candidateListHeader,
44
+ NO_ITEM_PULLED_YET,
45
+ noRecommendationLines,
46
+ outsideCountLines,
47
+ scopeFocus,
48
+ } from "./scopePhrases.ts";
49
+ import { wrapAriadSurface } from "./surfaceProtocol.ts";
50
+
51
+ const FRAME_TOP = "╭────────────────────────────────────────────────────────╮";
52
+ const FRAME_BOTTOM = "╰────────────────────────────────────────────────────────╯";
53
+ const FRAME_BLANK = "│ │";
54
+
55
+ /** Python `_format_project_recommendation`, for a recommendation that exists. */
56
+ function formatProjectRecommendation(candidate: PullCandidate): string {
57
+ return `🟦[${candidate.code}] ${candidate.title} — recommended next pull`;
58
+ }
59
+
60
+ /**
61
+ * Python `_project_focus_lines`, minus its `available candidates: none` line:
62
+ * "What looks next?" now says what is left in the scope, so the focus does not
63
+ * repeat it. The progress line strips the marker glyphs from `statusMarker`'s
64
+ * output with three sequential replaces, so a status whose text itself contains
65
+ * `◉ ` would lose it too. A focus with no status (a stated placeholder) has no
66
+ * progress to show.
67
+ */
68
+ function projectFocusLines(focus: RoadmapSnapshotItem | null): string[] {
69
+ if (focus === null) return [cardText(NO_ITEM_PULLED_YET)];
70
+ const lines = cardWrapped(`🟪[${focus.code}] ${focus.title}`);
71
+ if (focus.status) {
72
+ const marker = statusMarker(focus.status);
73
+ const progress = marker.replace("◉ ", "").replace("○ ", "").replace("✓ ", "");
74
+ lines.push(...cardWrapped(`progress: ${progress}`));
75
+ }
76
+ return lines;
77
+ }
78
+
79
+ /** The scoped candidate list: its header, the candidates, and what it leaves out. */
80
+ function candidateListLines(view: ScopedPullCandidates): string[] {
81
+ return [
82
+ cardText(candidateListHeader(view.scope)),
83
+ ...cardPrefixed(candidateLines(view.shown), "-"),
84
+ ...outsideCountLines(view).flatMap(cardWrapped),
85
+ ];
86
+ }
87
+
88
+ /** The recommendation, formatted by the surface; or what stands in its place. */
89
+ function nextMoveLines(
90
+ view: ScopedPullCandidates,
91
+ format: (candidate: PullCandidate) => string,
92
+ ): string[] {
93
+ if (view.recommended !== null) return cardWrapped(format(view.recommended));
94
+ return noRecommendationLines(view).flatMap(cardWrapped);
95
+ }
96
+
97
+ /** Python `render_project_position_report`, over the journey's scoped view. */
98
+ export function renderProjectPositionReport(
99
+ report: RoadmapSnapshotReport,
100
+ options: { view: ScopedPullCandidates; justMoved?: string | null },
101
+ ): string {
102
+ const { view } = options;
103
+ const justMoved = options.justMoved ?? null;
104
+ const focus = scopeFocus(report.items, view.scope);
105
+ const lines: string[] = [
106
+ "Roadmap",
107
+ "",
108
+ FRAME_TOP,
109
+ "│ 🧭 PROJECT POSITION │",
110
+ FRAME_BLANK,
111
+ cardText("Where are we now?"),
112
+ ...projectFocusLines(focus),
113
+ FRAME_BLANK,
114
+ ];
115
+ if (justMoved) {
116
+ lines.push(cardText("What just moved?"), ...cardWrapped(justMoved), FRAME_BLANK);
117
+ }
118
+ lines.push(
119
+ cardText("What looks next?"),
120
+ ...nextMoveLines(view, formatProjectRecommendation),
121
+ FRAME_BLANK,
122
+ ...candidateListLines(view),
123
+ FRAME_BLANK,
124
+ ...cardWrapped("Choose a pull when ready."),
125
+ FRAME_BOTTOM,
126
+ );
127
+ return wrapAriadSurface("project_position", `${lines.join("\n")}\n`);
128
+ }
129
+
130
+ /**
131
+ * Python `render_roadmap_snapshot_report`.
132
+ *
133
+ * Since CR002 the focus is the journey's CV and the `current` row is the scoped
134
+ * recommendation: one question, where Python asked two. Python chose `current` by
135
+ * running `_recommend` over only the focus CV's OWN delivery candidates
136
+ * (`code.startswith(f"{focus.code}.")`), a different question from the
137
+ * report-level recommendation — so the two rows can
138
+ * name different stories, and do whenever the recommended pull belongs to
139
+ * another CV.
140
+ */
141
+ export function renderRoadmapSnapshotReport(
142
+ report: RoadmapSnapshotReport,
143
+ options: { view: ScopedPullCandidates },
144
+ ): string {
145
+ const { view } = options;
146
+ const focus = scopeFocus(report.items, view.scope);
147
+ const result = view.shown.length > 0 ? "ready to pull" : "no pull candidates";
148
+ const lines: string[] = [
149
+ "ROADMAP SNAPSHOT",
150
+ "Delivery field overview",
151
+ "",
152
+ "view overview",
153
+ `result of roadmap-snapshot ${result}`,
154
+ "",
155
+ FRAME_TOP,
156
+ ];
157
+
158
+ if (focus === null) {
159
+ lines.push(cardLine("roadmap field", NO_ITEM_PULLED_YET));
160
+ } else {
161
+ lines.push(
162
+ // The value row says the CV's title whole, so the focus row may cut it (CR018).
163
+ cardLine(
164
+ `🟪[${focus.code}] ${focus.title}`,
165
+ focus.status ? statusMarker(focus.status) : "",
166
+ {
167
+ clip: true,
168
+ },
169
+ ),
170
+ ...cardWrapped(`value: ${focus.title}`),
171
+ FRAME_BLANK,
172
+ );
173
+ const current = view.recommended;
174
+ if (current !== null) {
175
+ const codeTail = current.code.split(".").pop() ?? current.code;
176
+ lines.push(
177
+ cardLine(` └─ 🟦[${codeTail}] ${current.title}`, "◉ current", { clip: true }),
178
+ cardText(" progress: not started"),
179
+ FRAME_BLANK,
180
+ );
181
+ }
182
+ lines.push(cardText(" Backlog"));
183
+ if (view.shown.length > 0) {
184
+ for (const candidate of view.shown) {
185
+ // Pull Candidates, rendered with this surface, lists each title whole (CR018).
186
+ lines.push(cardClipped(` ○ 🟦[${candidate.code}] ${candidate.title}`));
187
+ }
188
+ } else {
189
+ lines.push(cardText(" none"));
190
+ }
191
+ lines.push(
192
+ FRAME_BLANK,
193
+ cardText(" Recently promoted from Exploration"),
194
+ cardText(" none"),
195
+ FRAME_BLANK,
196
+ cardText(" Active constraints"),
197
+ cardText(" ✕ do not start candidates automatically"),
198
+ cardText(" ✓ Navigator explicitly chooses Pull"),
199
+ );
200
+ }
201
+
202
+ lines.push(
203
+ FRAME_BOTTOM,
204
+ "",
205
+ "source",
206
+ report.source ?? "none",
207
+ "",
208
+ "boundary",
209
+ "Roadmap was inspected only. No item was pulled. No lifecycle work was executed.",
210
+ );
211
+ return wrapAriadSurface("roadmap_snapshot", `${lines.join("\n")}\n`);
212
+ }
213
+
214
+ /** Python `render_pull_candidates_report`, over the journey's scoped view. */
215
+ export function renderPullCandidatesReport(view: ScopedPullCandidates): string {
216
+ const lines: string[] = [
217
+ "Delivery",
218
+ "",
219
+ FRAME_TOP,
220
+ "│ 🟪■ PULL CANDIDATES │",
221
+ FRAME_BLANK,
222
+ cardText("journey"),
223
+ cardText(view.journey),
224
+ FRAME_BLANK,
225
+ cardText("method"),
226
+ cardText(view.method),
227
+ FRAME_BLANK,
228
+ ...candidateListLines(view),
229
+ FRAME_BLANK,
230
+ cardText("recommended pull"),
231
+ ...nextMoveLines(view, formatCandidate),
232
+ FRAME_BLANK,
233
+ cardText("boundary"),
234
+ ...cardWrapped("No item was pulled. No lifecycle work was executed."),
235
+ FRAME_BOTTOM,
236
+ ];
237
+ return wrapAriadSurface("pull_candidates", `${lines.join("\n")}\n`);
238
+ }