raga-cli 0.0.0-stage → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (487) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/LICENSE +21 -0
  3. package/README.md +162 -2
  4. package/bundled-skills/AGENTS.md +5 -0
  5. package/bundled-skills/README.md +25 -0
  6. package/bundled-skills/agents/comment-sicko.json +9 -0
  7. package/bundled-skills/agents/comment-sicko.md +28 -0
  8. package/bundled-skills/agents/model-roles.json +41 -0
  9. package/bundled-skills/agents/poteto-agent.json +9 -0
  10. package/bundled-skills/agents/poteto-agent.md +4 -0
  11. package/bundled-skills/agents/worker.json +8 -0
  12. package/bundled-skills/agents/worker.md +1 -0
  13. package/bundled-skills/bundle.json +1514 -0
  14. package/bundled-skills/dependencies.json +40 -0
  15. package/bundled-skills/docs/guide/01-setup.md +3 -0
  16. package/bundled-skills/docs/guide/02-peak.md +135 -0
  17. package/bundled-skills/docs/guide/03-understand.md +79 -0
  18. package/bundled-skills/docs/guide/04-design.md +133 -0
  19. package/bundled-skills/docs/guide/05-build-and-clean.md +83 -0
  20. package/bundled-skills/docs/guide/06-verify-and-ship.md +166 -0
  21. package/bundled-skills/docs/guide/07-overnight.md +118 -0
  22. package/bundled-skills/docs/guide/08-principles.md +72 -0
  23. package/bundled-skills/docs/guide/09-make-it-yours.md +96 -0
  24. package/bundled-skills/docs/guide/10-recipes-and-pitfalls.md +180 -0
  25. package/bundled-skills/docs/guide/README.md +38 -0
  26. package/bundled-skills/docs/guide/images/design.jpg +0 -0
  27. package/bundled-skills/docs/guide/images/overnight.jpg +0 -0
  28. package/bundled-skills/docs/guide/images/recipes.jpg +0 -0
  29. package/bundled-skills/docs/guide/images/router.jpg +0 -0
  30. package/bundled-skills/docs/guide/images/understanding.jpg +0 -0
  31. package/bundled-skills/docs/guide/images/verification.jpg +0 -0
  32. package/bundled-skills/licenses/commander +22 -0
  33. package/bundled-skills/licenses/cursor-team-kit +21 -0
  34. package/bundled-skills/licenses/no-mistakes +21 -0
  35. package/bundled-skills/licenses/pstack +21 -0
  36. package/bundled-skills/skills/bend/SKILL.md +58 -0
  37. package/bundled-skills/skills/bend/bend_tool.py +144 -0
  38. package/bundled-skills/skills/peak/SKILL.md +184 -0
  39. package/bundled-skills/skills/peak/playbooks/authoring-a-skill.md +12 -0
  40. package/bundled-skills/skills/peak/playbooks/autonomous-run.md +13 -0
  41. package/bundled-skills/skills/peak/playbooks/autopilot-full.md +25 -0
  42. package/bundled-skills/skills/peak/playbooks/autopilot-stack.md +12 -0
  43. package/bundled-skills/skills/peak/playbooks/babysit.md +18 -0
  44. package/bundled-skills/skills/peak/playbooks/bug-fix.md +15 -0
  45. package/bundled-skills/skills/peak/playbooks/eval.md +25 -0
  46. package/bundled-skills/skills/peak/playbooks/feature.md +21 -0
  47. package/bundled-skills/skills/peak/playbooks/hillclimb.md +21 -0
  48. package/bundled-skills/skills/peak/playbooks/investigation.md +14 -0
  49. package/bundled-skills/skills/peak/playbooks/multi-phase-plan.md +155 -0
  50. package/bundled-skills/skills/peak/playbooks/opening-a-pr.md +34 -0
  51. package/bundled-skills/skills/peak/playbooks/orchestrate.md +112 -0
  52. package/bundled-skills/skills/peak/playbooks/pause-safely.md +10 -0
  53. package/bundled-skills/skills/peak/playbooks/perf-issue.md +25 -0
  54. package/bundled-skills/skills/peak/playbooks/prototype.md +14 -0
  55. package/bundled-skills/skills/peak/playbooks/refactoring.md +16 -0
  56. package/bundled-skills/skills/peak/playbooks/runtime-forensics.md +11 -0
  57. package/bundled-skills/skills/peak/playbooks/session-pickup.md +11 -0
  58. package/bundled-skills/skills/peak/playbooks/shipping.md +12 -0
  59. package/bundled-skills/skills/peak/playbooks/trace-forensics.md +14 -0
  60. package/bundled-skills/skills/peak/playbooks/visual-parity.md +11 -0
  61. package/bundled-skills/skills/peak/playbooks/worktree-cleanup.md +6 -0
  62. package/bundled-skills/skills/peak/references/architect/SKILL.md +81 -0
  63. package/bundled-skills/skills/peak/references/architect/references/design-red-flags.md +57 -0
  64. package/bundled-skills/skills/peak/references/architect/references/rationale-template.md +35 -0
  65. package/bundled-skills/skills/peak/references/architect/references/runner-prompt.md +20 -0
  66. package/bundled-skills/skills/peak/references/arena/SKILL.md +50 -0
  67. package/bundled-skills/skills/peak/references/automate-me/SKILL.md +106 -0
  68. package/bundled-skills/skills/peak/references/benchmark-checklist/SKILL.md +41 -0
  69. package/bundled-skills/skills/peak/references/blast-radius/SKILL.md +52 -0
  70. package/bundled-skills/skills/peak/references/bro/SKILL.md +9 -0
  71. package/bundled-skills/skills/peak/references/control-cli/SKILL.md +111 -0
  72. package/bundled-skills/skills/peak/references/control-ui/SKILL.md +111 -0
  73. package/bundled-skills/skills/peak/references/correct/SKILL.md +34 -0
  74. package/bundled-skills/skills/peak/references/create-formal-verification-skill/SKILL.md +43 -0
  75. package/bundled-skills/skills/peak/references/create-skill/SKILL.md +14 -0
  76. package/bundled-skills/skills/peak/references/create-verification-skill/SKILL.md +46 -0
  77. package/bundled-skills/skills/peak/references/create-verification-skill/references/feature-map-example/README.md +47 -0
  78. package/bundled-skills/skills/peak/references/create-verification-skill/references/feature-map-example/create-note.md +39 -0
  79. package/bundled-skills/skills/peak/references/create-verification-skill/references/feature-map-example/search.md +45 -0
  80. package/bundled-skills/skills/peak/references/deslop/SKILL.md +24 -0
  81. package/bundled-skills/skills/peak/references/figure-it-out/SKILL.md +54 -0
  82. package/bundled-skills/skills/peak/references/how/SKILL.md +38 -0
  83. package/bundled-skills/skills/peak/references/how/references/explainer-prompt.md +55 -0
  84. package/bundled-skills/skills/peak/references/how/references/explorer-prompt.md +35 -0
  85. package/bundled-skills/skills/peak/references/in-your-own-words/SKILL.md +9 -0
  86. package/bundled-skills/skills/peak/references/interrogate/SKILL.md +76 -0
  87. package/bundled-skills/skills/peak/references/interrogate/references/code-quality-review.md +47 -0
  88. package/bundled-skills/skills/peak/references/interrogate/references/lead-judgment.md +39 -0
  89. package/bundled-skills/skills/peak/references/interrogate/references/reviewer-prompt.md +62 -0
  90. package/bundled-skills/skills/peak/references/interrogate/references/rubric.md +77 -0
  91. package/bundled-skills/skills/peak/references/ketchup/SKILL.md +45 -0
  92. package/bundled-skills/skills/peak/references/maintain-formal-verification-skill/SKILL.md +60 -0
  93. package/bundled-skills/skills/peak/references/maintain-verification-skill/SKILL.md +41 -0
  94. package/bundled-skills/skills/peak/references/no-comments/SKILL.md +26 -0
  95. package/bundled-skills/skills/peak/references/no-mistakes/SKILL.md +20 -0
  96. package/bundled-skills/skills/peak/references/no-mistakes/upstream/SKILL.md +394 -0
  97. package/bundled-skills/skills/peak/references/peak-setup/SKILL.md +18 -0
  98. package/bundled-skills/skills/peak/references/poteto-help/SKILL.md +16 -0
  99. package/bundled-skills/skills/peak/references/poteto-help/references/prompting.md +51 -0
  100. package/bundled-skills/skills/peak/references/poteto-help/references/recipes.md +51 -0
  101. package/bundled-skills/skills/peak/references/principle-attack-the-premise/SKILL.md +23 -0
  102. package/bundled-skills/skills/peak/references/principle-boundary-discipline/SKILL.md +36 -0
  103. package/bundled-skills/skills/peak/references/principle-build-the-lever/SKILL.md +25 -0
  104. package/bundled-skills/skills/peak/references/principle-encode-lessons-in-structure/SKILL.md +13 -0
  105. package/bundled-skills/skills/peak/references/principle-exhaust-the-design-space/SKILL.md +11 -0
  106. package/bundled-skills/skills/peak/references/principle-experience-first/SKILL.md +21 -0
  107. package/bundled-skills/skills/peak/references/principle-explain-the-number/SKILL.md +19 -0
  108. package/bundled-skills/skills/peak/references/principle-fix-root-causes/SKILL.md +23 -0
  109. package/bundled-skills/skills/peak/references/principle-foundational-thinking/SKILL.md +23 -0
  110. package/bundled-skills/skills/peak/references/principle-guard-the-context-window/SKILL.md +14 -0
  111. package/bundled-skills/skills/peak/references/principle-laziness-protocol/SKILL.md +12 -0
  112. package/bundled-skills/skills/peak/references/principle-make-operations-idempotent/SKILL.md +24 -0
  113. package/bundled-skills/skills/peak/references/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +16 -0
  114. package/bundled-skills/skills/peak/references/principle-minimize-reader-load/SKILL.md +20 -0
  115. package/bundled-skills/skills/peak/references/principle-model-the-domain/SKILL.md +26 -0
  116. package/bundled-skills/skills/peak/references/principle-never-block-on-the-human/SKILL.md +17 -0
  117. package/bundled-skills/skills/peak/references/principle-outcome-oriented-execution/SKILL.md +23 -0
  118. package/bundled-skills/skills/peak/references/principle-prove-it-works/SKILL.md +24 -0
  119. package/bundled-skills/skills/peak/references/principle-redesign-from-first-principles/SKILL.md +18 -0
  120. package/bundled-skills/skills/peak/references/principle-separate-before-serializing-shared-state/SKILL.md +18 -0
  121. package/bundled-skills/skills/peak/references/principle-sequence-verifiable-units/SKILL.md +19 -0
  122. package/bundled-skills/skills/peak/references/principle-subtract-before-you-add/SKILL.md +20 -0
  123. package/bundled-skills/skills/peak/references/principle-test-behavior-not-implementation/SKILL.md +22 -0
  124. package/bundled-skills/skills/peak/references/principle-type-system-discipline/SKILL.md +15 -0
  125. package/bundled-skills/skills/peak/references/raga-runtime/SKILL.md +51 -0
  126. package/bundled-skills/skills/peak/references/raga-runtime/managed-tools.mjs +34 -0
  127. package/bundled-skills/skills/peak/references/raga-runtime/raga_runtime.py +55 -0
  128. package/bundled-skills/skills/peak/references/recall/SKILL.md +37 -0
  129. package/bundled-skills/skills/peak/references/reflect/SKILL.md +63 -0
  130. package/bundled-skills/skills/peak/references/reflect/references/divergent-reviewer.md +38 -0
  131. package/bundled-skills/skills/peak/references/reflect/references/judgment-reviewer.md +37 -0
  132. package/bundled-skills/skills/peak/references/reflect/references/synthesizer.md +56 -0
  133. package/bundled-skills/skills/peak/references/reflect/references/tooling-reviewer.md +48 -0
  134. package/bundled-skills/skills/peak/references/reviewer-triage.md +142 -0
  135. package/bundled-skills/skills/peak/references/sharpen/SKILL.md +18 -0
  136. package/bundled-skills/skills/peak/references/show-me-your-work/SKILL.md +84 -0
  137. package/bundled-skills/skills/peak/references/show-me-your-work/references/decision-log-template.tsv +1 -0
  138. package/bundled-skills/skills/peak/references/show-me-your-work/scripts/log.sh +42 -0
  139. package/bundled-skills/skills/peak/references/statechart/SKILL.md +38 -0
  140. package/bundled-skills/skills/peak/references/statechart/scripts/chart-to-tla.mjs +236 -0
  141. package/bundled-skills/skills/peak/references/statechart/scripts/compare-charts.mjs +104 -0
  142. package/bundled-skills/skills/peak/references/statechart/scripts/lib.mjs +725 -0
  143. package/bundled-skills/skills/peak/references/statechart/scripts/model/OutputFolder.cfg +6 -0
  144. package/bundled-skills/skills/peak/references/statechart/scripts/model/OutputFolder.map.mjs +106 -0
  145. package/bundled-skills/skills/peak/references/statechart/scripts/model/OutputFolder.tla +176 -0
  146. package/bundled-skills/skills/peak/references/statechart/scripts/statechart.mjs +749 -0
  147. package/bundled-skills/skills/peak/references/statechart/scripts/test/statechart.test.mjs +2790 -0
  148. package/bundled-skills/skills/peak/references/statechart/scripts/test/sync.mmd +19 -0
  149. package/bundled-skills/skills/peak/references/statechart/scripts/test/upload.mmd +16 -0
  150. package/bundled-skills/skills/peak/references/statechart/scripts/tlc-to-chart.mjs +1347 -0
  151. package/bundled-skills/skills/peak/references/swarm/SKILL.md +43 -0
  152. package/bundled-skills/skills/peak/references/tdd/SKILL.md +44 -0
  153. package/bundled-skills/skills/peak/references/teach/SKILL.md +23 -0
  154. package/bundled-skills/skills/peak/references/technical-writing/SKILL.md +108 -0
  155. package/bundled-skills/skills/peak/references/typescript-best-practices/SKILL.md +33 -0
  156. package/bundled-skills/skills/peak/references/typescript-best-practices/references/patterns.md +328 -0
  157. package/bundled-skills/skills/peak/references/unslop/SKILL.md +64 -0
  158. package/bundled-skills/skills/peak/references/verify-this/SKILL.md +76 -0
  159. package/bundled-skills/skills/peak/references/why/SKILL.md +100 -0
  160. package/bundled-skills/skills/peak/references/why/references/epistemics.md +71 -0
  161. package/bundled-skills/skills/peak/references/why/references/investigator-prompt.md +84 -0
  162. package/bundled-skills/skills/peak/references/why/references/source-playbook.md +17 -0
  163. package/bundled-skills/skills/peak/references/why/references/sources/code-archaeology.md +51 -0
  164. package/bundled-skills/skills/peak/references/why/references/sources/databricks.md +70 -0
  165. package/bundled-skills/skills/peak/references/why/references/sources/datadog.md +97 -0
  166. package/bundled-skills/skills/peak/references/why/references/sources/incident-postmortem.md +5 -0
  167. package/bundled-skills/skills/peak/references/why/references/sources/linear.md +48 -0
  168. package/bundled-skills/skills/peak/references/why/references/sources/notion.md +55 -0
  169. package/bundled-skills/skills/peak/references/why/references/sources/sentry.md +66 -0
  170. package/bundled-skills/skills/peak/references/why/references/sources/slack.md +17 -0
  171. package/bundled-skills/skills/peak/references/why/references/synthesizer-prompt.md +129 -0
  172. package/bundled-skills/skills/peak/scripts/check-plan.mjs +205 -0
  173. package/bundled-skills/skills/peak/scripts/check-plan.test.ts +96 -0
  174. package/bundled-skills/skills/peak/scripts/orch/orch.test.ts +761 -0
  175. package/bundled-skills/skills/peak/scripts/orch/orch.ts +588 -0
  176. package/bundled-skills/skills/peak/scripts/orch/store.ts +1676 -0
  177. package/bundled-skills/skills/peak/scripts/vendor/commander/LICENSE +22 -0
  178. package/bundled-skills/skills/peak/scripts/vendor/commander/esm.mjs +16 -0
  179. package/bundled-skills/skills/peak/scripts/vendor/commander/index.js +24 -0
  180. package/bundled-skills/skills/peak/scripts/vendor/commander/lib/argument.js +149 -0
  181. package/bundled-skills/skills/peak/scripts/vendor/commander/lib/command.js +2778 -0
  182. package/bundled-skills/skills/peak/scripts/vendor/commander/lib/error.js +39 -0
  183. package/bundled-skills/skills/peak/scripts/vendor/commander/lib/help.js +747 -0
  184. package/bundled-skills/skills/peak/scripts/vendor/commander/lib/option.js +379 -0
  185. package/bundled-skills/skills/peak/scripts/vendor/commander/lib/suggestSimilar.js +101 -0
  186. package/bundled-skills/skills/peak/scripts/vendor/commander/package.json +1 -0
  187. package/bundled-skills/skills/peak/scripts/vendor/commander/typings/esm.d.mts +3 -0
  188. package/bundled-skills/skills/peak/scripts/vendor/commander/typings/index.d.ts +1113 -0
  189. package/bundled-skills/skills/peak/scripts/watch-pr/cli.test.ts +224 -0
  190. package/bundled-skills/skills/peak/scripts/watch-pr/cli.ts +223 -0
  191. package/bundled-skills/skills/peak/scripts/watch-pr/fakes.test-helper.ts +129 -0
  192. package/bundled-skills/skills/peak/scripts/watch-pr/github.test.ts +436 -0
  193. package/bundled-skills/skills/peak/scripts/watch-pr/github.ts +718 -0
  194. package/bundled-skills/skills/peak/scripts/watch-pr/policy.test.ts +420 -0
  195. package/bundled-skills/skills/peak/scripts/watch-pr/policy.ts +836 -0
  196. package/bundled-skills/skills/peak/scripts/watch-pr/render.ts +169 -0
  197. package/bundled-skills/skills/peak/scripts/watch-pr/tsconfig.json +13 -0
  198. package/bundled-skills/skills/peak/scripts/watch-pr/types.compile.ts +93 -0
  199. package/bundled-skills/skills/peak/scripts/watch-pr/types.ts +413 -0
  200. package/bundled-skills/skills/peak/scripts/watch-pr/watch-pr +4 -0
  201. package/bundled-skills/skills/peak/scripts/worktree-audit.sh +4 -0
  202. package/bundled-skills/skills/raga-artifacts/SKILL.md +117 -0
  203. package/bundled-skills/skills/raga-artifacts/artifact.mjs +254 -0
  204. package/bundled-skills/skills/raga-artifacts/artifact_tool.py +55 -0
  205. package/bundled-skills/skills/raga-artifacts/diagrams.md +641 -0
  206. package/bundled-skills/skills/raga-artifacts/templates/doc.html +124 -0
  207. package/bundled-skills/skills/raga-artifacts/templates/slides.html +138 -0
  208. package/dist/bundle/chunks/anthropic-messages-BMDYDGHW.js +6 -0
  209. package/dist/bundle/chunks/anthropic.js +68 -0
  210. package/dist/bundle/chunks/azure-openai-responses-Q7EZTEHK.js +2 -0
  211. package/dist/bundle/chunks/bedrock-converse-stream.js +86 -0
  212. package/dist/bundle/chunks/chunk-3MNXTBQ3.js +2 -0
  213. package/dist/bundle/chunks/chunk-3V45WARH.js +7 -0
  214. package/dist/bundle/chunks/chunk-5AGIVGSV.js +6 -0
  215. package/dist/bundle/chunks/chunk-6QGV3IWD.js +6 -0
  216. package/dist/bundle/chunks/chunk-7CBJINYJ.js +20 -0
  217. package/dist/bundle/chunks/chunk-7DDKXS2Q.js +2 -0
  218. package/dist/bundle/chunks/chunk-7ZC4UXSL.js +2 -0
  219. package/dist/bundle/chunks/chunk-CF5GWZHW.js +31 -0
  220. package/dist/bundle/chunks/chunk-ENUUAC3O.js +131 -0
  221. package/dist/bundle/chunks/chunk-F7LFZTHT.js +1591 -0
  222. package/dist/bundle/chunks/chunk-G54UBZTF.js +44 -0
  223. package/dist/bundle/chunks/chunk-H76KZMOI.js +2 -0
  224. package/dist/bundle/chunks/chunk-IXDQLPKS.js +11 -0
  225. package/dist/bundle/chunks/chunk-JLV25LHE.js +2 -0
  226. package/dist/bundle/chunks/chunk-JQDZ4Z7L.js +2 -0
  227. package/dist/bundle/chunks/chunk-JXWJOIH4.js +2 -0
  228. package/dist/bundle/chunks/chunk-KNY3PXQI.js +2 -0
  229. package/dist/bundle/chunks/chunk-L2ZPNLYD.js +2 -0
  230. package/dist/bundle/chunks/chunk-LPXNMA2W.js +2 -0
  231. package/dist/bundle/chunks/chunk-LVVZS2NO.js +12 -0
  232. package/dist/bundle/chunks/chunk-LX24DVQ5.js +2 -0
  233. package/dist/bundle/chunks/chunk-NP6TH6UT.js +64 -0
  234. package/dist/bundle/chunks/chunk-OPPYO2IE.js +2 -0
  235. package/dist/bundle/chunks/chunk-P2QCWBFM.js +9 -0
  236. package/dist/bundle/chunks/chunk-P7FPUEYA.js +2 -0
  237. package/dist/bundle/chunks/chunk-PGXDEOVV.js +2 -0
  238. package/dist/bundle/chunks/chunk-PJACZCIR.js +2 -0
  239. package/dist/bundle/chunks/chunk-PMRZO46Y.js +2 -0
  240. package/dist/bundle/chunks/chunk-PRRNXB7S.js +2 -0
  241. package/dist/bundle/chunks/chunk-Q4U25BSY.js +2 -0
  242. package/dist/bundle/chunks/chunk-RDZVAGPR.js +2 -0
  243. package/dist/bundle/chunks/chunk-RFPFZTK2.js +7 -0
  244. package/dist/bundle/chunks/chunk-RP2WKNT7.js +4 -0
  245. package/dist/bundle/chunks/chunk-S5J3UB5O.js +75 -0
  246. package/dist/bundle/chunks/chunk-TEPMHNKQ.js +2 -0
  247. package/dist/bundle/chunks/chunk-TMTOQ7H7.js +2 -0
  248. package/dist/bundle/chunks/chunk-UKVNB6C4.js +2 -0
  249. package/dist/bundle/chunks/chunk-USJZMAIR.js +2 -0
  250. package/dist/bundle/chunks/chunk-UTB4DTYQ.js +2 -0
  251. package/dist/bundle/chunks/chunk-WJGLPWUE.js +16 -0
  252. package/dist/bundle/chunks/cli-QLDCDYOR.js +51 -0
  253. package/dist/bundle/chunks/cloudflare-workers-ai-system-one-S6CT2FXS.js +2 -0
  254. package/dist/bundle/chunks/codemode-worker.js +478 -0
  255. package/dist/bundle/chunks/easter-egg-3d-NPDBBPPF.js +2 -0
  256. package/dist/bundle/chunks/execute-WPTMS4XG.js +505 -0
  257. package/dist/bundle/chunks/github-copilot.js +2 -0
  258. package/dist/bundle/chunks/google-generative-ai-35NZKG6S.js +2 -0
  259. package/dist/bundle/chunks/google-vertex-P33AHYYX.js +2 -0
  260. package/dist/bundle/chunks/https-proxy-agent-LVOO5TPL.js +13 -0
  261. package/dist/bundle/chunks/image-resize-worker.js +2 -0
  262. package/dist/bundle/chunks/jiti-loader-GL4HJFAD.js +21 -0
  263. package/dist/bundle/chunks/jiti-static-loader-2RCDI6B4.js +2 -0
  264. package/dist/bundle/chunks/kimi-coding.js +2 -0
  265. package/dist/bundle/chunks/lib-4AVRLBHM.js +9 -0
  266. package/dist/bundle/chunks/llama-cpp-classify-U4EH6TAM.js +22 -0
  267. package/dist/bundle/chunks/meta.js +2 -0
  268. package/dist/bundle/chunks/mistral-conversations-J62DPS2P.js +5 -0
  269. package/dist/bundle/chunks/multipart-parser-FMGQ7342.js +3 -0
  270. package/dist/bundle/chunks/node-AUOGOBCN.js +2 -0
  271. package/dist/bundle/chunks/node-KCMANYC2.js +16 -0
  272. package/dist/bundle/chunks/openai-chatgpt.js +68 -0
  273. package/dist/bundle/chunks/openai-codex-responses-4HXATL2M.js +10 -0
  274. package/dist/bundle/chunks/openai-codex.js +68 -0
  275. package/dist/bundle/chunks/openai-completions-75YYY35N.js +7 -0
  276. package/dist/bundle/chunks/openai-decisions-HEBEVGA2.js +5 -0
  277. package/dist/bundle/chunks/openai-responses-UWNAKTBV.js +4 -0
  278. package/dist/bundle/chunks/openrouter-images-HVOPXKHS.js +2 -0
  279. package/dist/bundle/chunks/openrouter.js +68 -0
  280. package/dist/bundle/chunks/pi-messages-DG7CALOV.js +8 -0
  281. package/dist/bundle/chunks/radius.js +68 -0
  282. package/dist/bundle/chunks/runtime-N5MUH2WA.js +2 -0
  283. package/dist/bundle/chunks/src-QKGKDFSU.js +4 -0
  284. package/dist/bundle/chunks/typesafe-system-one-24HEGHNN.js +2 -0
  285. package/dist/bundle/chunks/virtual-modules-4I6Y6DPQ.js +2 -0
  286. package/dist/bundle/chunks/xai.js +2 -0
  287. package/dist/bundle/cli-runtime.js +3 -0
  288. package/dist/bundle/cli.js +5 -0
  289. package/dist/bundle/index.js +2 -0
  290. package/dist/bundle/raga-runtime.js +3 -0
  291. package/dist/bundle/raga.js +5 -0
  292. package/dist/bundle/rpc-entry.js +3 -0
  293. package/dist/core/export-html/template.css +1085 -0
  294. package/dist/core/export-html/template.html +56 -0
  295. package/dist/core/export-html/template.js +2044 -0
  296. package/dist/core/export-html/vendor/highlight.min.js +1213 -0
  297. package/dist/core/export-html/vendor/marked-cjk-friendly.min.js +6 -0
  298. package/dist/core/export-html/vendor/marked.min.js +79 -0
  299. package/dist/modes/interactive/assets/clankolas.png +0 -0
  300. package/dist/modes/interactive/theme/dark.json +78 -0
  301. package/dist/modes/interactive/theme/light.json +78 -0
  302. package/docs/cli-integration.md +106 -0
  303. package/docs/cli.md +343 -0
  304. package/docs/codemode.md +230 -0
  305. package/docs/compaction.md +489 -0
  306. package/docs/configuration.md +62 -0
  307. package/docs/containerization.md +183 -0
  308. package/docs/custom-provider.md +227 -0
  309. package/docs/docs.json +220 -0
  310. package/docs/environment-variables.md +101 -0
  311. package/docs/extensions.md +400 -0
  312. package/docs/how-pi-works.md +49 -0
  313. package/docs/images/doom-extension.png +0 -0
  314. package/docs/images/exy.png +0 -0
  315. package/docs/images/interactive-mode.png +0 -0
  316. package/docs/images/tree-view.png +0 -0
  317. package/docs/index.md +39 -0
  318. package/docs/json.md +226 -0
  319. package/docs/keybindings.md +195 -0
  320. package/docs/llama-cpp.md +121 -0
  321. package/docs/mcp.md +272 -0
  322. package/docs/message-types.md +279 -0
  323. package/docs/models-configuration.md +196 -0
  324. package/docs/models.md +225 -0
  325. package/docs/nvidia-rtx-llama-cpp.md +96 -0
  326. package/docs/packages.md +129 -0
  327. package/docs/prompt-templates.md +59 -0
  328. package/docs/providers.md +247 -0
  329. package/docs/quickstart.md +136 -0
  330. package/docs/rpc-commands.md +860 -0
  331. package/docs/rpc-extension-ui.md +200 -0
  332. package/docs/rpc.md +193 -0
  333. package/docs/sdk.md +149 -0
  334. package/docs/security.md +99 -0
  335. package/docs/session-format.md +293 -0
  336. package/docs/sessions.md +66 -0
  337. package/docs/settings.md +185 -0
  338. package/docs/shell-aliases.md +93 -0
  339. package/docs/skills.md +93 -0
  340. package/docs/slash-commands.md +61 -0
  341. package/docs/terminal-setup.md +233 -0
  342. package/docs/termux.md +117 -0
  343. package/docs/themes.md +138 -0
  344. package/docs/tmux.md +55 -0
  345. package/docs/tui.md +195 -0
  346. package/docs/usage.md +94 -0
  347. package/docs/virtual-models.md +114 -0
  348. package/docs/windows.md +81 -0
  349. package/examples/README.md +36 -0
  350. package/examples/extensions/README.md +214 -0
  351. package/examples/extensions/auto-commit-on-exit.ts +49 -0
  352. package/examples/extensions/bash-spawn-hook.ts +30 -0
  353. package/examples/extensions/bookmark.ts +50 -0
  354. package/examples/extensions/border-status-editor.ts +150 -0
  355. package/examples/extensions/built-in-tool-renderer.ts +225 -0
  356. package/examples/extensions/claude-rules.ts +86 -0
  357. package/examples/extensions/commands.ts +72 -0
  358. package/examples/extensions/confirm-destructive.ts +59 -0
  359. package/examples/extensions/custom-compaction.ts +117 -0
  360. package/examples/extensions/custom-footer.ts +64 -0
  361. package/examples/extensions/custom-header.ts +73 -0
  362. package/examples/extensions/custom-provider-anthropic/index.ts +617 -0
  363. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  364. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  365. package/examples/extensions/custom-provider-gitlab-duo/index.ts +405 -0
  366. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  367. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  368. package/examples/extensions/debug-provider.ts +90 -0
  369. package/examples/extensions/dirty-repo-guard.ts +56 -0
  370. package/examples/extensions/doom-overlay/README.md +46 -0
  371. package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  372. package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  373. package/examples/extensions/doom-overlay/doom/build.sh +152 -0
  374. package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  375. package/examples/extensions/doom-overlay/doom-component.ts +132 -0
  376. package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  377. package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  378. package/examples/extensions/doom-overlay/index.ts +74 -0
  379. package/examples/extensions/doom-overlay/wad-finder.ts +55 -0
  380. package/examples/extensions/dynamic-resources/SKILL.md +8 -0
  381. package/examples/extensions/dynamic-resources/dynamic.json +81 -0
  382. package/examples/extensions/dynamic-resources/dynamic.md +5 -0
  383. package/examples/extensions/dynamic-resources/index.ts +15 -0
  384. package/examples/extensions/dynamic-tools.ts +74 -0
  385. package/examples/extensions/entry-renderer.ts +41 -0
  386. package/examples/extensions/event-bus.ts +43 -0
  387. package/examples/extensions/file-trigger.ts +41 -0
  388. package/examples/extensions/git-checkpoint.ts +53 -0
  389. package/examples/extensions/git-merge-and-resolve.ts +115 -0
  390. package/examples/extensions/github-issue-autocomplete.ts +185 -0
  391. package/examples/extensions/gondolin/index.ts +531 -0
  392. package/examples/extensions/gondolin/package-lock.json +175 -0
  393. package/examples/extensions/gondolin/package.json +19 -0
  394. package/examples/extensions/handoff.ts +190 -0
  395. package/examples/extensions/hello.ts +26 -0
  396. package/examples/extensions/hidden-thinking-label.ts +53 -0
  397. package/examples/extensions/inline-bash.ts +94 -0
  398. package/examples/extensions/input-transform-streaming.ts +39 -0
  399. package/examples/extensions/input-transform.ts +43 -0
  400. package/examples/extensions/interactive-shell.ts +196 -0
  401. package/examples/extensions/jev-router.ts +113 -0
  402. package/examples/extensions/mac-system-theme.ts +47 -0
  403. package/examples/extensions/message-renderer.ts +59 -0
  404. package/examples/extensions/minimal-mode.ts +353 -0
  405. package/examples/extensions/modal-editor.ts +85 -0
  406. package/examples/extensions/model-status.ts +31 -0
  407. package/examples/extensions/notify.ts +57 -0
  408. package/examples/extensions/overlay-qa-tests.ts +1450 -0
  409. package/examples/extensions/overlay-test.ts +153 -0
  410. package/examples/extensions/permission-gate.ts +34 -0
  411. package/examples/extensions/pirate.ts +47 -0
  412. package/examples/extensions/plan-mode/README.md +66 -0
  413. package/examples/extensions/plan-mode/index.ts +394 -0
  414. package/examples/extensions/plan-mode/utils.ts +168 -0
  415. package/examples/extensions/preset.ts +436 -0
  416. package/examples/extensions/project-trust.ts +64 -0
  417. package/examples/extensions/prompt-customizer.ts +49 -0
  418. package/examples/extensions/protected-paths.ts +30 -0
  419. package/examples/extensions/provider-payload.ts +18 -0
  420. package/examples/extensions/qna.ts +118 -0
  421. package/examples/extensions/question.ts +286 -0
  422. package/examples/extensions/questionnaire.ts +448 -0
  423. package/examples/extensions/rainbow-editor.ts +88 -0
  424. package/examples/extensions/reload-runtime.ts +37 -0
  425. package/examples/extensions/rpc-demo.ts +118 -0
  426. package/examples/extensions/sandbox/index.ts +326 -0
  427. package/examples/extensions/sandbox/package-lock.json +92 -0
  428. package/examples/extensions/sandbox/package.json +19 -0
  429. package/examples/extensions/send-user-message.ts +97 -0
  430. package/examples/extensions/session-name.ts +27 -0
  431. package/examples/extensions/shutdown-command.ts +63 -0
  432. package/examples/extensions/snake.ts +343 -0
  433. package/examples/extensions/space-invaders.ts +560 -0
  434. package/examples/extensions/ssh.ts +220 -0
  435. package/examples/extensions/status-line.ts +32 -0
  436. package/examples/extensions/structured-output.ts +65 -0
  437. package/examples/extensions/subagent/README.md +177 -0
  438. package/examples/extensions/subagent/agents/planner.md +37 -0
  439. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  440. package/examples/extensions/subagent/agents/scout.md +50 -0
  441. package/examples/extensions/subagent/agents/worker.md +24 -0
  442. package/examples/extensions/subagent/agents.ts +157 -0
  443. package/examples/extensions/subagent/index.ts +1038 -0
  444. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  445. package/examples/extensions/subagent/prompts/implement.md +10 -0
  446. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  447. package/examples/extensions/summarize.ts +199 -0
  448. package/examples/extensions/system-prompt-header.ts +17 -0
  449. package/examples/extensions/tic-tac-toe.ts +1008 -0
  450. package/examples/extensions/timed-confirm.ts +70 -0
  451. package/examples/extensions/titlebar-spinner.ts +58 -0
  452. package/examples/extensions/todo.ts +297 -0
  453. package/examples/extensions/tool-override.ts +144 -0
  454. package/examples/extensions/tools.ts +146 -0
  455. package/examples/extensions/trigger-compact.ts +50 -0
  456. package/examples/extensions/truncated-tool.ts +195 -0
  457. package/examples/extensions/widget-placement.ts +9 -0
  458. package/examples/extensions/with-deps/index.ts +32 -0
  459. package/examples/extensions/with-deps/package-lock.json +31 -0
  460. package/examples/extensions/with-deps/package.json +22 -0
  461. package/examples/extensions/working-indicator.ts +123 -0
  462. package/examples/extensions/working-message-test.ts +25 -0
  463. package/examples/rpc-client.ts +35 -0
  464. package/examples/rpc-extension-ui.ts +661 -0
  465. package/examples/sdk/01-minimal.ts +26 -0
  466. package/examples/sdk/02-custom-model.ts +49 -0
  467. package/examples/sdk/03-custom-prompt.ts +75 -0
  468. package/examples/sdk/04-skills.ts +55 -0
  469. package/examples/sdk/05-tools.ts +48 -0
  470. package/examples/sdk/06-extensions.ts +99 -0
  471. package/examples/sdk/07-context-files.ts +47 -0
  472. package/examples/sdk/08-prompt-templates.ts +51 -0
  473. package/examples/sdk/09-api-keys-and-oauth.ts +34 -0
  474. package/examples/sdk/10-settings.ts +53 -0
  475. package/examples/sdk/11-sessions.ts +52 -0
  476. package/examples/sdk/12-full-control.ts +74 -0
  477. package/examples/sdk/13-session-runtime.ts +67 -0
  478. package/examples/sdk/14-codemode-mcp.ts +61 -0
  479. package/examples/sdk/README.md +141 -0
  480. package/package.json +21 -5
  481. package/python/raga_rlm/__init__.py +352 -0
  482. package/python/raga_rlm/mlx_models.py +265 -0
  483. package/python/raga_rlm/repl.py +920 -0
  484. package/schemas/keybindings.schema.json +441 -0
  485. package/schemas/models.schema.json +1211 -0
  486. package/schemas/settings.schema.json +829 -0
  487. package/schemas/theme.schema.json +365 -0
@@ -0,0 +1,1347 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import process from "node:process";
5
+ import { pathToFileURL } from "node:url";
6
+ import {
7
+ FULL_LABELS,
8
+ GROUP_PALETTE,
9
+ LEGEND_ID,
10
+ PLAIN_FILL,
11
+ checkTlcOutput,
12
+ compareCharts,
13
+ groupClassDef,
14
+ groupColorProblem,
15
+ isRecord,
16
+ joinLabels,
17
+ parseDot,
18
+ parseMermaid,
19
+ parseTraces,
20
+ productOf,
21
+ quote,
22
+ ruleSentences,
23
+ stateKey,
24
+ wrap,
25
+ STATUS_CLASSES,
26
+ } from "./lib.mjs";
27
+
28
+ const USAGE = "Usage: node tlc-to-chart.mjs --dot FILE --map FILE [options]";
29
+ const HELP = `${USAGE}
30
+
31
+ Collapses a TLC state graph onto the reader's states and writes a Mermaid stateDiagram-v2.
32
+ statechart.mjs runs it once for the overview and once for each bug chart.
33
+
34
+ --dot FILE The state graph from TLC -dump dot,actionlabels FILE, for the current code.
35
+ Required.
36
+ --map FILE The mapping module. Required. It exports state(vars, facts), and
37
+ optionally detail(vars, facts), step(action, before, after),
38
+ guard(action, before, after), timer(action, before, after),
39
+ cause(vars, facts, invariant), group(vars, facts), groupColors,
40
+ regionNames, readerActions, and legend. guard is when the step
41
+ can happen, in plain words. An edge reads "step [words]" when every
42
+ model step behind it has words. timer is how many seconds the code waits
43
+ before a timer step, and the step reads "after N s, step".
44
+ cause is the hidden reason a bad state is bad: counterexamples with the
45
+ same invariant and cause are one bug. When cause returns nothing, the
46
+ end state stands in for it. readerActions are the actions the
47
+ reader takes. group is the group of a state, below. legend, such as
48
+ { works: "Works on main" }, gives the legend's words for the works,
49
+ wrong, and fixed statuses.
50
+ facts.enabled is the set of actions the state enables. facts.settled is
51
+ true when only reader actions are enabled, so nothing else happens until
52
+ the reader acts.
53
+ --traces FILE TLC output that holds counterexamples. Repeat it. TLC -continue gives one
54
+ per bad state. It refuses a file from a TLC run that stopped early or hit
55
+ an error other than a broken rule.
56
+ --fixed-dot FILE The state graph of the model with the open fix. Each bug's steps replay
57
+ on it.
58
+ --fixed-traces FILE TLC output for the same invariants on the fixed model. Repeat it. It marks
59
+ states fixed or still wrong, and adds the bugs that only the fixed model
60
+ has after the current model's bugs.
61
+ --against-dot FILE The state graph of the spec that the current code must follow.
62
+ Each bug's steps replay on it. Do not use it with --fixed-dot.
63
+ --against-traces FILE TLC output for the spec. Repeat it. Its bugs are drawn wrong, and the
64
+ bugs that only the spec has number after the code's bugs.
65
+ --label TEXT The name of the second model in the chart and the stories. Default:
66
+ "the fix" with --fixed-dot, "the spec" with --against-dot.
67
+ --spec FILE A TLA+ file whose \\* comment lines above a rule are the rule's sentence in
68
+ the stories. Repeat it. The first file that has a sentence wins.
69
+ --view NAME customer (state labels, the default), developer (detail labels), or
70
+ layered (detail inside state).
71
+ --cover Keep only the shortest path to each settled reader state, plus the
72
+ counterexample paths.
73
+ --highlight N Draw bug N as a numbered path. Default 1. 0 draws none.
74
+ --path-only Draw only the highlighted path.
75
+ --full-labels Write each label on an edge in full, joined by " or ", and mark the chart
76
+ "${FULL_LABELS}". Default: write the leading words that the labels
77
+ share once, as in "you tap Pay or Back".
78
+ --flat Draw every state on its own, with no boxes.
79
+ --regions When every state reads "context: progress", draw two parallel regions,
80
+ context and progress, when they are smaller and proved. They draw the
81
+ steps of the shortest way to each region state, of each bug path, and
82
+ the nearest step with each label.
83
+ Otherwise draw the chart the other flags ask for, and print why.
84
+ Customer view, --highlight 0, no --flat or --path-only.
85
+ --proof FILE With --regions, write the flat chart that the proof compares with.
86
+ --regions-all FILE With --regions, also write the regions with every step.
87
+ --title TEXT The chart title.
88
+ --out FILE The Mermaid chart. Default: stdout.
89
+ --story FILE The reader stories, one per bug.
90
+ --summary FILE The counts for the chart and the bugs, as JSON.
91
+ -h, --help Print this help, and exit 0.
92
+
93
+ Each bug replays on the second model by its screens and step words, which are what the reader
94
+ sees. The replay takes the second model's steps that keep the screen and that the reader does not
95
+ take. A bug step that the reader does not see must keep the screen on the second model too, unless
96
+ the second model cannot take it. The replay stops at the first step that the second model shows
97
+ the reader differently. There the chart draws "with LABEL, STEP" (or "in LABEL, STEP" for
98
+ --against-dot) to where the second model goes, or a loop "... STEP cannot happen" when it cannot
99
+ take a step that the reader sees.
100
+
101
+ The second model may have other variables and actions than the current model. Variables and
102
+ record fields that one start state has and the other does not are left out when the two models
103
+ are compared. The second model's states take the current model's start values for the variables
104
+ it does not have, so one mapping draws both. A step of an action that the second model does not
105
+ have is skipped when it changes no shared variable, and matches any step to the same shared state
106
+ when it does.
107
+
108
+ In the customer view, states whose labels read "context: progress" with the same context sit in
109
+ one box named context, and each shows only its progress. A context with one state stays a plain
110
+ state. An arrow from a box's border stands for the same step from every state in the box: it is
111
+ drawn only when every state in the box takes that step, with the same labels, to the same state.
112
+ The layered view puts each detail state in a box named by its state label.
113
+
114
+ With --regions, each "context: progress" label is one state in the context region and one in the
115
+ progress region. A step that happens beside only some states of the other region gets a guard:
116
+ "[when A or B]" or "[unless A or B]", whichever is shorter, and a "%% when [not] ids" line under it.
117
+ The mapping's guard words, when the step has them, replace "when A or B" in the label.
118
+ The proof combines the regions with every step as compare-charts.mjs does, and needs exactly the
119
+ flat chart's states, start states, and labeled steps. Each state's color must be the worse color
120
+ of its two region states. Then the drawn arrows, combined, must make only steps of the flat chart:
121
+ a step that they make wrongly gets back every arrow that the full regions take there.
122
+ compare-charts.mjs on the --regions-all and --proof files repeats the first proof.
123
+ mapping.regionNames = [context, progress] names the two regions.
124
+
125
+ Outside regions, two states have at most one arrow. Its lines are the numbered steps, then the
126
+ current model's steps, then the second model's steps, with a blank line between each group.
127
+
128
+ Without group(), the legend shows only the statuses the chart uses. A chart with a second model
129
+ has the line "%% second model (fix|against): LABEL" right after stateDiagram-v2. Each chart
130
+ starts with front matter that keeps Mermaid from shrinking it to the page or rewrapping its labels.
131
+
132
+ Groups: group(vars, facts) returns the name of the group that a model state is in, such as
133
+ "Toast shown", or nothing. A chart state is in a group when all its model states are in it. A
134
+ region state is in a group when every state that it is part of is in it. Each group fills its
135
+ states with one color, and the legend lists only the groups that the chart draws: first the
136
+ groups in groupColors, in that order, then the others from A to Z. groupColors, such as
137
+ { "Toast shown": "#d6eaf8" }, gives a group a light "#rgb" or "#rrggbb" color. A group with no
138
+ color, or null, takes the next free palette color: light blue, orange, purple, yellow, teal, tan,
139
+ then pink. A state in no group, or in more than one, is gray. summary.json counts the drawn
140
+ states of each group in chartGroups, and lists the chart states with more than one in mixedGroups.
141
+ TLC decides the status, and a group cannot change it. On top of the group color, a state that
142
+ breaks a rule has a thick red border, and a fixed state or a state only in the second model a
143
+ thick green border. The legend then shows that border. A thick blue border marks the bug path on
144
+ states that work. On other states of the path, the numbered arrows show it. A group cannot have
145
+ the name of a status, such as "Breaks a rule".
146
+
147
+ Writes only the files that --out, --story, and --summary name.
148
+
149
+ Exit codes
150
+ 0 It wrote the chart.
151
+ 1 It cannot use an input: a mapping without state(), TLC output that it refuses, or a
152
+ --highlight with no such bug. The error says why.
153
+ 2 A bad argument.`;
154
+ const more = "Run node tlc-to-chart.mjs --help for the flags.";
155
+
156
+ const args = {
157
+ traces: [],
158
+ fixedTraces: [],
159
+ againstTraces: [],
160
+ specs: [],
161
+ view: "customer",
162
+ highlight: 1,
163
+ };
164
+ for (let i = 2; i < process.argv.length; i++) {
165
+ const a = process.argv[i];
166
+ const next = () => process.argv[++i];
167
+ if (a === "-h" || a === "--help") {
168
+ console.log(HELP);
169
+ process.exit(0);
170
+ } else if (a === "--dot") args.dot = next();
171
+ else if (a === "--map") args.map = next();
172
+ else if (a === "--traces") args.traces.push(next());
173
+ else if (a === "--fixed-dot") args.fixedDot = next();
174
+ else if (a === "--fixed-traces") args.fixedTraces.push(next());
175
+ else if (a === "--against-dot") args.againstDot = next();
176
+ else if (a === "--against-traces") args.againstTraces.push(next());
177
+ else if (a === "--label") args.label = next();
178
+ else if (a === "--spec") args.specs.push(next());
179
+ else if (a === "--view") args.view = next();
180
+ else if (a === "--highlight") args.highlight = Number(next());
181
+ else if (a === "--path-only") args.pathOnly = true;
182
+ else if (a === "--full-labels") args.fullLabels = true;
183
+ else if (a === "--flat") args.flat = true;
184
+ else if (a === "--regions") args.regions = true;
185
+ else if (a === "--proof") args.proof = next();
186
+ else if (a === "--regions-all") args.regionsAll = next();
187
+ else if (a === "--cover") args.cover = true;
188
+ else if (a === "--title") args.title = next();
189
+ else if (a === "--out") args.out = next();
190
+ else if (a === "--story") args.story = next();
191
+ else if (a === "--summary") args.summary = next();
192
+ else {
193
+ console.error(`unknown argument ${a}. ${more}`);
194
+ process.exit(2);
195
+ }
196
+ }
197
+ const fail = (message) => {
198
+ console.error(`${message} ${more}`);
199
+ process.exit(2);
200
+ };
201
+ if (!args.dot || !args.map) fail(USAGE);
202
+ if (!["customer", "developer", "layered"].includes(args.view))
203
+ fail(`--view must be customer, developer, or layered, not ${args.view}.`);
204
+ if (args.fixedDot && args.againstDot) fail("use --fixed-dot or --against-dot, not both.");
205
+ if (args.fixedTraces.length && !args.fixedDot) fail("--fixed-traces needs --fixed-dot.");
206
+ if (args.againstTraces.length && !args.againstDot) fail("--against-traces needs --against-dot.");
207
+ if (args.label !== undefined && !args.fixedDot && !args.againstDot)
208
+ fail("--label names the second model; it needs --fixed-dot or --against-dot.");
209
+ if (args.proof && !args.regions) fail("--proof writes the regions proof; it needs --regions.");
210
+ if (args.regionsAll && !args.regions)
211
+ fail("--regions-all writes every regions arrow; it needs --regions.");
212
+ if (
213
+ args.regions &&
214
+ (args.view !== "customer" || args.flat || args.pathOnly || args.highlight !== 0)
215
+ )
216
+ fail(
217
+ "--regions draws the whole customer chart: use --highlight 0, without --flat or --path-only.",
218
+ );
219
+
220
+ const role = args.fixedDot ? "fix" : args.againstDot ? "against" : null;
221
+ const L = args.label ?? (role === "fix" ? "the fix" : "the spec");
222
+ const within = role === "fix" ? "with" : "in";
223
+ const cap = (s) => s.charAt(0).toUpperCase() + s.slice(1);
224
+
225
+ const mapping = await import(pathToFileURL(path.resolve(args.map)).href);
226
+ if (typeof mapping.state !== "function")
227
+ throw new Error("the mapping module must export state(vars)");
228
+ if (args.view !== "customer" && typeof mapping.detail !== "function")
229
+ throw new Error(`--view ${args.view} needs detail(vars) in the mapping module`);
230
+ const SEP = "\u0000";
231
+ const edgeId = (from, to) => `${from}${SEP}${SEP}${to}`;
232
+
233
+ const rules = new Map();
234
+ for (const f of args.specs)
235
+ for (const [name, sentence] of ruleSentences(fs.readFileSync(f, "utf8")))
236
+ if (!rules.has(name)) rules.set(name, sentence);
237
+
238
+ const readerActions = new Set(mapping.readerActions ?? []);
239
+ const factsFrom = (enabled) => ({
240
+ enabled,
241
+ settled: [...enabled].every((a) => readerActions.has(a)),
242
+ });
243
+ function keyOf(vars, facts) {
244
+ if (args.view === "developer") return String(mapping.detail(vars, facts));
245
+ const outer = mapping.state(vars, facts);
246
+ if (typeof outer !== "string")
247
+ throw new Error(`state() must return a string; got ${JSON.stringify(outer)}`);
248
+ return args.view === "layered" ? `${outer}${SEP}${mapping.detail(vars, facts)}` : outer;
249
+ }
250
+ function stepLabel(action, before, after, view = args.view) {
251
+ const said = typeof mapping.step === "function" ? mapping.step(action, before, after) : action;
252
+ const named = view === "customer" || said === action ? said : `${said} [${action}]`;
253
+ const seconds = typeof mapping.timer === "function" ? mapping.timer(action, before, after) : null;
254
+ if (seconds === undefined || seconds === null || seconds === "" || !named) return named;
255
+ if (typeof seconds !== "number" || !Number.isFinite(seconds) || seconds <= 0)
256
+ throw new Error(
257
+ `timer() must return a number of seconds above 0, or nothing; got ${JSON.stringify(seconds)} for ${action}`,
258
+ );
259
+ return `after ${seconds} s, ${named}`;
260
+ }
261
+ function stepGuard(action, before, after) {
262
+ if (typeof mapping.guard !== "function") return "";
263
+ return String(mapping.guard(action, before, after) ?? "")
264
+ .replace(/[[\]]/g, "")
265
+ .trim();
266
+ }
267
+ function guarded(guards, label) {
268
+ const words = guards?.get(label);
269
+ return words && !words.has("") ? `${label} [${[...words].sort().join(" or ")}]` : label;
270
+ }
271
+ const addGuard = (guards, label, words) => {
272
+ if (!guards.has(label)) guards.set(label, new Set());
273
+ guards.get(label).add(words);
274
+ };
275
+
276
+ function lacking(a, b, prefix = []) {
277
+ return Object.keys(a).flatMap((k) =>
278
+ !(k in b)
279
+ ? [[...prefix, k]]
280
+ : isRecord(a[k]) && isRecord(b[k])
281
+ ? lacking(a[k], b[k], [...prefix, k])
282
+ : [],
283
+ );
284
+ }
285
+ const holder = (o, p) => p.slice(0, -1).reduce((x, k) => (isRecord(x?.[k]) ? x[k] : null), o);
286
+ function omit(vars, paths) {
287
+ if (!paths.length) return vars;
288
+ const out = structuredClone(vars);
289
+ for (const p of paths) {
290
+ const o = holder(out, p);
291
+ if (o) delete o[p.at(-1)];
292
+ }
293
+ return out;
294
+ }
295
+ function fill(vars, paths, from) {
296
+ if (!paths.length) return vars;
297
+ const out = structuredClone(vars);
298
+ for (const p of paths) {
299
+ const [o, f] = [holder(out, p), holder(from, p)];
300
+ if (o && f && p.at(-1) in f) o[p.at(-1)] = structuredClone(f[p.at(-1)]);
301
+ }
302
+ return out;
303
+ }
304
+
305
+ function modelOf(graph, lookup = (vars) => vars) {
306
+ const byId = new Map([...graph.nodes.keys()].map((id) => [id, new Set()]));
307
+ for (const e of [...graph.edges, ...graph.loops]) byId.get(e.from).add(e.action);
308
+ const index = new Map(
309
+ [...graph.nodes.values()].map((n) => [stateKey(n.raw ?? n.vars), byId.get(n.id)]),
310
+ );
311
+ const facts = (vars) => factsFrom(index.get(stateKey(lookup(vars))) ?? new Set());
312
+ return {
313
+ graph,
314
+ facts,
315
+ key: (vars) => keyOf(vars, facts(vars)),
316
+ customer: (vars) => String(mapping.state(vars, facts(vars))),
317
+ };
318
+ }
319
+
320
+ function quotient(m) {
321
+ const { graph } = m;
322
+ const keys = new Map();
323
+ for (const n of graph.nodes.values()) keys.set(n.id, m.key(n.vars));
324
+ const nodes = new Set(keys.values());
325
+ const initial = new Set(
326
+ [...graph.nodes.values()].filter((n) => n.init).map((n) => keys.get(n.id)),
327
+ );
328
+ const edges = new Map();
329
+ for (const e of graph.edges) {
330
+ const from = keys.get(e.from);
331
+ const to = keys.get(e.to);
332
+ if (from === to) continue;
333
+ const id = edgeId(from, to);
334
+ if (!edges.has(id)) edges.set(id, { from, to, counts: new Map(), guards: new Map() });
335
+ const [before, after] = [graph.nodes.get(e.from).vars, graph.nodes.get(e.to).vars];
336
+ const label = stepLabel(e.action, before, after);
337
+ if (!label) continue;
338
+ edges.get(id).counts.set(label, (edges.get(id).counts.get(label) ?? 0) + 1);
339
+ addGuard(edges.get(id).guards, label, stepGuard(e.action, before, after));
340
+ }
341
+ for (const e of edges.values())
342
+ e.labels = new Set([...e.counts].sort((a, b) => b[1] - a[1]).map(([l]) => l));
343
+ const settled = new Set(
344
+ [...graph.nodes.values()].filter((n) => m.facts(n.vars).settled).map((n) => keys.get(n.id)),
345
+ );
346
+ return { nodes, initial, edges, keys, settled };
347
+ }
348
+
349
+ const graph = parseDot(fs.readFileSync(args.dot, "utf8"));
350
+ const main = modelOf(graph);
351
+ const now = quotient(main);
352
+ const mainInit = [...graph.nodes.values()].find((n) => n.init).vars;
353
+
354
+ const secondDot = args.fixedDot ?? args.againstDot;
355
+ const secondGraph = secondDot ? parseDot(fs.readFileSync(secondDot, "utf8")) : null;
356
+ const secondInit = secondGraph && [...secondGraph.nodes.values()].find((n) => n.init).vars;
357
+ const lacks = secondGraph ? lacking(mainInit, secondInit) : [];
358
+ const extra = secondGraph ? lacking(secondInit, mainInit) : [];
359
+ const secondFilled = secondGraph && {
360
+ ...secondGraph,
361
+ nodes: new Map(
362
+ [...secondGraph.nodes].map(([id, n]) => [
363
+ id,
364
+ { ...n, raw: n.vars, vars: fill(n.vars, lacks, mainInit) },
365
+ ]),
366
+ ),
367
+ };
368
+ const second = secondFilled && modelOf(secondFilled, (vars) => omit(vars, lacks));
369
+ const secondQ = second && quotient(second);
370
+
371
+ function readerPath(trace, key, labelOf = stepLabel) {
372
+ const out = [{ key: key(trace.steps[0].vars), label: null, modelStep: 0 }];
373
+ for (let i = 1; i < trace.steps.length; i++) {
374
+ const { action } = trace.steps[i];
375
+ const k = key(trace.steps[i].vars);
376
+ if (k === out[out.length - 1].key && !readerActions.has(action)) continue;
377
+ const [before, after] = [trace.steps[i - 1].vars, trace.steps[i].vars];
378
+ const label = labelOf(action, before, after) || action;
379
+ const guard = stepGuard(action, before, after);
380
+ out.push({ key: k, label, guard, action, modelStep: i });
381
+ }
382
+ return out;
383
+ }
384
+
385
+ const readerLabel = (action, before, after) => stepLabel(action, before, after, "customer");
386
+ function replaySecond(trace) {
387
+ if (!second) return null;
388
+ const shared = (vars) => stateKey(omit(vars, lacks));
389
+ const sharedOf = (n) => stateKey(omit(n.raw, extra));
390
+ const out = new Map();
391
+ for (const e of [...second.graph.edges, ...second.graph.loops]) {
392
+ if (!out.has(e.from)) out.set(e.from, []);
393
+ out.get(e.from).push(e);
394
+ }
395
+ const actions = new Set([...second.graph.edges, ...second.graph.loops].map((e) => e.action));
396
+ const nodeOf = (id) => second.graph.nodes.get(id);
397
+ const screen = new Map(
398
+ [...second.graph.nodes.values()].map((n) => [n.id, second.customer(n.vars)]),
399
+ );
400
+ const words = (e) => readerLabel(e.action, nodeOf(e.from).vars, nodeOf(e.to).vars) || e.action;
401
+ const settle = (ids) => {
402
+ const all = new Set(ids);
403
+ for (const id of all)
404
+ for (const e of out.get(id) ?? [])
405
+ if (!readerActions.has(e.action) && screen.get(e.to) === screen.get(id)) all.add(e.to);
406
+ return all;
407
+ };
408
+ const seen = readerPath(trace, main.customer, readerLabel);
409
+ const shownAt = new Map(seen.map((p, j) => [p.modelStep, j]));
410
+ let here = settle(
411
+ [...second.graph.nodes.values()]
412
+ .filter((n) => n.init && screen.get(n.id) === seen[0].key)
413
+ .map((n) => n.id),
414
+ );
415
+ if (here.size === 0) return { reason: "starts on another screen" };
416
+ const passed = new Set(here);
417
+ let j = 0;
418
+ for (let i = 1; i < trace.steps.length; i++) {
419
+ const { action } = trace.steps[i];
420
+ const hidden = !shownAt.has(i);
421
+ if (!hidden) j = shownAt.get(i);
422
+ const want = seen[j];
423
+ const [before, after] = [trace.steps[i - 1].vars, trace.steps[i].vars];
424
+ const known = actions.has(action);
425
+ if (!known && (hidden || shared(before) === shared(after))) continue;
426
+ const tries = [...here].flatMap((id) =>
427
+ (out.get(id) ?? []).filter((e) =>
428
+ known ? e.action === action : sharedOf(nodeOf(e.to)) === shared(after),
429
+ ),
430
+ );
431
+ if (hidden && tries.length === 0) continue;
432
+ const same = known
433
+ ? tries.filter((e) => screen.get(e.to) === want.key && (hidden || words(e) === want.label))
434
+ : tries;
435
+ if (same.length) {
436
+ here = settle(same.map((e) => e.to));
437
+ for (const id of here) passed.add(id);
438
+ continue;
439
+ }
440
+ const outcomes = new Map();
441
+ for (const e of tries) {
442
+ const id = `${screen.get(e.to)}${SEP}${words(e)}`;
443
+ outcomes.set(id, [...(outcomes.get(id) ?? []), e]);
444
+ }
445
+ const taken = [...outcomes.values()].sort((a, b) => b.length - a.length)[0]?.[0];
446
+ const from = main.key(before);
447
+ const said = hidden ? readerLabel(action, before, after) || action : want.label;
448
+ const at = { step: j, said, from, hidden };
449
+ if (!taken)
450
+ return {
451
+ ...at,
452
+ key: from,
453
+ label: stepLabel(action, before, after) || action,
454
+ disabled: true,
455
+ };
456
+ const [b, a] = [nodeOf(taken.from).vars, nodeOf(taken.to).vars];
457
+ return { ...at, key: second.key(a), label: stepLabel(taken.action, b, a) || taken.action };
458
+ }
459
+ const bad = new Set(
460
+ rawSecondTraces
461
+ .filter((t) => t.invariant === trace.invariant)
462
+ .map((t) => stateKey(t.steps[t.steps.length - 1].vars)),
463
+ );
464
+ const ruleHolds =
465
+ secondTraceFiles.length > 0 && ![...passed].some((id) => bad.has(stateKey(nodeOf(id).vars)));
466
+ return { sameEnd: true, ruleHolds };
467
+ }
468
+
469
+ function readTraces(files, paths = []) {
470
+ return files.flatMap((f) => {
471
+ const text = fs.readFileSync(f, "utf8");
472
+ checkTlcOutput(text, f);
473
+ return parseTraces(text).map((t) => ({
474
+ ...t,
475
+ steps: t.steps.map((s) => ({ ...s, vars: fill(s.vars, paths, mainInit) })),
476
+ }));
477
+ });
478
+ }
479
+ // With -continue, TLC reports every bad state, including states after the first bad one.
480
+ function firstBad(raw) {
481
+ const badStates = new Map();
482
+ for (const t of raw) {
483
+ if (!badStates.has(t.invariant)) badStates.set(t.invariant, new Set());
484
+ badStates.get(t.invariant).add(stateKey(t.steps[t.steps.length - 1].vars));
485
+ }
486
+ const cuts = [];
487
+ const seen = new Set();
488
+ for (const t of raw) {
489
+ const first = t.steps.findIndex((s) => badStates.get(t.invariant).has(stateKey(s.vars)));
490
+ const cut = { invariant: t.invariant, steps: t.steps.slice(0, first + 1) };
491
+ const id = `${t.invariant}${SEP}${stateKey(cut.steps[cut.steps.length - 1].vars)}`;
492
+ if (seen.has(id)) continue;
493
+ seen.add(id);
494
+ cuts.push(cut);
495
+ }
496
+ return cuts;
497
+ }
498
+ const rawTraces = readTraces(args.traces);
499
+ const secondTraceFiles = role === "fix" ? args.fixedTraces : args.againstTraces;
500
+ const rawSecondTraces = readTraces(secondTraceFiles, lacks);
501
+ const traces = firstBad(rawTraces);
502
+ const secondTraces = firstBad(rawSecondTraces);
503
+ function group(cuts, m) {
504
+ const byId = new Map();
505
+ for (const trace of cuts) {
506
+ const rp = readerPath(trace, m.customer);
507
+ const last = trace.steps[trace.steps.length - 1].vars;
508
+ const said =
509
+ typeof mapping.cause === "function"
510
+ ? mapping.cause(last, m.facts(last), trace.invariant)
511
+ : null;
512
+ const cause = said === undefined || said === null ? null : String(said);
513
+ const id = `${trace.invariant}${SEP}${cause ?? rp[rp.length - 1].key}`;
514
+ const prev = byId.get(id);
515
+ if (
516
+ !prev ||
517
+ rp.length < prev.steps ||
518
+ (rp.length === prev.steps && trace.steps.length < prev.trace.steps.length)
519
+ ) {
520
+ byId.set(id, {
521
+ id,
522
+ cause,
523
+ steps: rp.length,
524
+ trace,
525
+ invariant: trace.invariant,
526
+ count: (prev?.count ?? 0) + 1,
527
+ });
528
+ } else prev.count++;
529
+ }
530
+ return byId;
531
+ }
532
+ const byOrder = (a, b) =>
533
+ a.steps - b.steps ||
534
+ a.trace.steps.length - b.trace.steps.length ||
535
+ (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
536
+ const inSecond = (l) => `${within} ${L}, ${l}`;
537
+ const modelEdges = new Map(now.edges);
538
+ if (secondQ)
539
+ for (const [id, e] of secondQ.edges)
540
+ if (!modelEdges.has(id))
541
+ modelEdges.set(id, {
542
+ ...e,
543
+ labels: new Set([...e.labels].map(inSecond)),
544
+ guards: new Map([...e.guards].map(([l, words]) => [inSecond(l), words])),
545
+ secondOnly: true,
546
+ });
547
+ function divergence(s) {
548
+ if (s?.key === undefined) return s;
549
+ const plain = edgeId(s.from, s.key);
550
+ const text = inSecond(`${s.label}${s.disabled ? " cannot happen" : ""}`);
551
+ const drawn = !s.disabled && [s.label, text].some((l) => modelEdges.get(plain)?.labels.has(l));
552
+ return { ...s, text, edge: drawn ? plain : `${plain}${SEP}second` };
553
+ }
554
+ const withPath = (c, m) => {
555
+ const path = readerPath(c.trace, m.key);
556
+ return { ...c, path, end: path[path.length - 1].key };
557
+ };
558
+ const classesById = group(traces, main);
559
+ const mainClasses = [...classesById.values()]
560
+ .sort(byOrder)
561
+ .map((c) => ({ ...withPath(c, main), second: divergence(replaySecond(c.trace)) }));
562
+ const introduced = second
563
+ ? [...group(secondTraces, second).values()]
564
+ .filter((c) => !classesById.has(c.id))
565
+ .sort(byOrder)
566
+ .map((c) => ({ ...withPath(c, second), second: null, introduced: true }))
567
+ : [];
568
+ const classes = [...mainClasses, ...introduced];
569
+
570
+ const diverges = (c) => c.second?.key !== undefined;
571
+ function badKeys(m, keys, raw) {
572
+ const g = m.graph;
573
+ const bad = new Set(raw.map((t) => stateKey(t.steps[t.steps.length - 1].vars)));
574
+ const out = new Set(raw.map((t) => m.key(t.steps[t.steps.length - 1].vars)));
575
+ const ok = (id) => !bad.has(stateKey(g.nodes.get(id).vars));
576
+ const next = new Map();
577
+ for (const e of g.edges) {
578
+ if (!next.has(e.from)) next.set(e.from, []);
579
+ next.get(e.from).push(e.to);
580
+ }
581
+ const clean = new Set([...g.nodes.values()].filter((n) => n.init && ok(n.id)).map((n) => n.id));
582
+ const queue = [...clean];
583
+ while (queue.length) {
584
+ for (const to of next.get(queue.shift()) ?? []) {
585
+ if (clean.has(to) || !ok(to)) continue;
586
+ clean.add(to);
587
+ queue.push(to);
588
+ }
589
+ }
590
+ const reached = new Set([...clean].map((id) => keys.get(id)));
591
+ for (const k of keys.values()) if (!reached.has(k)) out.add(k);
592
+ return out;
593
+ }
594
+ const badNow = badKeys(main, now.keys, rawTraces);
595
+ const badSecond =
596
+ second && secondTraceFiles.length > 0
597
+ ? badKeys(second, secondQ.keys, rawSecondTraces)
598
+ : role === "fix"
599
+ ? badNow
600
+ : new Set();
601
+ const allNodes = new Set([...now.nodes, ...(secondQ?.nodes ?? [])]);
602
+ const initial = new Set([...now.initial, ...(secondQ?.initial ?? [])]);
603
+ const onlySecond = (k) => second && !now.nodes.has(k);
604
+ const status = (k) => {
605
+ if (role === "against")
606
+ return badNow.has(k) || badSecond.has(k) ? "wrong" : onlySecond(k) ? "fixed" : "works";
607
+ if (role === "fix")
608
+ return badSecond.has(k) ? "wrong" : badNow.has(k) || onlySecond(k) ? "fixed" : "works";
609
+ return badNow.has(k) ? "wrong" : "works";
610
+ };
611
+
612
+ const chosen = args.highlight > 0 ? classes[args.highlight - 1] : null;
613
+ if (args.highlight > 0 && classes.length > 0 && !chosen)
614
+ throw new Error(`--highlight ${args.highlight}: only ${classes.length} classes`);
615
+ const onPath = new Set(chosen ? chosen.path.map((p) => p.key) : []);
616
+ if (chosen && diverges(chosen)) onPath.add(chosen.second.key);
617
+
618
+ const edgesAlong = (c) =>
619
+ c.path.slice(1).map((p, i) => ({
620
+ id: edgeId(c.path[i].key, p.key),
621
+ from: c.path[i].key,
622
+ to: p.key,
623
+ label: p.label,
624
+ guard: p.guard,
625
+ }));
626
+ function chartEdges() {
627
+ const edges = new Map(modelEdges);
628
+ for (const { second: s } of classes.filter(diverges)) {
629
+ if (modelEdges.has(s.edge)) continue;
630
+ if (!edges.has(s.edge))
631
+ edges.set(s.edge, { from: s.from, to: s.key, labels: new Set(), secondOnly: true });
632
+ edges.get(s.edge).labels.add(s.text);
633
+ }
634
+ const pathNumbers = new Map();
635
+ (chosen ? edgesAlong(chosen) : []).forEach((step, i) => {
636
+ if (!pathNumbers.has(step.id)) pathNumbers.set(step.id, []);
637
+ const shown = step.guard ? `${step.label} [${step.guard}]` : step.label;
638
+ pathNumbers.get(step.id).push({ n: i + 1, label: step.label, shown });
639
+ if (!edges.has(step.id))
640
+ edges.set(step.id, { from: step.from, to: step.to, labels: new Set([step.label]) });
641
+ });
642
+ if (!args.cover) return { edges, pathNumbers, coverEdges: null };
643
+
644
+ const coverEdges = new Set();
645
+ const bfs = (q, targets) => {
646
+ const parent = new Map([...q.initial].map((k) => [k, null]));
647
+ const queue = [...q.initial];
648
+ const outEdges = new Map();
649
+ for (const [id, e] of q.edges) {
650
+ if (!outEdges.has(e.from)) outEdges.set(e.from, []);
651
+ outEdges.get(e.from).push([id, e]);
652
+ }
653
+ while (queue.length) {
654
+ const k = queue.shift();
655
+ for (const [id, e] of outEdges.get(k) ?? []) {
656
+ if (parent.has(e.to)) continue;
657
+ parent.set(e.to, id);
658
+ queue.push(e.to);
659
+ }
660
+ }
661
+ for (const t of targets) {
662
+ for (let id = parent.get(t); id; id = parent.get(q.edges.get(id).from)) coverEdges.add(id);
663
+ }
664
+ };
665
+ bfs(now, now.settled);
666
+ if (secondQ)
667
+ bfs(
668
+ secondQ,
669
+ [...secondQ.settled].filter((k) => !now.nodes.has(k)),
670
+ );
671
+ const pathLabels = new Map();
672
+ for (const c of classes) {
673
+ for (const step of edgesAlong(c)) {
674
+ coverEdges.add(step.id);
675
+ pathLabels.set(step.id, (pathLabels.get(step.id) ?? new Set()).add(step.label));
676
+ }
677
+ if (diverges(c)) coverEdges.add(c.second.edge);
678
+ }
679
+ for (const id of coverEdges) {
680
+ if (edges.has(id)) continue;
681
+ const [from, to] = id.split(`${SEP}${SEP}`);
682
+ edges.set(id, { from, to, labels: pathLabels.get(id) });
683
+ }
684
+ return { edges, pathNumbers, coverEdges };
685
+ }
686
+ const { edges, pathNumbers, coverEdges } = chartEdges();
687
+
688
+ const LEGEND = {
689
+ works: ["legendWorks", mapping.legend?.works ?? (role === "fix" ? "Works today" : "Works")],
690
+ wrong: [
691
+ "legendWrong",
692
+ mapping.legend?.wrong ?? (role === "fix" ? `Still wrong with ${L}` : "Breaks a rule"),
693
+ ],
694
+ fixed: [
695
+ "legendFixed",
696
+ mapping.legend?.fixed ?? (role === "fix" ? `Fixed or new with ${L}` : `Only in ${L}`),
697
+ ],
698
+ };
699
+
700
+ const grouping = typeof mapping.group === "function";
701
+ const groupsOf = new Map();
702
+ const groupOf = (k) => (groupsOf.get(k)?.size === 1 ? [...groupsOf.get(k)][0] : "");
703
+ const groupNames = [];
704
+ const fills = new Map();
705
+ if (grouping) {
706
+ const add = (k, vars, m) => {
707
+ const g = mapping.group(vars, m.facts(vars));
708
+ if (g !== undefined && g !== null && typeof g !== "string")
709
+ throw new Error(`group() must return a group name or nothing; got ${JSON.stringify(g)}`);
710
+ if (!groupsOf.has(k)) groupsOf.set(k, new Set());
711
+ groupsOf.get(k).add((g ?? "").replace(/\s+/g, " ").trim());
712
+ };
713
+ for (const n of graph.nodes.values()) add(now.keys.get(n.id), n.vars, main);
714
+ if (second)
715
+ for (const n of second.graph.nodes.values()) add(secondQ.keys.get(n.id), n.vars, second);
716
+
717
+ const given = mapping.groupColors ?? {};
718
+ if (!isRecord(given))
719
+ throw new Error("groupColors must be an object from a group name to a color such as #d6eaf8");
720
+ const statusNames = new Set(
721
+ [
722
+ "Works",
723
+ "Works today",
724
+ "Breaks a rule",
725
+ "Still wrong",
726
+ "Fixed",
727
+ ...Object.values(LEGEND).map(([, t]) => t),
728
+ ].map((t) => t.toLowerCase()),
729
+ );
730
+ const found = new Set([...groupsOf.values()].flatMap((gs) => [...gs]).filter(Boolean));
731
+ for (const g of found)
732
+ if (statusNames.has(g.toLowerCase()))
733
+ throw new Error(
734
+ `group "${g}" has the name of a status; TLC decides the status, so name the group for what the reader sees`,
735
+ );
736
+ groupNames.push(
737
+ ...Object.keys(given).filter((g) => found.has(g)),
738
+ ...[...found].filter((g) => !Object.hasOwn(given, g)).sort(),
739
+ );
740
+ for (const [g, color] of Object.entries(given)) {
741
+ if (color === undefined || color === null || color === "") continue;
742
+ const why = groupColorProblem(color);
743
+ if (why) throw new Error(`groupColors["${g}"] ${JSON.stringify(color)} ${why}`);
744
+ if (found.has(g)) fills.set(g, color.toLowerCase());
745
+ }
746
+ const free = GROUP_PALETTE.filter((c) => ![...fills.values()].includes(c));
747
+ for (const g of groupNames) {
748
+ if (fills.has(g)) continue;
749
+ if (!free.length)
750
+ throw new Error(
751
+ `the mapping has ${groupNames.length} groups, more than the ${GROUP_PALETTE.length} colors of the palette; give the others a color in groupColors`,
752
+ );
753
+ fills.set(g, free.shift());
754
+ }
755
+ }
756
+ const mixedGroups = [...groupsOf].filter(([, gs]) => gs.size > 1).map(([k]) => k);
757
+
758
+ const BORDER = { works: "", wrong: "Wrong", fixed: "Fixed" };
759
+ const classFor = (g, s, path) =>
760
+ grouping
761
+ ? `${g ? `group${groupNames.indexOf(g)}` : "plain"}${BORDER[s] || (path ? "Path" : "")}`
762
+ : `${s}${path ? "Path" : ""}`;
763
+ const drawnState = (g, s, path = false) => ({ group: g, status: s, cls: classFor(g, s, path) });
764
+ const groupCounts = (drawn) =>
765
+ groupNames
766
+ .map((g) => ({
767
+ name: g,
768
+ color: fills.get(g),
769
+ states: drawn.filter((d) => d.group === g).length,
770
+ }))
771
+ .filter((g) => g.states > 0);
772
+
773
+ // Mermaid shrinks a chart to the page width unless useMaxWidth is false, and rewraps labels that
774
+ // wrap() already wrapped unless markdownAutoWrap is false.
775
+ const frontMatter = (title) => [
776
+ "---",
777
+ ...(title ? [`title: ${JSON.stringify(title)}`] : []),
778
+ "config:",
779
+ " markdownAutoWrap: false",
780
+ " state:",
781
+ " useMaxWidth: false",
782
+ "---",
783
+ ];
784
+ function head(drawn) {
785
+ const out = [...frontMatter(args.title), "stateDiagram-v2"];
786
+ if (role) out.push(` %% second model (${role}): ${L}`);
787
+ if (args.fullLabels) out.push(` ${FULL_LABELS}`);
788
+ const used = Object.keys(LEGEND).filter((s) => drawn.some((d) => d.status === s));
789
+ const legend = grouping
790
+ ? [
791
+ ...groupCounts(drawn).map(({ name }) => {
792
+ const i = groupNames.indexOf(name);
793
+ return [`legendGroup${i}`, name, `group${i}`];
794
+ }),
795
+ ...used.filter((s) => BORDER[s]).map((s) => [...LEGEND[s], `plain${BORDER[s]}`]),
796
+ ]
797
+ : used.map((s) => [...LEGEND[s], s]);
798
+ if (grouping) {
799
+ const styleClasses = new Set([...drawn.map((d) => d.cls), ...legend.map(([, , c]) => c)]);
800
+ const bases = [["plain", PLAIN_FILL], ...groupNames.map((g, i) => [`group${i}`, fills.get(g)])];
801
+ for (const [base, fill] of bases)
802
+ for (const border of ["", "Wrong", "Fixed", "Path"])
803
+ if (styleClasses.has(base + border)) out.push(` ${groupClassDef(base, fill, border)}`);
804
+ } else for (const c of STATUS_CLASSES) out.push(` ${c}`);
805
+ if (legend.length) {
806
+ out.push(` state "Legend" as ${LEGEND_ID} {`);
807
+ for (const [id, text] of legend) out.push(` state "${quote(text)}" as ${id}`);
808
+ out.push(" }");
809
+ for (const [id, , c] of legend) out.push(` class ${id} ${c}`);
810
+ }
811
+ return out;
812
+ }
813
+
814
+ function regionsChart({ states, starts, edges, classes, proofPath, allPath }) {
815
+ const norm = (s) => s.replaceAll(";", ",").replace(/\s+/g, " ").trim();
816
+ const split = (k) => {
817
+ const at = k.indexOf(": ");
818
+ return at > 0 && at + 2 < k.length ? [norm(k.slice(0, at)), norm(k.slice(at + 2))] : null;
819
+ };
820
+ const nodes = [...states].sort();
821
+ if (!nodes.every(split)) return null;
822
+ const steps = [];
823
+ for (const e of edges.values()) {
824
+ if (e.labels.size === 0) return { why: "a step has no label" };
825
+ for (const l of e.labels)
826
+ steps.push({
827
+ from: split(e.from),
828
+ to: split(e.to),
829
+ label: norm(l),
830
+ words: e.guards?.get(l) ?? new Set([""]),
831
+ });
832
+ }
833
+ const bar = (p) => p.join(" | ");
834
+ const flat = {
835
+ Main: {
836
+ states: new Set(nodes.map((k) => bar(split(k)))),
837
+ initial: new Set(starts.map((k) => bar(split(k)))),
838
+ transitions: steps.map((s) => ({ from: bar(s.from), to: bar(s.to), label: s.label })),
839
+ },
840
+ };
841
+ const ids = new Map([...flat.Main.states].map((s, i) => [s, `p${i}`]));
842
+ const proof = [
843
+ ...frontMatter(),
844
+ "stateDiagram-v2",
845
+ ...[...ids].map(([s, id]) => ` state "${quote(s)}" as ${id}`),
846
+ ...[...flat.Main.initial].map((s) => ` [*] --> ${ids.get(s)}`),
847
+ ...steps.map((s) => ` ${ids.get(bar(s.from))} --> ${ids.get(bar(s.to))} : ${s.label}`),
848
+ ];
849
+ if (proofPath) fs.writeFileSync(proofPath, proof.join("\n") + "\n");
850
+
851
+ const beside = [new Map(), new Map()];
852
+ for (const k of nodes) {
853
+ const p = split(k);
854
+ for (const i of [0, 1]) {
855
+ if (!beside[i].has(p[i])) beside[i].set(p[i], new Set());
856
+ beside[i].get(p[i]).add(p[1 - i]);
857
+ }
858
+ }
859
+ const size = beside[0].size + beside[1].size;
860
+ if (size >= nodes.length)
861
+ return { why: `${size} region states are not fewer than the ${nodes.length} flat states` };
862
+
863
+ const rank = ["works", "fixed", "wrong"];
864
+ const regionStatus = [new Map(), new Map()];
865
+ for (const k of nodes) {
866
+ const p = split(k);
867
+ for (const i of [0, 1]) {
868
+ const prev = regionStatus[i].get(p[i]);
869
+ if (!prev || rank.indexOf(status(k)) < rank.indexOf(prev))
870
+ regionStatus[i].set(p[i], status(k));
871
+ }
872
+ }
873
+ for (const k of nodes) {
874
+ const p = split(k);
875
+ const shown = rank[Math.max(...[0, 1].map((i) => rank.indexOf(regionStatus[i].get(p[i]))))];
876
+ if (shown !== status(k))
877
+ return {
878
+ why: `"${k}" is "${LEGEND[status(k)][1]}", and its two region states would show "${LEGEND[shown][1]}"`,
879
+ };
880
+ }
881
+
882
+ const moves = [new Map(), new Map()];
883
+ for (const s of steps)
884
+ for (const i of [0, 1]) {
885
+ const id = [s.from[i], s.label, s.to[i]].join(SEP);
886
+ if (!moves[i].has(id))
887
+ moves[i].set(id, {
888
+ from: s.from[i],
889
+ label: s.label,
890
+ to: s.to[i],
891
+ beside: new Set(),
892
+ guards: new Map(),
893
+ });
894
+ moves[i].get(id).beside.add(s.from[1 - i]);
895
+ for (const w of s.words) addGuard(moves[i].get(id).guards, s.label, norm(w));
896
+ }
897
+ for (const m of moves)
898
+ for (const [id, x] of m)
899
+ if (
900
+ x.from === x.to &&
901
+ ![...m.values()].some((y) => y.from === x.from && y.label === x.label && y.to !== x.from)
902
+ )
903
+ m.delete(id);
904
+ const rid = beside.map((m, i) => new Map([...m.keys()].sort().map((s, j) => [s, `r${i}s${j}`])));
905
+ const regionGroup = [new Map(), new Map()];
906
+ for (const k of nodes) {
907
+ const p = split(k);
908
+ for (const i of [0, 1]) {
909
+ const prev = regionGroup[i].get(p[i]);
910
+ regionGroup[i].set(p[i], prev === undefined || prev === groupOf(k) ? groupOf(k) : "");
911
+ }
912
+ }
913
+ const drawn = rid.map((m, i) =>
914
+ [...m.keys()].map((s) => drawnState(regionGroup[i].get(s), regionStatus[i].get(s))),
915
+ );
916
+ const draw = (only) => {
917
+ const out = head(drawn.flat());
918
+ out.push(` state "At the same time" as regions {`);
919
+ let count = 0;
920
+ for (const i of [0, 1]) {
921
+ if (i) out.push(" --");
922
+ const name = mapping.regionNames?.[i];
923
+ const pad = name ? " " : " ";
924
+ if (name) out.push(` state "${quote(wrap(name))}" as region${i} {`);
925
+ for (const s of new Set(starts.map((k) => split(k)[i])))
926
+ out.push(`${pad}[*] --> ${rid[i].get(s)}`);
927
+ for (const [s, id] of rid[i]) out.push(`${pad}state "${quote(wrap(s))}" as ${id}`);
928
+ const shownMoves = [...moves[i]]
929
+ .filter(([id]) => !only || only[i].has(id))
930
+ .map(([, m]) => m)
931
+ .sort((a, b) => a.from.localeCompare(b.from) || a.label.localeCompare(b.label));
932
+ for (const m of shownMoves) {
933
+ const all = beside[i].get(m.from);
934
+ const not = [...all].filter((s) => !m.beside.has(s)).sort();
935
+ const yes = [...m.beside].sort();
936
+ const unless = not.length > 0 && not.length < yes.length;
937
+ const guard = not.length === 0 ? null : unless ? not : yes;
938
+ const worded = guarded(m.guards, m.label);
939
+ const text =
940
+ worded !== m.label || !guard
941
+ ? worded
942
+ : `${m.label} [${unless ? "unless" : "when"} ${guard.join(" or ")}]`;
943
+ out.push(`${pad}${rid[i].get(m.from)} --> ${rid[i].get(m.to)} : ${wrap(text, 30)}`);
944
+ if (guard)
945
+ out.push(
946
+ `${pad}%% when ${unless ? "not " : ""}${guard.map((s) => rid[1 - i].get(s)).join(", ")}`,
947
+ );
948
+ count++;
949
+ }
950
+ if (name) out.push(" }");
951
+ }
952
+ out.push(" }");
953
+ for (const i of [0, 1])
954
+ [...rid[i].values()].forEach((id, j) => out.push(` class ${id} ${drawn[i][j].cls}`));
955
+ return { mermaid: out.join("\n") + "\n", count };
956
+ };
957
+
958
+ const all = draw(null);
959
+ const differences = compareCharts(parseMermaid(all.mermaid).regions, flat);
960
+ if (differences.length) {
961
+ const n = differences.reduce((sum, [, xs]) => sum + xs.length, 0);
962
+ return {
963
+ why: `the regions do not combine into the flat chart, with ${n} difference(s), such as ${differences[0][0]}: ${differences[0][1][0]}`,
964
+ };
965
+ }
966
+ if (allPath) fs.writeFileSync(allPath, all.mermaid);
967
+
968
+ const parts = new Map(nodes.map((k) => [bar(split(k)), split(k)]));
969
+ const key = (from, label, to) => `${from} --> ${to} : ${label}`;
970
+ const real = new Set(steps.map((s) => key(bar(s.from), s.label, bar(s.to))));
971
+ const next = new Map();
972
+ for (const s of steps) {
973
+ if (!next.has(bar(s.from))) next.set(bar(s.from), []);
974
+ next.get(bar(s.from)).push(s);
975
+ }
976
+ const keep = [new Set(), new Set()];
977
+ const movesOf = (s) =>
978
+ [0, 1]
979
+ .filter((i) => s.from[i] !== s.to[i])
980
+ .map((i) => [i, [s.from[i], s.label, s.to[i]].join(SEP)]);
981
+ const price = (s) => movesOf(s).filter(([i, id]) => !keep[i].has(id)).length * nodes.length + 1;
982
+ const wanted = [];
983
+ const want = (s) => {
984
+ wanted.push(s);
985
+ for (const [i, id] of movesOf(s)) keep[i].add(id);
986
+ };
987
+ const stepsOf = (e, said) => {
988
+ const labels = [...e.labels].map(norm);
989
+ return (labels.includes(said) ? [said] : labels).map((label) => ({
990
+ from: split(e.from),
991
+ to: split(e.to),
992
+ label,
993
+ }));
994
+ };
995
+ for (const c of classes) {
996
+ for (const step of edgesAlong(c)) {
997
+ const e = edges.get(step.id);
998
+ if (e) stepsOf(e, norm(step.label)).forEach(want);
999
+ }
1000
+ if (diverges(c)) stepsOf(edges.get(c.second.edge), "").forEach(want);
1001
+ }
1002
+ const cheapest = () => {
1003
+ const cost = new Map(starts.map((k) => [bar(split(k)), 0]));
1004
+ const parent = new Map();
1005
+ const open = new Set(cost.keys());
1006
+ while (open.size) {
1007
+ const k = [...open].reduce((a, b) => (cost.get(b) < cost.get(a) ? b : a));
1008
+ open.delete(k);
1009
+ for (const s of next.get(k) ?? []) {
1010
+ const c = cost.get(k) + price(s);
1011
+ if (c < (cost.get(bar(s.to)) ?? Infinity)) {
1012
+ cost.set(bar(s.to), c);
1013
+ parent.set(bar(s.to), s);
1014
+ open.add(bar(s.to));
1015
+ }
1016
+ }
1017
+ }
1018
+ return { cost, parent };
1019
+ };
1020
+ const targets = [
1021
+ ...[0, 1].flatMap((i) =>
1022
+ [...beside[i].keys()].sort().map((r) => ({ at: (k) => parts.get(k)[i] === r })),
1023
+ ),
1024
+ ...[...new Set(steps.map((s) => s.label))]
1025
+ .filter((l) => !wanted.some((s) => s.label === l))
1026
+ .sort()
1027
+ .map((l) => ({ ends: (s) => s.label === l })),
1028
+ ];
1029
+ while (targets.length) {
1030
+ const { cost, parent } = cheapest();
1031
+ let best = null;
1032
+ targets.forEach((t, n) => {
1033
+ const ways = t.at
1034
+ ? [...cost.keys()].filter(t.at).map((k) => ({ k, c: cost.get(k), last: null }))
1035
+ : steps
1036
+ .filter(t.ends)
1037
+ .filter((s) => cost.has(bar(s.from)))
1038
+ .map((s) => ({ k: bar(s.from), c: cost.get(bar(s.from)) + price(s), last: s }));
1039
+ for (const w of ways) if (!best || w.c < best.c) best = { ...w, n };
1040
+ });
1041
+ if (!best) break;
1042
+ targets.splice(best.n, 1);
1043
+ const path = best.last ? [best.last] : [];
1044
+ for (let s = parent.get(best.k); s; s = parent.get(bar(s.from))) path.push(s);
1045
+ path.forEach(want);
1046
+ }
1047
+ const takes = (x, label) =>
1048
+ [0, 1].flatMap((i) =>
1049
+ [...moves[i]]
1050
+ .filter(([, m]) => m.from === x[i] && m.label === label && m.beside.has(x[1 - i]))
1051
+ .map(([id]) => [i, id]),
1052
+ );
1053
+ let scoped;
1054
+ let made;
1055
+ for (;;) {
1056
+ scoped = draw(keep);
1057
+ made = productOf(parseMermaid(scoped.mermaid).regions);
1058
+ const madeKeys = new Set(made.transitions.map((t) => key(t.from, t.label, t.to)));
1059
+ const redo = [
1060
+ ...made.transitions
1061
+ .filter((t) => parts.has(t.from) && !real.has(key(t.from, t.label, t.to)))
1062
+ .map((t) => [parts.get(t.from), t.label]),
1063
+ ...wanted
1064
+ .filter((s) => made.states.has(bar(s.from)))
1065
+ .filter((s) => !madeKeys.has(key(bar(s.from), s.label, bar(s.to))))
1066
+ .map((s) => [s.from, s.label]),
1067
+ ];
1068
+ const before = keep[0].size + keep[1].size;
1069
+ for (const [x, label] of redo) for (const [i, id] of takes(x, label)) keep[i].add(id);
1070
+ if (keep[0].size + keep[1].size === before) break;
1071
+ }
1072
+ const sound =
1073
+ [...made.states].every((s) => parts.has(s)) &&
1074
+ made.transitions.every((t) => real.has(key(t.from, t.label, t.to)));
1075
+ if (!sound) scoped = all;
1076
+
1077
+ return {
1078
+ mermaid: scoped.mermaid,
1079
+ states: [beside[0].size, beside[1].size],
1080
+ transitions: scoped.count,
1081
+ all: all.count,
1082
+ flat: { states: nodes.length, transitions: steps.length },
1083
+ status: Object.fromEntries(
1084
+ rank.map((s) => [
1085
+ s,
1086
+ regionStatus.reduce((n, m) => n + [...m.values()].filter((x) => x === s).length, 0),
1087
+ ]),
1088
+ ),
1089
+ groups: groupCounts(drawn.flat()),
1090
+ };
1091
+ }
1092
+ const regions = args.regions
1093
+ ? regionsChart({
1094
+ states: allNodes,
1095
+ starts: [...initial],
1096
+ edges,
1097
+ classes,
1098
+ proofPath: args.proof,
1099
+ allPath: args.regionsAll,
1100
+ })
1101
+ : null;
1102
+ if (regions?.why) console.error(`regions: not drawn, because ${regions.why}`);
1103
+
1104
+ const flatStates =
1105
+ args.pathOnly && chosen
1106
+ ? onPath
1107
+ : coverEdges
1108
+ ? new Set([
1109
+ ...initial,
1110
+ ...[...coverEdges].flatMap((id) => [edges.get(id).from, edges.get(id).to]),
1111
+ ])
1112
+ : allNodes;
1113
+ const sorted = [...flatStates].sort();
1114
+ const drawn = sorted.map((k) => drawnState(groupOf(k), status(k), onPath.has(k)));
1115
+
1116
+ const boxes = new Map();
1117
+ const boxOf = new Map();
1118
+ const inBox = (k, box) => {
1119
+ if (!boxes.has(box)) boxes.set(box, []);
1120
+ boxes.get(box).push(k);
1121
+ };
1122
+ if (args.view === "layered") for (const k of sorted) inBox(k, k.split(SEP)[0]);
1123
+ else if (args.view === "customer" && !args.flat) {
1124
+ for (const k of sorted) {
1125
+ const at = k.indexOf(": ");
1126
+ if (at > 0 && at + 2 < k.length) inBox(k, k.slice(0, at));
1127
+ }
1128
+ for (const [box, members] of boxes) if (members.length < 2) boxes.delete(box);
1129
+ }
1130
+ const boxIds = new Map([...boxes.keys()].map((box, i) => [box, `g${i}`]));
1131
+ for (const [box, members] of boxes) for (const k of members) boxOf.set(k, box);
1132
+ const declared = [...[...boxes.values()].flat(), ...sorted.filter((k) => !boxOf.has(k))];
1133
+ const stateIds = new Map(declared.map((k, i) => [k, `s${i}`]));
1134
+ const idOf = (k) => stateIds.get(k);
1135
+ const caption = (k) =>
1136
+ args.view === "layered" ? k.split(SEP)[1] : boxOf.has(k) ? k.slice(boxOf.get(k).length + 2) : k;
1137
+
1138
+ function numbered(steps) {
1139
+ const runs = [];
1140
+ for (const { n, shown } of steps) {
1141
+ const last = runs.at(-1);
1142
+ if (last?.shown === shown && last.n.at(-1) === n - 1) last.n.push(n);
1143
+ else runs.push({ shown, n: [n] });
1144
+ }
1145
+ return runs.map(({ shown, n }) => `${n.join(", ")}. ${shown}`);
1146
+ }
1147
+ const drawnLabels = new Set();
1148
+ const arrows = new Map();
1149
+ for (const [id, e] of edges) {
1150
+ if (!flatStates.has(e.from) || !flatStates.has(e.to)) continue;
1151
+ if (args.pathOnly && chosen && !pathNumbers.has(id) && id !== chosen.second?.edge) continue;
1152
+ if (coverEdges && !coverEdges.has(id)) continue;
1153
+ const pair = edgeId(e.from, e.to);
1154
+ if (!arrows.has(pair))
1155
+ arrows.set(pair, { from: e.from, to: e.to, groups: [[], [], []], plain: true, labels: [] });
1156
+ const a = arrows.get(pair);
1157
+ const [numbers, current, other] = a.groups;
1158
+ const all = [...e.labels];
1159
+ const shown = all.map((l) => guarded(e.guards, l));
1160
+ const said = args.fullLabels ? shown.join(" or ") : joinLabels(shown);
1161
+ const p = pathNumbers.get(id);
1162
+ const chosenStep = chosen?.second && id === chosen.second.edge;
1163
+ if (p) numbers.push(...numbered(p));
1164
+ else if (!e.secondOnly) current.push(said);
1165
+ else other.push(args.pathOnly && chosenStep ? chosen.second.text : said);
1166
+ if (chosenStep && !e.secondOnly) other.push(chosen.second.text);
1167
+ if (p || chosenStep || id.endsWith(`${SEP}second`)) a.plain = false;
1168
+ a.labels.push(...all);
1169
+ for (const l of p ? p.map((s) => s.label) : all) drawnLabels.add(l);
1170
+ }
1171
+ const transitions = [...arrows.values()].map(({ from, to, groups, plain, labels }) => ({
1172
+ from,
1173
+ to,
1174
+ // ";" ends a Mermaid statement, even inside a transition label.
1175
+ text: groups
1176
+ .map((g) =>
1177
+ g
1178
+ .filter(Boolean)
1179
+ .map((l) => wrap(l.replaceAll(";", ","), 30))
1180
+ .join("<br/>"),
1181
+ )
1182
+ .filter(Boolean)
1183
+ .join("<br/>#160;<br/>"),
1184
+ plain,
1185
+ labels: labels.sort(),
1186
+ }));
1187
+ const fromBox = [];
1188
+ const folded = new Set();
1189
+ for (const [box, members] of boxes) {
1190
+ const mine = new Set(members);
1191
+ const byStep = new Map();
1192
+ for (const l of transitions) {
1193
+ if (!l.plain || !mine.has(l.from) || mine.has(l.to)) continue;
1194
+ const step = `${l.to}${SEP}${l.text}${SEP}${l.labels.join(SEP)}`;
1195
+ if (!byStep.has(step)) byStep.set(step, []);
1196
+ byStep.get(step).push(l);
1197
+ }
1198
+ for (const same of byStep.values()) {
1199
+ if (new Set(same.map((l) => l.from)).size !== members.length) continue;
1200
+ for (const l of same) folded.add(l);
1201
+ fromBox.push({ ...same[0], box });
1202
+ }
1203
+ }
1204
+
1205
+ const out = head(drawn);
1206
+ for (const [box, members] of boxes) {
1207
+ out.push(` state "${quote(wrap(box))}" as ${boxIds.get(box)} {`);
1208
+ for (const k of members) out.push(` state "${quote(wrap(caption(k)))}" as ${idOf(k)}`);
1209
+ out.push(" }");
1210
+ }
1211
+ for (const k of sorted)
1212
+ if (!boxOf.has(k)) out.push(` state "${quote(wrap(caption(k)))}" as ${idOf(k)}`);
1213
+ for (const k of sorted)
1214
+ if (initial.has(k) && (!args.pathOnly || k === chosen?.path[0].key))
1215
+ out.push(` [*] --> ${idOf(k)}`);
1216
+ const arrow = (from, l) => ` ${from} --> ${idOf(l.to)}${l.text ? ` : ${l.text}` : ""}`;
1217
+ for (const l of fromBox) out.push(arrow(boxIds.get(l.box), l));
1218
+ for (const l of transitions) if (!folded.has(l)) out.push(arrow(idOf(l.from), l));
1219
+ sorted.forEach((k, i) => out.push(` class ${idOf(k)} ${drawn[i].cls}`));
1220
+ const mermaid = regions?.mermaid ?? out.join("\n") + "\n";
1221
+ if (args.out) fs.writeFileSync(args.out, mermaid);
1222
+ else process.stdout.write(mermaid);
1223
+
1224
+ const show = (k) => (args.view === "layered" ? k.replace(SEP, ": ") : k);
1225
+ const ruleOf = (c) => rules.get(c.invariant) ?? `\`${c.invariant}\``;
1226
+ function secondLine(c) {
1227
+ const W = cap(within);
1228
+ if (c.introduced) return `Only ${L} has this bug. The steps above happen ${within} ${L}.`;
1229
+ const s = c.second;
1230
+ if (!s) return null;
1231
+ if (s.sameEnd)
1232
+ return s.ruleHolds
1233
+ ? `${W} ${L} you see the same screens and steps, yet the rule holds, so the state labels do not show what ${L} changes.`
1234
+ : `${W} ${L} you see the same screens and steps, so ${L} does not change this path.`;
1235
+ if (s.reason) return `${cap(L)} cannot take these steps: it ${s.reason}.`;
1236
+ const outcome = s.disabled
1237
+ ? "cannot happen, so you stay at"
1238
+ : s.key === s.from
1239
+ ? "keeps you at"
1240
+ : "takes you to";
1241
+ if (s.hidden) {
1242
+ const when = s.step ? `after step ${s.step}` : "before your first step";
1243
+ return `${W} ${L}, ${when}, "${s.said}" happens and ${outcome}: ${show(s.key)}.`;
1244
+ }
1245
+ return `${W} ${L}, step ${s.step} (${s.said}) ${outcome}: ${show(s.key)}.`;
1246
+ }
1247
+ if (args.story) {
1248
+ const lines = [];
1249
+ const header = role === "fix" ? `New with ${L}: ` : `Only in ${L}: `;
1250
+ classes.forEach((c, i) => {
1251
+ lines.push(`## ${i + 1}. ${c.introduced ? header : ""}${show(c.end)}`, "");
1252
+ lines.push(`Rule broken: ${ruleOf(c)}`);
1253
+ if (c.cause) lines.push(`Why: ${c.cause.replace(/\.$/, "")}.`);
1254
+ lines.push(
1255
+ c.count === 1
1256
+ ? "This is the only way to get here in the model."
1257
+ : `There are ${c.count} ways to get here in the model. This is the shortest.`,
1258
+ "",
1259
+ );
1260
+ c.path.forEach((p, j) =>
1261
+ lines.push(
1262
+ j === 0 ? `0. Start: ${show(p.key)}` : `${j}. ${cap(p.label)}. Now: ${show(p.key)}`,
1263
+ ),
1264
+ );
1265
+ lines.push("");
1266
+ const said = secondLine(c);
1267
+ if (said) lines.push(said, "");
1268
+ });
1269
+ fs.writeFileSync(args.story, lines.join("\n"));
1270
+ }
1271
+ const CODE_LIKE = /\b[a-z]{2,}[A-Z][a-z]|\b\w+_\w+\b|\b[A-Za-z_]\w*\(|\b[A-Z][a-z]+[A-Z][a-z]/;
1272
+ const codeLike = [
1273
+ ...new Set([
1274
+ ...(args.view === "customer" ? [...sorted, ...drawnLabels] : []),
1275
+ ...classes.map((c) => c.cause).filter(Boolean),
1276
+ ]),
1277
+ ].filter((t) => CODE_LIKE.test(t));
1278
+ const secondOf = (c) =>
1279
+ c.introduced
1280
+ ? role === "fix"
1281
+ ? `new with ${L}`
1282
+ : `only in ${L}`
1283
+ : c.second
1284
+ ? c.second.sameEnd
1285
+ ? c.second.ruleHolds
1286
+ ? "same screens, but the rule holds"
1287
+ : "unchanged"
1288
+ : (c.second.reason ?? show(c.second.key))
1289
+ : null;
1290
+ const summary = {
1291
+ view: args.view,
1292
+ ...(role ? { second: { role, label: L, lacks: lacks.map((p) => p.join(".")) } } : {}),
1293
+ modelStates: graph.nodes.size,
1294
+ modelTransitions: graph.edges.length,
1295
+ chartStates: regions?.mermaid ? regions.states[0] + regions.states[1] : flatStates.size,
1296
+ chartTransitions: regions?.mermaid ? regions.transitions : transitions.length,
1297
+ chartStatus: regions?.mermaid
1298
+ ? regions.status
1299
+ : Object.fromEntries(
1300
+ ["works", "wrong", "fixed"].map((s) => [
1301
+ s,
1302
+ [...flatStates].filter((k) => status(k) === s).length,
1303
+ ]),
1304
+ ),
1305
+ ...(grouping
1306
+ ? {
1307
+ chartGroups: regions?.mermaid ? regions.groups : groupCounts(drawn),
1308
+ mixedGroups: mixedGroups.map(show).sort(),
1309
+ }
1310
+ : {}),
1311
+ ...(regions
1312
+ ? {
1313
+ regions: regions.mermaid
1314
+ ? {
1315
+ drawn: true,
1316
+ states: regions.states,
1317
+ transitions: regions.transitions,
1318
+ all: regions.all,
1319
+ flat: regions.flat,
1320
+ }
1321
+ : { drawn: false, why: regions.why },
1322
+ }
1323
+ : {}),
1324
+ classes: classes.map((c, i) => ({
1325
+ n: i + 1,
1326
+ end: show(c.end),
1327
+ invariant: c.invariant,
1328
+ rule: rules.get(c.invariant) ?? null,
1329
+ cause: c.cause,
1330
+ readerSteps: c.path.length - 1,
1331
+ modelSteps: c.trace.steps.length,
1332
+ counterexamples: c.count,
1333
+ [role ?? "fix"]: secondOf(c),
1334
+ })),
1335
+ codeLike,
1336
+ };
1337
+ if (args.summary) fs.writeFileSync(args.summary, JSON.stringify(summary, null, 2) + "\n");
1338
+ const extraBugs = introduced.length
1339
+ ? `, ${introduced.length} ${role === "fix" ? `new with ${L}` : `only in ${L}`}`
1340
+ : "";
1341
+ const drawnAs = regions?.mermaid
1342
+ ? ` of ${regions.all} in 2 regions (proved against ${regions.flat.states} states, ${regions.flat.transitions} transitions)`
1343
+ : "";
1344
+ console.error(
1345
+ `chart: ${summary.chartStates} states, ${summary.chartTransitions} transitions${drawnAs} from ${summary.modelStates} model states; ` +
1346
+ `${mainClasses.length} bug(s)${extraBugs}`,
1347
+ );