agentic-mermaid 0.1.1 → 0.2.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 (340) hide show
  1. package/AGENT_NATIVE.md +155 -91
  2. package/CHANGELOG.md +163 -4
  3. package/Instructions_for_agents.md +10 -10
  4. package/LICENSES/Apache-2.0.txt +202 -0
  5. package/README.md +31 -38
  6. package/THIRD_PARTY_NOTICES.md +29 -0
  7. package/assets/hero.png +0 -0
  8. package/dist/agent-core.d.ts +463 -0
  9. package/dist/agent-core.js +397 -0
  10. package/dist/agent.d.ts +73 -1868
  11. package/dist/agent.js +336 -37325
  12. package/dist/agentic-mermaid-mcp.js +7 -38399
  13. package/dist/am.js +33626 -40446
  14. package/dist/chunk-2P5FKUQC.js +3485 -0
  15. package/dist/chunk-2S3VSS6I.js +149 -0
  16. package/dist/chunk-2SILJDW2.js +37 -0
  17. package/dist/chunk-BJYMBT4I.js +61963 -0
  18. package/dist/chunk-FDM43XU7.js +615 -0
  19. package/dist/chunk-MYPDVZCO.js +4702 -0
  20. package/dist/index-DWPa2MJs.d.ts +6780 -0
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +310 -33014
  23. package/dist/mcp-cli-5J47WEY3.js +17 -0
  24. package/docs/README.md +39 -31
  25. package/docs/agent-api-cookbook.md +44 -35
  26. package/docs/agent-mutation-policy.md +1 -1
  27. package/docs/agent-workflow-examples.md +2 -2
  28. package/docs/api.md +284 -77
  29. package/docs/ascii.md +26 -16
  30. package/docs/code-mode-impact.md +214 -0
  31. package/docs/comparison.md +23 -11
  32. package/docs/computational-aesthetics-prototype-plan.md +252 -0
  33. package/docs/config.md +26 -10
  34. package/docs/custom-fonts.md +265 -0
  35. package/docs/custom-style-cookbook.md +130 -9
  36. package/docs/diagram-families.md +48 -23
  37. package/docs/features.md +59 -52
  38. package/docs/fork-differences.md +6 -3
  39. package/docs/getting-started.md +23 -16
  40. package/docs/issue-derived-test-cases.md +29 -22
  41. package/docs/mcp-code-mode-rationale.md +13 -6
  42. package/docs/mcp-http-transport.md +20 -13
  43. package/docs/mermaid-layout-complaints.md +66 -52
  44. package/docs/mutation-testing.md +145 -66
  45. package/docs/quality.md +23 -14
  46. package/docs/react.md +2 -2
  47. package/docs/schemas/style-spec.schema.json +1221 -30
  48. package/docs/style-authoring.md +241 -38
  49. package/docs/svg-semantic-contract.md +49 -0
  50. package/docs/testing-strategy.md +166 -58
  51. package/docs/theming.md +19 -32
  52. package/examples/styles/catalog.json +54 -0
  53. package/examples/styles/cloudflare-workers-inspired-prototype.mmd +9 -0
  54. package/examples/styles/cloudflare-workers-inspired-prototype.style.json +110 -0
  55. package/examples/styles/cupertino-prototype.mmd +10 -0
  56. package/examples/styles/cupertino-prototype.style.json +63 -0
  57. package/examples/styles/mid-century-report.style.json +2 -2
  58. package/examples/styles/star-chart-atlas.style.json +2 -1
  59. package/examples/styles/transit-route-map.style.json +2 -2
  60. package/examples/styles/vercel-inspired-prototype.mmd +6 -0
  61. package/examples/styles/vercel-inspired-prototype.style.json +87 -0
  62. package/package.json +85 -45
  63. package/server.json +2 -2
  64. package/skills/agentic-mermaid-diagram-workflow/SKILL.md +17 -36
  65. package/skills/agentic-mermaid-diagram-workflow/references/cli.md +4 -5
  66. package/skills/agentic-mermaid-diagram-workflow/references/code-mode.md +15 -31
  67. package/skills/agentic-mermaid-diagram-workflow/references/sequence.md +10 -14
  68. package/skills/agentic-mermaid-live-editor/SKILL.md +100 -206
  69. package/TODO.md +0 -684
  70. package/dist/index-wjRjbsP-.d.ts +0 -1102
  71. package/docs/assets/improvements/box-start-connector.png +0 -0
  72. package/docs/assets/improvements/comment-dropped-lint.png +0 -0
  73. package/docs/assets/improvements/er-content-loss.png +0 -0
  74. package/docs/assets/improvements/fan-in-trunks.png +0 -0
  75. package/docs/assets/improvements/gantt-family.png +0 -0
  76. package/docs/assets/improvements/gantt-milestone-clamp.png +0 -0
  77. package/docs/assets/improvements/gantt-verify-seam.png +0 -0
  78. package/docs/assets/improvements/subgraph-container-edges.png +0 -0
  79. package/docs/assets/improvements/wrapper-frontmatter.png +0 -0
  80. package/docs/assets/improvements/wrapper-init-directive.png +0 -0
  81. package/docs/assets/improvements/wrapper-leading-comment.png +0 -0
  82. package/docs/assets/style-cookbook/mid-century-report.png +0 -0
  83. package/docs/assets/style-cookbook/star-chart-atlas.png +0 -0
  84. package/docs/assets/style-cookbook/transit-route-map.png +0 -0
  85. package/docs/contributing/adding-diagram-types.md +0 -144
  86. package/docs/contributing/diagram-family-citizenship.matrix.json +0 -2127
  87. package/docs/contributing/diagram-family-citizenship.md +0 -82
  88. package/docs/contributing/harvesting-upstream-tests.md +0 -126
  89. package/docs/contributing/lessons-learned.md +0 -276
  90. package/docs/contributing/releasing.md +0 -82
  91. package/docs/contributing/visual-review-evidence.md +0 -53
  92. package/docs/design/families/README.md +0 -26
  93. package/docs/design/families/architecture-beta.md +0 -48
  94. package/docs/design/families/flowchart-parser-conformance.md +0 -22
  95. package/docs/design/families/gantt-research.md +0 -137
  96. package/docs/design/families/gantt.md +0 -329
  97. package/docs/design/families/journey-agentic-cards-before.png +0 -0
  98. package/docs/design/families/journey-experience-curve-implementation.png +0 -0
  99. package/docs/design/families/journey-mermaid-classic-implementation.png +0 -0
  100. package/docs/design/families/journey-mermaid-classic-prototype.png +0 -0
  101. package/docs/design/families/journey-mermaid-classic-prototype.svg +0 -163
  102. package/docs/design/families/journey-mermaid-classic-prototype.svg.png +0 -0
  103. package/docs/design/families/journey-migration-parity.md +0 -439
  104. package/docs/design/families/journey-section-overlap-after.png +0 -0
  105. package/docs/design/families/journey-section-overlap-before.png +0 -0
  106. package/docs/design/families/journey-usage-research.md +0 -423
  107. package/docs/design/families/journey.md +0 -153
  108. package/docs/design/families/xychart.md +0 -85
  109. package/docs/design/system/README.md +0 -45
  110. package/docs/design/system/abstraction-audit.md +0 -224
  111. package/docs/design/system/abstraction-recommendations.md +0 -655
  112. package/docs/design/system/architecture.mmd +0 -29
  113. package/docs/design/system/architecture.svg +0 -137
  114. package/docs/design/system/layout-guarantees-and-robustness.md +0 -351
  115. package/docs/design/system/layout-rubric.md +0 -138
  116. package/docs/design/system/route-contracts.md +0 -848
  117. package/docs/design/system/source-preservation-ladder.md +0 -52
  118. package/docs/design/system/styles-rollout.md +0 -216
  119. package/docs/design/system/ugly-layouts.md +0 -119
  120. package/docs/layout-characterization/README.md +0 -245
  121. package/docs/layout-characterization/contact-sheet-families.md +0 -498
  122. package/docs/layout-characterization/contact-sheet.md +0 -638
  123. package/docs/layout-characterization/properties.md +0 -281
  124. package/docs/layout-characterization/visual-quality.md +0 -160
  125. package/docs/layout-characterization/visual-snapshots/architecture.svg +0 -79
  126. package/docs/layout-characterization/visual-snapshots/class.svg +0 -60
  127. package/docs/layout-characterization/visual-snapshots/er.svg +0 -61
  128. package/docs/layout-characterization/visual-snapshots/flowchart.svg +0 -58
  129. package/docs/layout-characterization/visual-snapshots/gantt.svg +0 -52
  130. package/docs/layout-characterization/visual-snapshots/journey.svg +0 -98
  131. package/docs/layout-characterization/visual-snapshots/pie.svg +0 -36
  132. package/docs/layout-characterization/visual-snapshots/quadrant.svg +0 -47
  133. package/docs/layout-characterization/visual-snapshots/sequence.svg +0 -59
  134. package/docs/layout-characterization/visual-snapshots/state.svg +0 -52
  135. package/docs/layout-characterization/visual-snapshots/timeline.svg +0 -64
  136. package/docs/layout-characterization/visual-snapshots/xychart.svg +0 -97
  137. package/docs/project/divergences.md +0 -98
  138. package/docs/project/dogfooding-docs-strategy.md +0 -66
  139. package/docs/project/hosted-mcp-cloudflare-plan.md +0 -338
  140. package/docs/project/lessons-learned.md +0 -1019
  141. package/docs/project/remove-role-styling-plan.md +0 -176
  142. package/examples/agent-improve-auth-flow.ts +0 -206
  143. package/examples/agent-loop.ts +0 -53
  144. package/examples/mcp-vs-cli-complex-diagrams.ts +0 -133
  145. package/llms.txt +0 -104
  146. package/skill-evals/README.md +0 -77
  147. package/skill-evals/fixtures/architecture-cache/input.mmd +0 -5
  148. package/skill-evals/fixtures/class-payment/input.mmd +0 -4
  149. package/skill-evals/fixtures/er-orders/input.mmd +0 -10
  150. package/skill-evals/fixtures/flowchart-cache/input.mmd +0 -3
  151. package/skill-evals/fixtures/gantt-release/input.mmd +0 -7
  152. package/skill-evals/fixtures/journey-checkout/input.mmd +0 -6
  153. package/skill-evals/fixtures/pie-exports/input.mmd +0 -5
  154. package/skill-evals/fixtures/quadrant-priorities/input.mmd +0 -11
  155. package/skill-evals/fixtures/sequence-retry/input.mmd +0 -8
  156. package/skill-evals/fixtures/state-lifecycle/input.mmd +0 -5
  157. package/skill-evals/fixtures/timeline-release/input.mmd +0 -6
  158. package/skill-evals/fixtures/xychart-growth/input.mmd +0 -5
  159. package/skill-evals/shared-benchmark.json +0 -510
  160. package/src/agent/analyze.ts +0 -157
  161. package/src/agent/apply.ts +0 -146
  162. package/src/agent/architecture-body.ts +0 -458
  163. package/src/agent/certificates.ts +0 -110
  164. package/src/agent/class-body.ts +0 -319
  165. package/src/agent/core.ts +0 -207
  166. package/src/agent/create.ts +0 -106
  167. package/src/agent/describe.ts +0 -368
  168. package/src/agent/er-body.ts +0 -224
  169. package/src/agent/facts.ts +0 -360
  170. package/src/agent/families-builtin.ts +0 -920
  171. package/src/agent/families.ts +0 -232
  172. package/src/agent/family-layouts.ts +0 -832
  173. package/src/agent/flowchart-body.ts +0 -372
  174. package/src/agent/flowchart-unsupported.ts +0 -243
  175. package/src/agent/font-coverage.ts +0 -288
  176. package/src/agent/gantt-body.ts +0 -495
  177. package/src/agent/index.ts +0 -10
  178. package/src/agent/journey-body.ts +0 -374
  179. package/src/agent/label-metrics.ts +0 -28
  180. package/src/agent/layout-to-rendered.ts +0 -63
  181. package/src/agent/mutate.ts +0 -97
  182. package/src/agent/mutation-ops.ts +0 -51
  183. package/src/agent/op-schema.ts +0 -443
  184. package/src/agent/parse.ts +0 -223
  185. package/src/agent/pie-body.ts +0 -270
  186. package/src/agent/png.ts +0 -171
  187. package/src/agent/quadrant-body.ts +0 -336
  188. package/src/agent/quality.ts +0 -363
  189. package/src/agent/readability-audit.ts +0 -92
  190. package/src/agent/rendered-route-audit.ts +0 -61
  191. package/src/agent/sequence-body.ts +0 -345
  192. package/src/agent/serialize.ts +0 -214
  193. package/src/agent/state-body.ts +0 -589
  194. package/src/agent/structural-count.ts +0 -150
  195. package/src/agent/timeline-body.ts +0 -280
  196. package/src/agent/trace-log.ts +0 -43
  197. package/src/agent/types.ts +0 -995
  198. package/src/agent/verify.ts +0 -603
  199. package/src/agent/xychart-body.ts +0 -472
  200. package/src/architecture/config.ts +0 -282
  201. package/src/architecture/layout.ts +0 -496
  202. package/src/architecture/parser.ts +0 -375
  203. package/src/architecture/renderer.ts +0 -611
  204. package/src/architecture/types.ts +0 -113
  205. package/src/ascii/ansi.ts +0 -423
  206. package/src/ascii/architecture.ts +0 -210
  207. package/src/ascii/canvas.ts +0 -443
  208. package/src/ascii/class-diagram.ts +0 -697
  209. package/src/ascii/converter.ts +0 -475
  210. package/src/ascii/draw.ts +0 -1693
  211. package/src/ascii/edge-bundling.ts +0 -328
  212. package/src/ascii/edge-routing.ts +0 -330
  213. package/src/ascii/er-diagram.ts +0 -439
  214. package/src/ascii/gantt.ts +0 -276
  215. package/src/ascii/grid.ts +0 -747
  216. package/src/ascii/index.ts +0 -196
  217. package/src/ascii/journey.ts +0 -169
  218. package/src/ascii/meta.ts +0 -244
  219. package/src/ascii/multiline-utils.ts +0 -80
  220. package/src/ascii/pathfinder.ts +0 -301
  221. package/src/ascii/pie.ts +0 -67
  222. package/src/ascii/quadrant.ts +0 -158
  223. package/src/ascii/reverse.ts +0 -216
  224. package/src/ascii/sequence.ts +0 -499
  225. package/src/ascii/shapes/circle.ts +0 -27
  226. package/src/ascii/shapes/corners.ts +0 -143
  227. package/src/ascii/shapes/diamond.ts +0 -27
  228. package/src/ascii/shapes/hexagon.ts +0 -27
  229. package/src/ascii/shapes/index.ts +0 -106
  230. package/src/ascii/shapes/rectangle.ts +0 -168
  231. package/src/ascii/shapes/rounded.ts +0 -27
  232. package/src/ascii/shapes/special.ts +0 -332
  233. package/src/ascii/shapes/stadium.ts +0 -110
  234. package/src/ascii/shapes/state.ts +0 -192
  235. package/src/ascii/shapes/types.ts +0 -73
  236. package/src/ascii/timeline.ts +0 -141
  237. package/src/ascii/types.ts +0 -312
  238. package/src/ascii/validate.ts +0 -120
  239. package/src/ascii/width.ts +0 -81
  240. package/src/ascii/wrap.ts +0 -131
  241. package/src/ascii/xychart.ts +0 -919
  242. package/src/browser.ts +0 -29
  243. package/src/class/layout.ts +0 -240
  244. package/src/class/parser.ts +0 -332
  245. package/src/class/renderer.ts +0 -638
  246. package/src/class/types.ts +0 -129
  247. package/src/cli/agent-instructions.ts +0 -1
  248. package/src/cli/am-bin.ts +0 -6
  249. package/src/cli/exit-codes.ts +0 -18
  250. package/src/cli/index.ts +0 -1132
  251. package/src/cli/init-agent.ts +0 -147
  252. package/src/cli/run-entrypoint.ts +0 -10
  253. package/src/color-resolver.ts +0 -152
  254. package/src/elk-instance.ts +0 -114
  255. package/src/er/layout.ts +0 -187
  256. package/src/er/parser.ts +0 -226
  257. package/src/er/renderer.ts +0 -792
  258. package/src/er/types.ts +0 -99
  259. package/src/family-rubric.ts +0 -425
  260. package/src/font-manifest.ts +0 -31
  261. package/src/gantt/layout.ts +0 -365
  262. package/src/gantt/parser.ts +0 -282
  263. package/src/gantt/pipeline.ts +0 -27
  264. package/src/gantt/renderer.ts +0 -414
  265. package/src/gantt/schedule.ts +0 -513
  266. package/src/gantt/types.ts +0 -255
  267. package/src/index.ts +0 -368
  268. package/src/journey/layout.ts +0 -554
  269. package/src/journey/parse-core.ts +0 -251
  270. package/src/journey/parser.ts +0 -101
  271. package/src/journey/renderer.ts +0 -917
  272. package/src/journey/types.ts +0 -144
  273. package/src/layout/geometry.ts +0 -182
  274. package/src/layout/pass.ts +0 -142
  275. package/src/layout/passes/index.ts +0 -3916
  276. package/src/layout-engine.ts +0 -1565
  277. package/src/layout-rubric.ts +0 -659
  278. package/src/mcp/artifacts.ts +0 -137
  279. package/src/mcp/dynamic-harness.ts +0 -31
  280. package/src/mcp/dynamic-modules.d.ts +0 -7
  281. package/src/mcp/facade.ts +0 -608
  282. package/src/mcp/harness-runtime.ts +0 -203
  283. package/src/mcp/hosted-server.ts +0 -468
  284. package/src/mcp/mcp-bin.ts +0 -3
  285. package/src/mcp/mcp-cli.ts +0 -82
  286. package/src/mcp/protocol.ts +0 -13
  287. package/src/mcp/sandbox.ts +0 -195
  288. package/src/mcp/sdk-decl.ts +0 -391
  289. package/src/mcp/server.ts +0 -362
  290. package/src/mcp/tool-surface.ts +0 -146
  291. package/src/mermaid-source.ts +0 -655
  292. package/src/multiline-utils.ts +0 -228
  293. package/src/parser.ts +0 -1031
  294. package/src/pie/layout.ts +0 -168
  295. package/src/pie/parser.ts +0 -136
  296. package/src/pie/renderer.ts +0 -234
  297. package/src/pie/types.ts +0 -82
  298. package/src/quadrant/layout.ts +0 -191
  299. package/src/quadrant/parser.ts +0 -183
  300. package/src/quadrant/renderer.ts +0 -322
  301. package/src/quadrant/types.ts +0 -113
  302. package/src/render-family-hooks.ts +0 -244
  303. package/src/renderer.ts +0 -1099
  304. package/src/route-contracts.ts +0 -2723
  305. package/src/scene/backend.ts +0 -76
  306. package/src/scene/fidelity.ts +0 -183
  307. package/src/scene/hybrid-backend.ts +0 -174
  308. package/src/scene/ir.ts +0 -181
  309. package/src/scene/marks.ts +0 -127
  310. package/src/scene/rough-backend.ts +0 -378
  311. package/src/scene/seed.ts +0 -46
  312. package/src/scene/style-registry.ts +0 -556
  313. package/src/sequence/layout.ts +0 -456
  314. package/src/sequence/parser.ts +0 -255
  315. package/src/sequence/renderer.ts +0 -604
  316. package/src/sequence/types.ts +0 -154
  317. package/src/shape-clipping.ts +0 -351
  318. package/src/shared/accessibility-directives.ts +0 -22
  319. package/src/shared/batched.ts +0 -61
  320. package/src/shared/color-math.ts +0 -93
  321. package/src/shared/svg-a11y.ts +0 -29
  322. package/src/shared/syntax-error.ts +0 -17
  323. package/src/shared/unicode-ranges.ts +0 -135
  324. package/src/styles.ts +0 -318
  325. package/src/svg-paths.ts +0 -28
  326. package/src/text-metrics.ts +0 -248
  327. package/src/theme.ts +0 -710
  328. package/src/timeline/layout.ts +0 -381
  329. package/src/timeline/parser.ts +0 -215
  330. package/src/timeline/renderer.ts +0 -538
  331. package/src/timeline/types.ts +0 -105
  332. package/src/types.ts +0 -462
  333. package/src/website-worker-modules.d.ts +0 -18
  334. package/src/xychart/axis-utils.ts +0 -109
  335. package/src/xychart/colors.ts +0 -124
  336. package/src/xychart/config.ts +0 -88
  337. package/src/xychart/layout.ts +0 -656
  338. package/src/xychart/parser.ts +0 -504
  339. package/src/xychart/renderer.ts +0 -640
  340. package/src/xychart/types.ts +0 -282
@@ -1,1019 +0,0 @@
1
- # Lessons Learned — Loops 1 through 22
2
-
3
- This document replaces the Loop 1 retrospective. It is the cumulative
4
- narrative across the agentic-mermaid fork. Each section reflects what a
5
- critic or implementer wished they had known when they started.
6
-
7
- > **Scope.** This is the long-form fork narrative. For dated, incident-tagged
8
- > contributor process lessons ("add new at the top"), see
9
- > [`../contributing/lessons-learned.md`](../contributing/lessons-learned.md).
10
-
11
- ## (a) What we wish we'd known
12
-
13
- When we started this fork we built a class diagram parser, then an ER
14
- parser, then a class-body mutation surface, then an ER-body mutation
15
- surface. Loop 6 finally did the ecosystem survey we should have done in
16
- Loop 1 and discovered that `mermaid-ast` already existed — a community
17
- package that parses a much larger surface than ours and would have made
18
- the structured-uplift path much shorter. The same survey turned up
19
- `nereid`, `mermaid-skill`, `mermaid-fixer`, and a half-dozen smaller
20
- projects. None of them is a strict superset of what we built; several
21
- overlap. The right move at Loop 1 would have been: survey first, then
22
- build.
23
-
24
- We also wish we'd known the determinism property of ELK was a property of
25
- its configuration, not its API. We invested in a `withSeededRandom` /
26
- `LayoutContext.rng` wrapper that did nothing — seed 1 and seed 999999
27
- produced byte-identical output because ELK never read `Math.random` on
28
- our path. That apparatus shipped in v3 and was deleted in v4 once we
29
- realized. A short empirical probe in Loop 1 would have saved a Loop 3.
30
-
31
- ## (b) Our differentiators vs the ecosystem
32
-
33
- After the survey, the position we hold that no other package holds is
34
- the *combination* of:
35
-
36
- - **Structured-or-opaque rule.** Every built-in renderable family has a
37
- structured body for its modeled syntax, and opaque/source-preserved
38
- fallback when a construct is unmodeled. We never silently drop a construct.
39
- - **Tiered verification.** Tier 1 (structural — reliable, universal),
40
- Tier 2 (geometric — flowchart-shaped), and Tier 3 (lint — family-specific
41
- common agent mistakes). Tier 1 is gated; Tier 2 and Tier 3 are advisory.
42
- - **Determinism, cross-process and cross-runtime.** Three separate bun
43
- processes produce byte-identical layout JSON; bun and node produce
44
- byte-identical layout JSON on the same source. Both tests run on every
45
- PR.
46
- - **Corpus gates.** The 271-entry mermaid-js docs corpus and the
47
- 132-case MermaidSeqBench both run as PR gates; regressions break the
48
- build with a named diff.
49
- - **Agent-contract verbs.** `am capabilities --json` (Loop 7) lets an
50
- agent introspect what the SDK can do without trial-and-error; `am batch
51
- --jsonl` (Loop 7) bulk-runs ops without the per-call subprocess overhead.
52
- - **Code Mode MCP.** One `execute` tool, typed `mermaid.*` SDK in scope,
53
- sandboxed via `node:vm`. The agent writes the algorithm; we don't grow
54
- the verb set.
55
-
56
- Mermaid-ast ships structured parsing for more families than we do; it
57
- does not ship verify, mutate, capabilities, batch, or Code Mode. Nereid
58
- ships a renderer; it doesn't ship parse-and-mutate. The
59
- structured-or-opaque rule + cross-runtime determinism + agent verbs is
60
- the differentiator stack.
61
-
62
- ## (c) What structured-or-opaque taught us
63
-
64
- The rule "never silently drop a construct" has been load-bearing in ways
65
- we didn't anticipate. When a sequence diagram has `alt`/`loop`/`opt`
66
- blocks that the structured sequence body doesn't model, we fall back to
67
- opaque. The agent can still verify, render, and round-trip — it just
68
- can't call structured mutation ops on that diagram. The type system
69
- rejects the call statically. The user experience is "you can intentionally
70
- edit the preserved opaque body.source and re-parse/verify," not "your diagram broke."
71
-
72
- The opposite rule (parse what you can, throw away the rest) would have
73
- been simpler to implement and would have been catastrophic in practice.
74
- A repo with N sequence diagrams, of which 30% use `alt` blocks, would
75
- have silently lost those blocks on every round-trip. We learned in Loop
76
- 2 that byte-fidelity round-trip is not a "nice to have" — it is the
77
- contract that makes structured editing safe for agents.
78
-
79
- A second learning: a narrower IR enables more confident mutation. We
80
- intentionally kept `FlowchartGraph` and `StateGraph` small. The mutation
81
- surface (`add_node`, `remove_node`, `add_edge`, etc.) is short because
82
- the IR is short. When the IR is short, every op has a clear preimage and
83
- postcondition, and `verify` can check both. Wide IRs grow ops that
84
- nobody can reason about end-to-end.
85
-
86
- ## (d) Cost / value of the corpus + LLM-judge
87
-
88
- The mermaid-js docs corpus (now 271 entries) has paid for itself twice. In Loop 5 a
89
- state-diagram round-trip regression slipped past the unit tests because
90
- our hand-written fixtures didn't cover the exact `note left of` /
91
- `note right of` pattern; the corpus caught it within seconds. In Loop 7
92
- the corpus catches every layout / serialize change that affects more
93
- than a single fixture.
94
-
95
- The live-model eval stance (from Loop 5 onward) is intentionally
96
- periodic-not-per-PR. Running it on every PR would dominate the wallclock
97
- budget and produce a grade that is statistically noisy across runs. The
98
- current deterministic harness is `bun run eval/agent-usage/run.ts`; committed
99
- subagent-backed transcript sets replay in CI, while direct API-backed captures
100
- remain a pre-release/on-demand task when credentials are available. Net: cheap,
101
- high-signal, not always-on.
102
-
103
- ## (e) Process changes for Loop 8 and later
104
-
105
- - **Survey first, build second.** Before adding a new family parser or
106
- serializer, search the npm registry and GitHub for prior art. Spend
107
- half a day before spending two weeks.
108
- - **Commit per milestone.** Loop 6 stalled silently because the
109
- implementer agent batched everything and ran out of budget without
110
- pushing. Loop 7 made each milestone its own commit + push and the
111
- result is visible progress that the next implementer can verify.
112
- - **Use the Workflow tool for end-to-end cycles.** When the plan calls
113
- for "do X, run tests, commit, push, do Y, run tests, commit, push,"
114
- the autopilot / workflow harness handles the bookkeeping correctly.
115
- Manually orchestrating commits inside one agent thread is what stalled
116
- Loop 6.
117
- - **Document scope cuts honestly.** Loop 7 cut the Loop 6 mermaid-ast
118
- structured uplift, the SVG `--compact` mode, the CSS-variable font
119
- override, the TTY guard, and `renderAsciiWithMeta()`. Those cuts live
120
- in the PR description so the next loop doesn't have to rediscover
121
- what's missing.
122
- - **Probe before fixing.** Loop 7's pathfinder-determinism fix was
123
- *not* a fix — a 10-run probe showed the code was already deterministic.
124
- The "fix" became a regression guard. Cheaper to probe than to ship a
125
- speculative change.
126
-
127
- ## Loop 8 + 9 lessons (PNG export + finish-the-backlog)
128
-
129
- - **Implementer stalls happen — design for recovery.** Loop 8's
130
- implementer hit a content-filter at minute ~19 mid-M5. Loop 9's
131
- implementer hit a 500 server error at minute ~27 mid-B11. Both
132
- were recoverable because **commit-per-milestone was treated as
133
- load-bearing** in the hardened plan. The 8 Loop 9 milestones
134
- (A1-A10) landed cleanly before the stall; B11 was a clean
135
- pickup. The plan structure IS the recovery scaffold.
136
- - **Skip the planner/critic phase when the prompt already encodes
137
- prior critic feedback.** Loop 8 took critics from prior loops as
138
- given (embedded in the user's prompt) and went straight to
139
- implementer. Worked. Loop 9 spent budget on planner + 5 critics
140
- for a hardened plan that was 90% pre-decided by the user — the
141
- remaining 10% was incremental.
142
- - **Honest scope cuts beat over-promising.** Loop 9 shipped 13 of
143
- 16 milestones. The 3 cuts (B14 pathfinder, C15 mermaid-ast
144
- journey/xychart, C16 plugin consolidation) are documented in
145
- `docs/project/divergences.md` with concrete reasons (context budget; mermaid-ast
146
- transitive deps broken; refactor too large for remaining budget).
147
- Loop 10 picks them up with no mystery.
148
- - **Probe dep availability before committing to a dep-based plan.**
149
- Loop 9's C15 cut wasn't a budget call — it was a dep-availability
150
- call. `mermaid-ast` is installed but its transitive `langium` →
151
- `vscode-jsonrpc` → `@chevrotain/regexp-to-ast` chain is broken in
152
- the sandbox. A 30-second `bun -e "require('mermaid-ast')"` probe
153
- at plan time would have surfaced this. Lesson: validate the
154
- dependency you're building on actually loads before spec'ing the
155
- work around it.
156
- - **Direct execution beats subagent-spawn when stalls compound.**
157
- Loop 9's recovery used direct execution (no subagent spawn for the
158
- remaining B11/B12/B13 milestones). Past two implementer stalls in
159
- consecutive loops made the spawn-cost no longer worth it. The
160
- workflow-orchestration value of subagents is real (parallel
161
- critics, isolated context windows), but a third consecutive stall
162
- would have been wasted minutes. Direct execution + per-milestone
163
- commit is the fallback recovery mode.
164
-
165
- ## Loop 10 lesson — verification passes must observe behavior, not grep
166
-
167
- Loop 10's pre-flight verification claimed 5 ecosystem gaps were open.
168
- THREE were already implemented:
169
- - #116 auto-contrast: I grepped `theme.ts` (found only shadow-luminance)
170
- and missed `renderer.ts`'s `contrastTextColor`. The feature worked.
171
- - #113 fanout trunk-sharing: I never checked `edge-bundling.ts`, which
172
- already does fan-out bundles with shared trunks.
173
- - #67 root detection: already in `grid.ts`; I under-counted coverage.
174
-
175
- Only #81 (CSS classes) and #66 (A* OOM guard) were genuine gaps.
176
-
177
- The mistake: a verification pass that greps ONE plausibly-related file
178
- and concludes "not done." The fix: **render the actual feature and
179
- observe the output** before declaring a gap. For #116 that's one
180
- `renderMermaidSVG('...style A fill:#000') → check text fill` — 10
181
- seconds, definitive. Grepping the wrong file is worse than not checking,
182
- because it produces false confidence.
183
-
184
- Cost this time was low — I added regression tests for already-working
185
- features (net positive: they were untested). But the same false-negative
186
- on a "build it from scratch" item would have meant rebuilding what
187
- exists, or worse, a second parallel implementation. When the verification
188
- says "gap," the bar to act on it is: reproduce the absence by running the
189
- code, not by reading one file.
190
-
191
- The flip side worked well: committing per milestone with honest commit
192
- messages ("already implemented; added coverage") kept the record
193
- truthful. The PR history shows exactly which items were built vs
194
- already-present vs documented-cut — no inflation.
195
-
196
- ## Loop 11 lesson — observation-first verification worked
197
-
198
- Loop 10's failure was grep-based verification (3 of 5 "gaps" were already
199
- built). Loop 11 applied the corrected method: before scoping the work, I
200
- RAN the code and inspected output — rendered two diagrams and diffed their
201
- ids (#7540 confirmed real), rendered with accTitle and checked for <title>
202
- (#7254 confirmed absent), scanned output for external-fetch vectors (#7645
203
- confirmed @import-only). All 5 targeted items were genuine. Zero false
204
- negatives, zero rebuilt-what-existed waste.
205
-
206
- The cost of observation-first is ~5 minutes of running snippets up front;
207
- the cost of getting it wrong (Loop 10) was rebuilding-risk + a misleading
208
- scorecard. Observation-first is now the default for any "are we missing X?"
209
- question.
210
-
211
- A second Loop 11 note: the localized-post-pass pattern (namespaceSvgIds,
212
- injectAccessibility) keeps paying off. Both #7540 and #7254 could have been
213
- threaded through the 8 family renderers; instead a single post-render
214
- string rewrite in the resolve() funnel handled every family at once, with
215
- no blast radius into renderer signatures. When a feature is expressible as
216
- "rewrite the finished SVG," prefer that over threading state through the
217
- render tree — it's testable in isolation and family-agnostic by default.
218
-
219
- ## Loop 12 lesson — benchmark honesty, even when it stings
220
-
221
- The benchmark milestone was the one most at risk of motivated reasoning:
222
- we'd been claiming "compete on correctness" for several loops with zero
223
- numbers. The temptation is to measure only the axes we win.
224
-
225
- What actually happened when I ran it: **termaid cold-starts in ~102ms;
226
- our Bun CLI takes ~870ms.** termaid beats us on the exact thing (ASCII to
227
- terminal) we'd positioned as our turf. The honest move — recorded in
228
- RESULTS.md and the commit — was to say so plainly, and to relocate our
229
- actual differentiator: not ASCII speed, but the agent surface (AST,
230
- verify, mutate, SVG/PNG, MCP, determinism, structured errors) that
231
- termaid doesn't have. Against mmdc the story IS decisive (browserless,
232
- 3x faster cold, 5x smaller), and that's measured too.
233
-
234
- Two process notes:
235
- 1. Attempting the competitor installs for real was worth it. mmdc's
236
- "headless Chrome refuses to run as root" failure isn't a number I
237
- could have honestly asserted from docs — running it turned a claimed
238
- weakness into a demonstrated one. And termaid actually running turned
239
- an assumed win into an honest loss-on-one-axis. Observation beat
240
- assumption in both directions.
241
- 2. The benchmark exposed a concrete roadmap item (single-binary, #1018)
242
- that would close the cold-start gap. A benchmark that only flattered
243
- us would not have produced that.
244
-
245
- Also Loop 12: a fixed bug (M4 rgb-comma-split) had been deferred twice
246
- (found Loop 10, deferred Loop 11). The fix was ~15 lines. Lesson: small
247
- real bugs accumulate interest when deferred; a "found it, here's the one
248
- -liner repro" item should usually be fixed in the loop it's found, not
249
- filed.
250
-
251
- ## Loop 13 lesson — the closed loop is the real risk now
252
-
253
- The single most important realization of this whole arc: **13 loops of
254
- work sit in one unmerged PR (#11), and every quality signal is
255
- self-generated.** Our tests, our docs corpus, our MermaidSeqBench wiring,
256
- our benchmark, our LLM-judge, our agent-usage harness — all authored by
257
- the same effort that authored the code. That's not worthless (it caught
258
- real bugs: the state round-trip regression, the marker-id collision, the
259
- A* OOM, the rgb-comma split), but it has a ceiling. A closed build→
260
- self-review→build loop produces breadth and internal consistency and
261
- *cannot* produce the one thing that matters most: evidence that a real
262
- external consumer is served.
263
-
264
- We should have sought a merge + a real consumer around Loop 5-6, not
265
- Loop 13. The pattern to avoid in future efforts: treating "more features,
266
- all green" as progress when the artifact has never left the lab. Shipping
267
- surface is not the same as shipping value.
268
-
269
- What Loop 13 itself added that points the right way:
270
- - **The agent-usage harness (M6)** is the closest we've come to measuring
271
- the thing that matters — but even it has a scripted agent, not a real
272
- one. The real validation (Layer 3) needs a live model and, ideally,
273
- real tasks from a real consumer.
274
- - **The benchmark + single-binary (M3)** were both honesty wins: the
275
- binary halves cold-start but doesn't beat termaid/Go, and we said so.
276
- - **The TODO.md backlog (M1)** finally names the non-code blockers
277
- (publish-name decision and real external consumer) as first-class items
278
- instead of leaving them implicit.
279
-
280
- The recommendation that's now written into TODO.md: stop adding features;
281
- finish the naming/publish decision and get one real consumer. The next loop
282
- that adds a feature instead of pursuing those is probably the wrong loop.
283
-
284
- Smaller Loop 13 notes:
285
- - bun-compiled binaries embed the runtime (112MB) and the resvg native
286
- addon embeds cleanly — PNG works from the single binary. Good surprise.
287
- - Extracting `renderFileOnce` made --watch testable without fighting
288
- fs.watch timing — same "pure core + thin imperative shell" move that
289
- made the post-pass features (namespaceSvgIds etc.) testable.
290
-
291
- ## Loop 14 lesson — consistency is a feature, not cleanup
292
-
293
- Rebasing PR #11 onto `main` was mechanically easy; the dangerous part was
294
- not the code merge, it was the contract drift hiding in docs and capability
295
- metadata. The branch had accumulated several small inconsistencies:
296
-
297
- - PR title still described Loop 7 even though the branch had become Loops
298
- 7-13.
299
- - `docs/features.md` and generated `llms.txt` claimed MCP `query`/`xref` tools
300
- that do not exist. The real surface is primary `execute` plus narrow
301
- `render_png` and `describe` helpers.
302
- - `am capabilities` under-reported output formats (`svg`, `ascii`, `png`)
303
- even though the CLI also supports `unicode` and `json`.
304
- - The capability envelope was reporting plugin-internal hooks rather than
305
- the public agent surface, so it implied most families could not parse,
306
- serialize, or verify even though `parseMermaid` / `serializeMermaid` /
307
- `verifyMermaid` support all registered families.
308
- - Tier 3 docs once described an opt-in lint layer before one shipped. The
309
- honest state then was "reserved"; the current shipped lint catalogue is
310
- explicit and small (`DUPLICATE_EDGE`, `UNREACHABLE_NODE`) so capabilities,
311
- docs, and emitted warning codes stay in lockstep.
312
-
313
- These are not cosmetic mismatches. Agent-facing software is consumed by
314
- machines that rely on the manifest. A stale `capabilities` response sends
315
- agents down the wrong path; a doc-only MCP tool wastes a tool call and
316
- teaches distrust; a stale PR title causes reviewers to review the wrong
317
- change. For agent-native products, **the docs, schemas, generated digests,
318
- CLI help, and PR title are part of the runtime surface**.
319
-
320
- Two practical rules came out of the rebase:
321
-
322
- 1. **After every scope-changing loop, run a contract-drift audit.** Grep
323
- for old tool names, output formats, package names, and roadmap statuses;
324
- then compare the results to live CLI/MCP output. In this loop, the
325
- decisive command was `am capabilities --json`, not reading the docs.
326
- 2. **Regenerate ignored artifacts before browser/e2e tests.** The first
327
- post-rebase browser run failed because ignored `editor.html` was stale
328
- from a previous branch. `bun run editor && bun run test:browser` is
329
- the safe sequence. A generated file outside git can still poison local
330
- verification.
331
-
332
- The deeper lesson: once a branch is large, consistency work is no longer
333
- "polish." It is how you make the branch reviewable and trustworthy enough
334
- to merge. Stop feature work, align the contract, then ship.
335
-
336
- ## Loop 15 lesson — if we replayed PR #11, start with the agent contract
337
-
338
- If we could do this branch again from scratch, the biggest change would be
339
- sequence. We built a lot of correct pieces, but too many of the governing
340
- rules emerged after the implementation already existed. The better order
341
- would have been:
342
-
343
- 1. **Write the agent contract first.** Define the public surface before
344
- adding features: docs, schemas, CLI help, MCP declarations,
345
- `llms.txt`, package exports, and examples. For an agent-native library,
346
- those are not downstream documentation; they are the API that agents
347
- actually consume.
348
- 2. **Decide structured-vs-source-level policy up front.** We eventually
349
- landed on structured mutation for every built-in renderable family, with
350
- source-level-only editing reserved for opaque fallback bodies where
351
- unmodeled syntax was preserved losslessly. That policy should have existed
352
- before mutation ops were exposed. Render support is not mutation support,
353
- and an unsafe typed edit is worse than no typed edit.
354
- 3. **Separate greenfield creation from existing-diagram edits earlier.**
355
- We over-corrected toward "always mutate" before recognizing the useful
356
- distinction: new diagrams can often be authored directly as Mermaid
357
- source, then parsed/verified/rendered; existing structured diagrams
358
- should go parse → narrow → mutate → verify → serialize. This also led
359
- to the right eval metric split: `safePathRate` is not the same thing as
360
- `structuredPathRate`.
361
- 4. **Build the canonical examples at the beginning.** The MCP-vs-CLI
362
- parity example and the agent-improvement loop should have been seed
363
- fixtures, not late validation. A runnable example that creates a
364
- complicated diagram through MCP and CLI, plus another that creates →
365
- assesses → mutates → reassesses → renders, would have clarified the
366
- intended product shape and prevented doc drift.
367
- 5. **Treat `verify.ok` as structural, not visual.** The Auth Flow episode
368
- made this concrete: the diagram verified while the layout was visually
369
- poor. Geometry assertions, source-order assertions, and screenshot/PNG
370
- regressions should have been part of the layout work from the start.
371
- 6. **Pin the Code Mode security model before marketing the feature.** We
372
- eventually enforced read-only SDK results, trusted diagram lineage,
373
- synchronous execution, no host constructors/functions, and explicit
374
- `node:vm` caveats. Those rules should have been written before the MCP
375
- story shipped. We also should have avoided any wording that implied a
376
- current Cloudflare Codemode or Worker integration.
377
- 7. **Keep the branch smaller and merge earlier.** PR #11 accumulated
378
- agent APIs, CLI affordances, MCP design, docs, security hardening,
379
- visual/layout fixes, evals, examples, and repository cleanup. Most of
380
- those are valuable, but their combination made review harder and made
381
- the closed-loop risk worse. A better path would have been: ship the
382
- minimal honest agent contract, get one external consumer, then add the
383
- next layer.
384
-
385
- The high-order lesson is that agent-native work is contract-first work.
386
- The implementation can be correct and still be hard to trust if the public
387
- surface, examples, evals, and docs are not aligned from day one. If we
388
- replayed this branch, we would start with a small runnable contract,
389
- exercise it through MCP and CLI immediately, and let that contract decide
390
- which implementation work belongs in the branch.
391
-
392
- ## Loop 16 lesson — layout quality needs executable heuristics, not vibes
393
-
394
- The bad-layout work only became tractable once the complaint was converted
395
- into geometric oracles. "Looks wrong" turned into concrete assertions:
396
- acyclic edges should progress in the declared direction, unrelated edge
397
- segments should not cross node boxes, feedback-heavy processes should keep
398
- clean routes, self-loops need visible clearance, and `verify.ok` still does
399
- not mean the diagram is visually good. Those checks are cheaper and more
400
- reviewable than screenshot-only debugging, and they explain failures in
401
- terms a layout algorithm can act on.
402
-
403
- The red phase mattered. The new fan-in/fan-out heuristic caught real
404
- backward `TD` edges (`A2 -> A`, `B2 -> B`) before the fix. The green phase
405
- was not a broad renderer rewrite; it was a source-aware, cycle-tolerant
406
- model order that adds source-before-target constraints unless they would
407
- turn a feedback edge into a forward edge. That preserved the Auth Flow
408
- lesson from Loop 15: feedback loops should route backward, but acyclic
409
- edges should not accidentally point backward because parser insertion order
410
- was target-biased.
411
-
412
- The refactor lesson: layout defaults are shared infrastructure, so every
413
- fix needs a blast-radius check. The first implementation improved
414
- flowcharts but perturbed architecture snapshots and group-boundary routing.
415
- The durable version made the exception explicit: architecture, as a
416
- projected family, preserves direct child order inside groups while still
417
- letting root-level group/service siblings use source-aware ordering. The
418
- iconless-service regression exists because shape-based inference was too
419
- implicit; when preserving semantics matters, pass the intent explicitly.
420
-
421
- Finally, Cloudflare CodeMode belongs in the backlog only as an honest
422
- future deployment path, not as accidental marketing copy. A Worker-hosted
423
- Agentic Mermaid app may make sense, but it needs a scoped security model,
424
- auth/rate limits, persistence, and parity with the current CLI/MCP/library
425
- contract. Naming it in `TODO.md` is useful; implying the local `node:vm`
426
- MCP is already Cloudflare CodeMode would be a contract bug.
427
-
428
- ## Loop 17 lesson — measure blast radius before believing a layout heuristic
429
-
430
- The audit loop (PR #17) ported two upstream layout ideas and rejected a
431
- third, and the difference between the three outcomes was instrumentation.
432
- The before/after comparison harness (`eval/layout-compare/run.ts`) was
433
- built first, as a prerequisite, and immediately earned its keep: the first
434
- fan-in grouping implementation used blanket in-degree and silently wrecked
435
- three state-machine corpus samples — Off ⇄ On toggle pairs were treated as
436
- fan-in joins and dragged sideways into label collisions. No unit test
437
- caught it; the corpus-wide diff did. The refined heuristic (exclude
438
- self-loops and 2-cycle back-edges) shipped with evidence: one sample
439
- improved, zero regressed. The drift sentinel forces you to *acknowledge*
440
- layout changes; only a before/after instrument lets you *judge* them.
441
-
442
- Second lesson: upstream evidence beats upstream rules. Upstream PR #127
443
- proposed a strict per-side ER cardinality grammar, but the mermaid-docs
444
- corpus — scraped from Mermaid's own documentation — uses `o{` on the left
445
- (`o{--||`), which that rule would reject. The right token set was the
446
- side-agnostic Mermaid lexer set that `er-body.ts` already used. When an
447
- upstream fix and the upstream corpus disagree, the corpus wins.
448
-
449
- Third: layout fixes that ship upstream as coordinated sets must be ported
450
- as sets. A minimal cherry-pick of PR #113's preferred-direction A* change
451
- visibly broke our trunk post-processing (stray corners, a diagonal
452
- arrowhead) because the upstream fix only works together with its FIFO heap
453
- tie-breaking and branch-point re-routing. Probe, observe, revert fast,
454
- and scope the full port honestly (BUILD-10) instead of pushing through.
455
-
456
- Third, resolved (BUILD-10) — and the resolution sharpens the lesson:
457
- "port the whole set" does NOT mean "every upstream hunk lands." It means
458
- port the *coordinated intent*, then let this fork's own architecture decide
459
- which hunks are load-bearing. Of PR #113's four parts, only two were needed
460
- here: (1a) deterministic FIFO tie-breaking in the pathfinder MinHeap and
461
- (3b) label placement preferring the per-sibling vertical drop in TD. Those
462
- two alone reproduced the upstream golden byte-for-byte AND kept the LR
463
- box-start repro byte-identical. The other two — (1b) `preferredDir` A*
464
- neighbour reordering and (2) explicit branch-point re-routing — REGRESSED
465
- trunk rendering exactly as the minimal probe had (stray `+`, `◢`, broken LR),
466
- because this fork already carries trunk machinery upstream lacked
467
- (edge-bundling for unlabelled siblings) plus the new FIFO determinism; the
468
- reorder/re-route then fought routing that was already correct. The general
469
- rule: when a fork has diverged, an upstream fix is a hypothesis about *intent*,
470
- not a patch to apply verbatim. Bisect the set against the fork (here: an
471
- env-var gate per part + the corpus diff isolated the two that mattered in
472
- minutes), ship the minimal load-bearing subset, and sabotage-test it
473
- (reversing the FIFO tie-break alone re-introduces the `─center*─` detour,
474
- proving it is the actual lever). Dead upstream scaffolding (the `preferredDir`
475
- param) is kept only when it is a real, separately-tested capability.
476
-
477
- Fourth: a documentation gap on an agent surface *is* an API gap. An audit
478
- subagent reading our own docs concluded state diagrams were not mutable,
479
- because every narrower list omitted the actual path (`asFlowchart` narrows
480
- state bodies). The code was right and the consumer still failed. The fix
481
- was a sentence on every agent surface plus a doc-sync guard that fails if
482
- any surface claims state mutation without documenting the narrowing path.
483
-
484
- Fifth: "100% parse success" is not faithfulness. The ER `}o` bug lived
485
- inside a fully gated corpus for the same reason it shipped: the gate
486
- asserted parsing, while the renderer silently dropped the relationship and
487
- its entities. Faithfulness needs node/edge-count oracles — the harness now
488
- treats any count change as a regression by default.
489
-
490
- ## Loop 18 lesson — evidence-backed agent work beats imagined ergonomics
491
-
492
- The release-model transcript work and the failure corpus changed the quality
493
- of the backlog. Before EVAL-1/EVAL-2, the agent loop mostly measured our own
494
- scripted ideal path. Capturing pi-subagent-backed transcripts and preserving
495
- known-bad raw responses made the next feature decisions less speculative:
496
- Tier 3 lint should start with observed agent mistakes (`DUPLICATE_EDGE`,
497
- `UNREACHABLE_NODE`), not a grand style-guide catalogue.
498
-
499
- The same rule applied to BUILD-7. **MCP reachability was not just "start an
500
- HTTP server."** The durable shape was transport-neutral behavior (stdio and
501
- HTTP/SSE call the same `handleRequest` core), session lifecycle correctness,
502
- safe artifact storage, and URL/file outputs that do not become an arbitrary
503
- file-write primitive. The useful contract is `{path,url,mimeType,bytes,sha256}`
504
- for large or binary outputs, with loopback defaults, authenticated remote
505
- binding, content-type/Origin gates, size limits, TTL/cleanup, and tests that
506
- fetch the artifact back and verify its bytes.
507
-
508
- **BUILD-2 is not "rename verify and format."** `process --mode
509
- validate|canonicalize` only deserves to exist if it reduces agent routing
510
- errors relative to today's explicit verbs (`verify`, `format`, `parse`,
511
- `serialize`, `mutate`, `batch`). The right next step is a triage artifact: write
512
- the exact JSON envelope and exit-code contract, run it against docs/evals, and
513
- then either implement a thin schema-tested wrapper or deliberately park it. A
514
- wrapper that only adds another synonym would make the contract larger without
515
- making agents safer.
516
-
517
- ## Loop 19 lesson — CI-green is not the same as audit-clean
518
-
519
- PR #30 was GitHub-green before it was ready. The browser e2e failure was fixed
520
- first, but the remaining non-CI audit found real route defects: text-embedded
521
- long links lost their rank length, non-incident nodes could be moved onto a
522
- certified route without a `ROUTE_STALE_AFTER_NODE_MOVE`, and nested subgraph
523
- edges were extracted in the wrong coordinate frame. The lesson is not "make CI
524
- run everything"; it is "know which proof each gate provides." CI proved the
525
- standard package and browser path. `audit:ugly`, route-contract properties, and
526
- focused nested-subgraph tests proved layout-contract claims the normal CI did
527
- not yet cover.
528
-
529
- The route-contract work also exposed a metadata bug: a fixed-point retry can
530
- first straighten an edge and later downgrade it to an explained detour. The
531
- geometry was acceptable, but the certificate still carried a stale
532
- `straightened` bit until the finalization pass recomputed bend counts and
533
- cleared impossible flags. For proof-carrying layout, certificates are part of
534
- the artifact, not logging. If later passes can mutate geometry, final facts must
535
- be recomputed after all retries, and property tests should assert the certificate
536
- matches the final route.
537
-
538
- Nested subgraphs taught a coordinate-frame rule: `INCLUDE_CHILDREN` and
539
- `SEPARATE` hierarchy modes have different edge-hosting semantics, and both need
540
- regressions. Edges such as `outer/A -> inner/B` must be hosted at their lowest
541
- common compound so extraction adds the right absolute offset. Edges to a
542
- subgraph id such as `X --> Pipeline` target the container box, not a phantom
543
- root node and not an internal child. Direction overrides are exactly where this
544
- becomes dangerous, because an empty or mis-hosted external segment can pass unit
545
- shape checks and then crash a later straightener.
546
-
547
- Finally, grammar support and route support have to move together. Mermaid's
548
- text-embedded label syntax splits the operator around the label (`-- No ---->`,
549
- `-. Maybe ..->`, `== Sure ====>`). Treating that as just label parsing silently
550
- collapsed authored link length during canonical serialization. The pinned tests
551
- now parse, serialize, and re-parse the length. The general rule: when a syntax
552
- feature affects layout rank, preserve it in the parser even if the renderer's
553
- visual output looks acceptable.
554
-
555
- Process note: reviewer subagents are useful, but only as another signal. The
556
- reviewer caught the stale `straightened` counterexample after the main fixes;
557
- local focused tests then reproduced it, a pinned regression locked it, and a
558
- second reviewer pass returned no blockers. Mutation testing remains a gap map,
559
- not a PR gate: the route run was attempted and timed out after partial progress,
560
- so it is evidence to schedule, not evidence to block the merge.
561
- ## Wrapper-fidelity lesson — official examples are a free conformance corpus, and laws need named constructs
562
-
563
- Three sessions of research work (the layout-complaint catalog, the `@{ shape }`
564
- silent-loss bug, and the wrapper-fidelity batch) converged on one method and
565
- one contract lesson.
566
-
567
- **The method: probe official documentation examples through the round-trip,
568
- not just our own fixtures.** The `@{ shape: ... }` silent-loss bug (BUILD-23,
569
- issue #29) was found by taking the Mermaid docs' own typed-shape syntax and
570
- running it through `parseMermaid → serializeMermaid`; the wrapper-fidelity
571
- gaps (BUILD-21) were found the same way with the syntax-reference's
572
- frontmatter examples. Both were violations of guarantees we believed we had:
573
- the `@{}` case fabricated phantom nodes from metadata keys with
574
- `verify.ok: true`; the frontmatter case flattened `config:`-nested keys into
575
- top-level YAML Mermaid silently ignores — so an edit loop *kept the bytes*
576
- that expressed the author's `config.layout` request while *killing their
577
- meaning* on interop. Neither was caught by 2,200+ tests, the 258-entry
578
- corpus, or the round-trip floors, because every fixture we owned was written
579
- by people who already knew what the parser modeled. Upstream documentation
580
- examples are adversarial in exactly the right way: they encode what real
581
- sources will contain, not what our parser expects. The standing rule:
582
- whenever Mermaid documents a syntax surface, its examples belong in our
583
- round-trip/verify corpus before we claim compatibility — secondary-source
584
- blog posts are leads, but the probe against the live code is the evidence.
585
-
586
- **The contract lesson: a preservation law that doesn't name construct classes
587
- hides loss in the gaps between its clauses.** The structured-or-opaque law
588
- said "any line your parser does not model must be preserved verbatim" and
589
- "structured bodies serialize to canonical source." Wrappers and comments fell
590
- between those clauses: frontmatter was *re-synthesized* (neither preserved
591
- nor canonical-to-Mermaid), directives were duplicated, and comments were
592
- dropped — each defensible under one clause, all violations of the law's
593
- intent. The fix wasn't more cleverness; it was naming the constructs and
594
- their policies explicitly (owner decisions 1C/2C): wrappers round-trip
595
- byte-verbatim with canonical synthesis opt-in; in-body comments are
596
- canonicalized away but *announced* (`COMMENT_DROPPED`), never silent, with
597
- segment preservation as the stated destination. The general form: for every
598
- construct class a parser can encounter — body statements, wrappers,
599
- directives, comments, future `@{}` metadata — the contract must state one of
600
- *modeled*, *preserved-verbatim*, or *dropped-with-warning*. "Canonical" is
601
- not a policy; it's where unstated policies hide.
602
-
603
- A smaller process note, same spirit as Loop 17's evidence rule: the
604
- `COMMENT_DROPPED` detector diffs the parsed comments against the actual
605
- serialized output rather than asking each family parser to report what it
606
- kept. Families that preserve comments in opaque segments (sequence) are
607
- correct by construction, and a future family that starts preserving them
608
- stops warning without anyone updating a list. Detect by observing the
609
- output, not by trusting per-family bookkeeping.
610
-
611
- ## Loop 20 lesson — hidden family registries are product surface
612
-
613
- The Gantt good-citizen audit exposed a different failure mode than a parser
614
- bug: a diagram family can be correctly implemented and still be only partly
615
- present in the product. The first pass wired parser, renderer, mutation,
616
- capabilities, docs, and PR evidence, but it still missed places that consume
617
- "the list of families" indirectly: the live editor example picker, eval
618
- fixture tags, generated `llms.txt`, `am init-agent`, MCP initialize guidance,
619
- SDK declarations, stale policy language, and even the issue checklist that was
620
- supposed to guide the work.
621
-
622
- The root cause was twofold. First, there was no typed built-in-family registry
623
- that all outward-facing projections were forced to agree with. Second, some
624
- surfaces were treated as collateral documentation even though agents and users
625
- consume them as runtime affordances. The durable fix is not a longer memory
626
- checklist; it is a checked source of truth (`BUILTIN_FAMILY_METADATA`) plus
627
- projection tests that prove every registered family appears in editor examples,
628
- glyphs, eval fixtures, generated agent docs, CLI capabilities, MCP guidance,
629
- and sandbox-callable narrowers.
630
-
631
- The audit prompt that found the misses is worth preserving: ask "who consumes
632
- this family list?" and then grep for the old nouns, not just the new feature.
633
- Search for family names, narrowers, `source-level`, `examples`, `capabilities`,
634
- `llms.txt`, `init-agent`, `initialize`, `tools/list`, SDK declarations, eval
635
- manifests, sample galleries, generated site assets, package exports, and
636
- private/holdback prompt manifests. Anything that teaches a human or model what
637
- families exist is a product surface.
638
-
639
- This also changes the good-citizen checklist. It must distinguish family
640
- correctness from system citizenship. Correctness asks whether the parser,
641
- serializer, verifier, renderer, and properties preserve the family semantics.
642
- Citizenship asks whether every registry projection, generated artifact,
643
- distribution bundle, editor sample, skill/eval prompt, CLI/MCP declaration,
644
- and release artifact either derives from the registry or has a test proving it
645
- is synchronized. A new family is not done when it renders; it is done when an
646
- agent discovering the system through any supported entry point reaches the same
647
- typed path.
648
-
649
- ## Loop 21 lesson — tracked exceptions are gap maps, not permission slips
650
-
651
- Issue #41 made the good-citizen standard executable, and it also made one
652
- thing impossible to hide: **there were historical family gaps.** That was not a
653
- failure of the ratchet; it was the reason the ratchet existed. The BUILD-22
654
- backfill closed the visible citizenship exceptions, but the process lesson
655
- remains: older families do not need to instantly match a new evidence bar in
656
- one PR, but any difference from that bar must be named, checked, and tied to a
657
- live backlog item instead of living in a comment or reviewer memory.
658
-
659
- The important distinction is between a **supported-family blocker** and a
660
- **citizenship backfill gap**. A blocker means a public surface lies, data is
661
- lost silently, or an agent is sent down an unsafe path. A backfill gap means
662
- the family is usable through the current public contract, but lacks one of the
663
- higher-confidence evidence lanes Gantt now has. The matrix in
664
- `docs/contributing/diagram-family-citizenship.matrix.json` records those cells
665
- as `exception` only when they point at `TODO.md`/issues; CI fails if an
666
- exception is untracked or appears on a core surface.
667
-
668
- The gaps #41 surfaced were concrete, and BUILD-22 closed the current matrix
669
- exceptions:
670
-
671
- - **Stable region assertions** now cover every registered family through
672
- `src/ascii/meta.ts` and `src/__tests__/agent-ascii-meta.test.ts`.
673
- - **Targeted mutation or sabotage lanes** now exist for state, sequence,
674
- timeline, class, ER, journey, pie, and quadrant, alongside the pre-existing
675
- flowchart/link-routing, architecture+xychart, and Gantt lanes.
676
- - **Executable upstream-docs harvest and divergence evidence** now covers all
677
- registered families via the regenerated 271-example Mermaid docs corpus plus
678
- `eval/mermaid-docs-corpus/divergences.json`. The cross-family parser/DB
679
- bench in `eval/mermaid-upstream-suite-bench/` now accounts for every current
680
- renderable-family BUILD-20 upstream block, while Gantt's deeper
681
- family-specific pilot remains in `eval/mermaid-gantt-bench/`.
682
- - **Generated-site drift was real.** Pie, Quadrant, and Gantt were supported
683
- families but the sample gallery still lacked explicit color/prefix handling;
684
- #41 fixed that instance and added tests so future family categories cannot
685
- fall back silently.
686
-
687
- The lesson: an exception ledger should make maintainers slightly
688
- uncomfortable. If it reads as "we're allowed to ignore this," it has failed.
689
- It should read as "this is the exact remaining work, this is why it is not a
690
- merge blocker today, and this is the test/backlog hook that will fail if we
691
- forget it." Good citizenship is therefore both a contract and a gap map: Gantt
692
- sets the destination, the matrix records current state, and the upstream
693
- ratchets keep the deeper-compatibility path executable beyond the now-closed
694
- BUILD-22 matrix gaps.
695
-
696
- ## PR #54 audit lesson — closure PRs need adversarial self-review, not just green checks
697
-
698
- The final #26/#38 closure pass exposed three classes of mistakes that ordinary
699
- green tests did not make obvious enough:
700
-
701
- - **Debug metadata can lie even when rendering looks fine.** Sequence self-message
702
- SVG drew a loop, but the layout JSON adapter initially flattened it to a
703
- two-point zero-bend line and certified that false geometry. Certificate tests
704
- must check the rendered family geometry, not just that a certificate object
705
- exists.
706
- - **Generated-site inputs are docs too.** Updating `docs/features.md` and
707
- `docs/api.md` was not enough; pages such as `/differences` are generated from
708
- `scripts/site/*` copy and need the same capability audit as Markdown docs.
709
- - **Seed ratchets are useful only when named honestly.** The first BUILD-20
710
- seed bench was valuable as a cross-family parser/DB smoke ratchet, but it
711
- had to be named as partial until the full accounted harvest replaced it. The
712
- current bench records imported blocks, excluded blocks, and zero deferred
713
- blocks so future readers do not mistake curated smoke coverage for
714
- comprehensive accounting.
715
-
716
- The practice change: before merging a broad closure PR, run a fresh-context
717
- review explicitly asking "what claims would become false if a consumer trusted
718
- our debug metadata, generated site copy, or issue labels?" Then either fix the
719
- claim or carve out a named follow-up.
720
-
721
- ## PR #64 lesson — "below perceptibility" is a reason to annotate, not to skip visual evidence
722
-
723
- The issue #61 mixed-hub fix recenters a hub by a few pixels (worst
724
- `peerBarycenterDelta` 8.0px → ~4.0px; the representative case 6.13px → 3.07px).
725
- I rendered a raw before/after, saw the shift was invisible at full scale, and
726
- cited good-pr's "don't pad a PR with near-identical screenshots" to justify
727
- shipping *no* visual at all — only a numeric table. That inverted the rule. The
728
- guidance forbids padding with screenshots that show nothing; it does not bless
729
- omitting evidence for a genuinely visual change. For a small-magnitude geometry
730
- change the correct move is to **make the change legible**: overlay the reference
731
- geometry the metric is computed from — here, the incoming/outgoing barycenter
732
- and hub-center guide lines, parsed from the rendered node rects rather than hand
733
- placed — and/or zoom. "Too small to see raw" is an argument for an annotated or
734
- zoomed artifact, not for its absence. A layout library whose own reviewer
735
- checklist asks "is the hub centered over the peer group?"
736
- (`docs/contributing/visual-review-evidence.md`) treats the picture as part of
737
- the contract, complementary to the table and the tests, not replaceable by them.
738
-
739
- Second, the artifact pattern already existed; I should have read it before
740
- inventing a justification for skipping it. Git history and the contributing doc
741
- already prescribe per-issue evidence scripts (`scripts/pr-assets/issue-NN-evidence.ts`)
742
- that render BEFORE via a worktree at a base SHA and AFTER from the tree, writing
743
- a committed composite to `docs/pr-assets/`, reproducible from source. The right
744
- first step for any "should this PR have screenshots?" question is to grep the
745
- history for how the repo did it last time, not to reason from first principles.
746
-
747
- Third, a red→green check can be honest about magnitude *and* about which tests
748
- discriminate the bug. This fix was probe-driven, with the ratchet tests written
749
- after it worked; reverting only the implementation proved 18 tests genuinely
750
- fail without the fix. But the same revert showed the small-fan-out `≤0.75px`
751
- tests pass on the original code too — they are regression guards, not
752
- bug-discriminating tests. A green suite where only a subset is load-bearing
753
- should say so in the PR; claiming "N tests prove the fix" without separating the
754
- discriminating tests from the guards overstates the evidence. Naming the
755
- difference is the trust dimension, not a footnote.
756
-
757
- ## Loop 22 / issue #71 lesson — abstractions are product contracts, not cleanup
758
-
759
- The commit history since the fork from Beautiful Mermaid has a pattern that is
760
- easy to miss when looking at any one PR: the durable improvements were the
761
- ones that turned a claim into an executable waist. The first fork commits made
762
- rendering parity and fork positioning visible. The agent-native loop turned
763
- "agents can edit diagrams" into typed parse/mutate/verify surfaces. The route
764
- work turned "layout quality" into certificates, mutation scores, contact
765
- sheets, and tripwires. Gantt and the family-citizenship work turned "we support
766
- this family" into registry projections, generated docs, SDK declarations, eval
767
- fixtures, and CI checks. Issue #71 is the same lesson at the architecture
768
- level: an abstraction is not done when the code compiles; it is done when every
769
- consumer reaches the same checked contract.
770
-
771
- The original issue #71 spec was mostly right, but this session showed why old
772
- specs need a second audit after the codebase has taught us more. The best
773
- solution was not a body-only `FamilyPlugin.layout` hook; that would have made
774
- opaque bodies, frontmatter, `%%init%%`, and accessibility directives second
775
- class. The implemented solution made layout hooks source-context hooks. The
776
- best solution was also not "iterate the registry map and trust insertion
777
- order"; the history from family citizenship and generated-doc drift says
778
- family order is a product surface, so built-ins now follow
779
- `BUILTIN_FAMILY_METADATA` and external registrations sort after them.
780
- Re-reading the spec against the academic literature, competitive examples, and
781
- the repo's own failure history improved the design more than simply executing
782
- the old checklist would have.
783
-
784
- The docs audit after implementation was not administrative polish. It found
785
- that current-facing docs still carried old nouns: `buildColors`,
786
- `FamilyRouteCertificate`, and vague "route/family certificates." Those names
787
- are part of the API an agent or maintainer learns from. Leaving them in public
788
- docs would create a second abstraction in prose after the code had unified the
789
- real one. Historical docs are allowed to keep old names, but they must label
790
- their era; current docs must use current contracts. This is the same lesson as
791
- the generated-site and `llms.txt` fixes: docs, skills, PR descriptions, and
792
- generated assets are not collateral. They are discovery surfaces.
793
-
794
- The practice change is concrete:
795
-
796
- - Reappraise old specs before implementing them wholesale. Ask which claims
797
- are still true after the last 20 commits, not just whether the requested
798
- task is clear.
799
- - Prefer thin waist contracts over universal models. `RenderContext`,
800
- `resolveDiagramColors`, `FamilyPlugin` hooks, and the certificate split each
801
- remove duplicated decisions without pretending SVG, ASCII, agent layout, and
802
- family-specific geometry are one IR.
803
- - When a refactor renames an abstraction, grep the docs and skills for the old
804
- nouns before calling the PR done. Either update the reference or mark it
805
- explicitly as audit-time history.
806
- - Use the fork's own history as evidence. If a prior lesson says generated
807
- docs drift, family lists drift, or visual claims need artifacts, treat that
808
- as a design constraint in the next abstraction PR, not as trivia from an old
809
- retrospective.
810
-
811
- ## PR #79 lesson — a certifying "freeze" is only as strong as its repair set, and contextual metrics couple edges
812
-
813
- The pass-manifest work (PR #79, continuing #71/#26) shipped a certifying
814
- straightener, `applyRouteContracts`, that classifies → straightens → certifies →
815
- **freezes** node geometry. The intent was a principled contract: after the freeze,
816
- routes are correct and carry a proof. What the four-round bug hunt actually taught
817
- us is that **the freeze is not a fixpoint**, and the certificate is only as strong
818
- as the set of repairs the certifier can perform.
819
-
820
- **Post-freeze repair passes are a leak indicator, not a feature.** This PR added a
821
- fourth pass that runs *after* the freeze to fix what an upstream pass produced:
822
- `repairLabelsOnSharedTrunks`, `repairLabelsOffOwnRoute`, `reanchorOffOutlineEndpoints`,
823
- and now `rerouteEdgesThroughNodes`. Each exists because a node-mover
824
- (`equalizePeerNodeDimensions`, `honorLinkRankDistance`, `alignPortLanes`) mutates
825
- geometry without re-establishing the contract's invariants, and the certifier
826
- cannot always repair the result. Two concrete blind spots in the certifier's own
827
- escape logic were the proximate cause of round 4: `tryZRoute` has no in-span escape
828
- lane when the obstacle is exactly as tall as the endpoints, and `tryEscapeDetour`'s
829
- first stub is rejected as a channel conflict when a sibling edge shares the source
830
- exit lane (there was a shared-*target* fan-in exemption but no shared-*source* one).
831
- So "we have a certifying straightener" was never the same as "routes are correct
832
- after the freeze." The durable lesson: when you find yourself adding the *n*th
833
- downstream net, that is evidence the upstream contract is leaking — the honest fix
834
- is to give the movers their own clearance checks, or to adopt real ELK ports (#38)
835
- so an edge cannot dangle or cut a node after a move. The downstream-net route is
836
- defensible (one class-agnostic net catches any mover, present or future, and is
837
- provably no-op on the clean corpus), but it is a *tradeoff to name in the PR*, not a
838
- win to bank silently. An independent design workflow reached the same recommendation
839
- *and* flagged the same caveat, which is the tell that it is a real fork in the road.
840
-
841
- **Gate on the rubric's EXACT predicate for a mechanically-provable no-op.** The
842
- safety property that made every one of these fixes shippable is "no-op on the
843
- HARD-clean corpus": gate the new behaviour on a HARD-violation condition the clean
844
- corpus never exhibits, then let the byte-exact layout-equivalence gate and
845
- `bun run track` zero-drift *mechanically prove* the no-op instead of asserting it.
846
- The subtlety worth keeping: a *proxy* predicate (e.g. `routeClearOfNodes` with a
847
- clearance margin) is a superset of the violation, only *empirically* 0-firing;
848
- keying detection on the rubric's own predicate (we exported `segmentThroughShape`
849
- so the trigger set is *exactly* the rubric's `edgeThroughNode` set — "touch ⇔
850
- already-HARD") closes the gap between "provably no-op" and "no-op so far." When a
851
- pass is meant to be invisible on good inputs, tie its trigger to the exact oracle
852
- that defines "bad," not to a convenient approximation.
853
-
854
- **Contextual metrics couple edges: fixing one can reclassify another that you never
855
- touched.** The most surprising finding. Rerouting a through-node edge *vacated a
856
- lane*, which flipped a *sibling* edge's pre-existing jog from "explained" (its
857
- straight alternative was blocked) to a `hitch` (the alternative is now clear) — even
858
- though the sibling's geometry is byte-identical before and after. The `hitch`/`sym`
859
- family of metrics is contextual: they ask "is there a clearer lane?", so a change
860
- *elsewhere* can alter a verdict *here*. Consequences: (1) a "never-worse per-edge"
861
- guarantee is *not* "never-worse per-layout"; (2) a naive global HARD-*count* guard
862
- is actively counterproductive — here it would preserve the more-severe
863
- `edgeThroughNode` rather than accept the lesser `hitch`, because count does not
864
- encode severity. We accepted the reclassification (a through-node becoming a small
865
- jog is an improvement) and *documented* it, and curated the standing-gate generator
866
- to the aligned family that isolates the through-node class — the same "isolate the
867
- class, don't drag in the adjacent swamp" discipline round 3's `conedFanin` used.
868
- A broad feedback fuzz here surfaced 74 off-outline-endpoint + 31 through-node cases
869
- from a *separate* pre-existing class; a zero-tolerance gate generator must be a
870
- targeted instrument, and the broad sweep belongs in a separate advisory harness.
871
-
872
- **CI-green under a moving `main`: new gates land retroactively.** A process echo of
873
- Loops 14 and 19, with a new twist. The `test` job went red not on any test but on a
874
- `website:check` step that did not exist at the branch's base — `main` had added a
875
- content-hashed editor bundle + a sync gate, and GitHub CI checks out the PR *merged
876
- into main*, so it ran a gate the branch had never seen. Because the bundle is hashed
877
- over `src/`, *any* source change flips its hash and staleness the committed output.
878
- The generated website bundle is now runtime surface a src-touching PR must
879
- regenerate (Loop 14's "generated artifacts are product surface," now CI-enforced).
880
- The lesson for long-lived branches: re-merge `main` periodically not just for code
881
- but because new *gates* land on `main` and apply retroactively to your merge — and
882
- after the merge, re-run the equivalence gate and tracker to prove the merge did not
883
- perturb your own subsystem (it didn't: byte-exact, zero drift) before trusting green.
884
-
885
- ## PR #94 lesson — a public compute endpoint is a security *and* a cost surface, and the boundary must be named honestly
886
-
887
- Shipping the hosted MCP endpoint (`https://agentic-mermaid.dev/mcp`) turned a
888
- static site into public, unauthenticated compute: agent JavaScript runs in a
889
- per-request Dynamic Worker isolate. That changed the failure modes from "wrong
890
- pixels" to "wrong containment claim" and "unbounded bill," and five rounds of
891
- external audit taught lessons that generalize beyond this endpoint.
892
-
893
- **Name the real boundary; everything else is defense in depth, and saying so is
894
- the honest move.** The temptation was to describe the harness's global-shadowing
895
- and wrapper tricks as "the sandbox." They are not. The guaranteed boundary is the
896
- isolate configuration — `globalOutbound: null`, empty env, no bindings, `cpuMs` —
897
- enforced by workerd, not by our code. A round-5 finding proved it: a
898
- comma+IIFE breakout still *runs* at eval time; the parenthesized wrap only makes
899
- `import`/statement injection a `SyntaxError`, and `hardenIsolateGlobals()` only
900
- strips capability globals *best-effort*. The right response was not to claim the
901
- layers close the hole but to document that they are DiD on top of the isolate,
902
- which remains the thing an attacker cannot defeat. A security note that overstates
903
- containment is worse than none: it invites reliance the code cannot honor.
904
-
905
- **CORS is not an access-control boundary for a public credential-less endpoint —
906
- and knowing that is what makes the "fix" correct.** An auditor flagged wildcard
907
- `Access-Control-Allow-Origin: *` as exposing the endpoint. But CORS gates only
908
- *browser* cross-origin reads; agents, servers, and curl ignore it entirely, so it
909
- never gated the primary threat (an attacker calling from their own machines —
910
- bounded by the WAF, not CORS). The one real vector `*` enables is a malicious site
911
- driving its *visitors'* browsers against the endpoint. So the correct fix is
912
- narrow: reflective CORS with Origin validation that keeps `*` for no-Origin
913
- (non-browser) clients and 403s disallowed browser Origins — closing the browser
914
- vector without breaking the actual consumers. Applying a finding literally
915
- ("lock down CORS") would have broken every agent client for no security gain;
916
- applying it *understood* fixed the real thing.
917
-
918
- **Distinguish a CI gate from a manual probe, out loud.** `website/e2e-mcp.sh`
919
- exercises the real isolate (breakout rejected, globals stripped, SDK still
920
- renders) but needs a live `wrangler dev` with the Worker Loader, so it is **not**
921
- in CI — the CI gate is `bun test src/__tests__/`. Comments that said "covered by
922
- e2e" read as "covered by CI" and were quietly corrected. When a claim of coverage
923
- is load-bearing for trust, state exactly *which* harness proves it and whether
924
- that harness runs automatically.
925
-
926
- **Cache correctness on a deploy is a versioning problem, not a TTL problem.** Two
927
- audit rounds converged on the same class of bug: a warm isolate or a cached
928
- response can serve *stale code* after a deploy that doesn't bump the package
929
- version. The fix is to key both the isolate ID and the response cache on a
930
- content hash of what actually executes — the harness bundle for isolates, and a
931
- full-deploy hash (bundled worker JS + harness + wasm + fonts + `compatibility_date`)
932
- for responses — so any change to any hosted surface invalidates without a manual
933
- version bump. Time-based expiry does not fix a correctness bug; identity does.
934
-
935
- **Committed build artifacts make every rebase commit a conflict.** Rebasing this
936
- branch onto a moved `main` conflicted on the generated harness bundle and
937
- deploy-hash at *multiple* commits, because both sides regenerate them. Hand-merging
938
- a minified bundle is pointless. The reliable technique: resolve generated-file
939
- conflicts mechanically (take either side to get past the commit), let the rebase
940
- finish, then run one authoritative `bun run website` at the tip and fold the fresh
941
- artifacts into the final commit so `website:check` is green. Only the tip's
942
- artifacts have to be correct; intermediate commits' generated files are throwaway.
943
- The counterpart source lesson: keep the deterministic *source* in the diff and
944
- treat committed build outputs as regenerable, never as things to merge by hand.
945
-
946
- **Cost is a first-class design axis, and pure tools are the lever.** Hosted
947
- `execute` spins a billable isolate; the direct `render_svg`/`render_ascii`/
948
- `render_png`/`verify`/`describe` tools cost one ordinary Worker invocation and are
949
- edge-cacheable. Splitting the surface so the common render/verify paths never touch
950
- an isolate — plus a batch fan-out cap so one request cannot spawn N isolates — is
951
- what keeps a public endpoint affordable. On a metered platform, "which calls are
952
- free" is part of the API design, not an afterthought.
953
-
954
- ## Agent-edit boundary lesson — a compile-time guarantee is only as strong as the runtime boundary that re-checks it
955
-
956
- The typed `mutate` surface is correctness-by-construction — *for callers the
957
- compiler checks*. But agents reach it through untyped JSON (MCP, CLI, Code Mode),
958
- where there is no compiler, so the guarantee silently degrades to unchecked: an op
959
- like `{ kind:"add_class", name:"Duck" }` (using `name` where `id` is expected)
960
- slipped past and the mutator produced `class undefined`. A live eval with a weak
961
- model is what surfaced it; the fix and the way we validated it generalize.
962
-
963
- **Validate at the choke point where types are lost, not everywhere and not
964
- nowhere.** The naive options were both wrong: folding shape validation into the
965
- low-level `mutate()` makes every compiler-checked internal caller pay for (and risk
966
- false-rejection from) a check it doesn't need; leaving it out entirely is the bug.
967
- The right placement is a single `mutateChecked` at the *trust boundary* — the one
968
- function every untyped path (declarative `applyOps`/`build`, the Code Mode facade,
969
- the CLI `--ops`) funnels through — so a bad op is rejected identically no matter how
970
- it arrives, and the typed path stays untouched. "Where do the guarantees stop being
971
- free?" is the question that locates the check.
972
-
973
- **Ground "buy vs build" in the actual code, not the abstract capability.** The MCP
974
- TS SDK + Zod were the obvious "buy" for validation/discovery — until we read the
975
- code they'd land in: the hosted server is hand-rolled JSON-RPC precisely so it runs
976
- runtime-neutral on workerd and stays edge-cacheable, and the ~90 op types are
977
- hand-written unions. Both the SDK transport and a Zod rewrite fight that
978
- architecture, while the *capabilities* they promised (input validation, in-band
979
- `isError` self-correction, `tools/list` discovery, declarative tools) were
980
- deliverable natively in a day. The decision flipped only once the plan was grounded
981
- in the files it would touch.
982
-
983
- **Marshal at the boundary you own, and build the shared DTO first.** Code Mode
984
- `return diagram` failed `non-serializable` because the SDK object is a hardened
985
- provenance proxy over Maps. Building the canonical `{ ok, family, source, verify }`
986
- envelope *first* made the marshalling fix fall out for free — the host-side
987
- marshaller just reuses that envelope, so `return d` and the declarative tool now
988
- emit the identical shape. That also settled a build-vs-buy: a bespoke one-way
989
- serializer beat adopting Cap'n Web / Cloudflare Code Mode, which invert the sync
990
- model, are Workers-only, and rebuild IP (the provenance set, the determinism) we
991
- already own. Reach for the RPC framework only when you actually need bidirectional
992
- stubs; a single return-marshal is not that.
993
-
994
- **A grader drifts as the product grows; a correct-but-new path reads as failure.**
995
- Re-running the eval after adding the declarative tools, a *correct* Haiku answer
996
- scored `traceOk:false` — the grader credited only `am verify`, not the now-endorsed
997
- `am mutate`/MCP `mutate`/`applyOps` path, which verify internally. A second case
998
- false-failed on an oracle that exact-matched `+speak()` and rejected a valid
999
- `+speak() void`. Fixing false-negatives is not gaming the metric — it is what keeps
1000
- the metric measuring capability — but the discipline is to *separate* them from
1001
- genuine model misses (a dropped `done` label, an absolute date where `after core`
1002
- was asked) and leave those failing. Confirm the split by running a stronger model:
1003
- Opus passed exactly the cases Haiku missed, proving they were capability, not
1004
- tooling.
1005
-
1006
- **A claim guarded by one test silently drifts; guard the invariant.** Adding the
1007
- tools updated `start.md` (which a doc-sync test pins) but left the canonical agent
1008
- guide saying "six tools" — untested, so it rotted. The fix was not just to correct
1009
- the number but to add a test asserting the guide names *every* `HOSTED_TOOLS` entry,
1010
- so the next tool can't drift it. Pin the property, not the instance.
1011
-
1012
- **Fuzz the untyped boundary — that is precisely what fuzzing is for, and the
1013
- discriminating invariant is "success implies validity."** Example tests proved the
1014
- fix on the cases we imagined; only property fuzz (arbitrary malformed ops across all
1015
- 12 families) proved the two things that actually matter at a hostile boundary: it
1016
- never throws, and *a successful apply implies the op passed shape validation* — the
1017
- machine statement of "no silent mangle." That invariant is what goes red when the
1018
- validator is removed, which is how you know the fuzz is discriminating and not a
1019
- tautology.