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.
- package/AGENT_NATIVE.md +155 -91
- package/CHANGELOG.md +163 -4
- package/Instructions_for_agents.md +10 -10
- package/LICENSES/Apache-2.0.txt +202 -0
- package/README.md +31 -38
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/assets/hero.png +0 -0
- package/dist/agent-core.d.ts +463 -0
- package/dist/agent-core.js +397 -0
- package/dist/agent.d.ts +73 -1868
- package/dist/agent.js +336 -37325
- package/dist/agentic-mermaid-mcp.js +7 -38399
- package/dist/am.js +33626 -40446
- package/dist/chunk-2P5FKUQC.js +3485 -0
- package/dist/chunk-2S3VSS6I.js +149 -0
- package/dist/chunk-2SILJDW2.js +37 -0
- package/dist/chunk-BJYMBT4I.js +61963 -0
- package/dist/chunk-FDM43XU7.js +615 -0
- package/dist/chunk-MYPDVZCO.js +4702 -0
- package/dist/index-DWPa2MJs.d.ts +6780 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +310 -33014
- package/dist/mcp-cli-5J47WEY3.js +17 -0
- package/docs/README.md +39 -31
- package/docs/agent-api-cookbook.md +44 -35
- package/docs/agent-mutation-policy.md +1 -1
- package/docs/agent-workflow-examples.md +2 -2
- package/docs/api.md +284 -77
- package/docs/ascii.md +26 -16
- package/docs/code-mode-impact.md +214 -0
- package/docs/comparison.md +23 -11
- package/docs/computational-aesthetics-prototype-plan.md +252 -0
- package/docs/config.md +26 -10
- package/docs/custom-fonts.md +265 -0
- package/docs/custom-style-cookbook.md +130 -9
- package/docs/diagram-families.md +48 -23
- package/docs/features.md +59 -52
- package/docs/fork-differences.md +6 -3
- package/docs/getting-started.md +23 -16
- package/docs/issue-derived-test-cases.md +29 -22
- package/docs/mcp-code-mode-rationale.md +13 -6
- package/docs/mcp-http-transport.md +20 -13
- package/docs/mermaid-layout-complaints.md +66 -52
- package/docs/mutation-testing.md +145 -66
- package/docs/quality.md +23 -14
- package/docs/react.md +2 -2
- package/docs/schemas/style-spec.schema.json +1221 -30
- package/docs/style-authoring.md +241 -38
- package/docs/svg-semantic-contract.md +49 -0
- package/docs/testing-strategy.md +166 -58
- package/docs/theming.md +19 -32
- package/examples/styles/catalog.json +54 -0
- package/examples/styles/cloudflare-workers-inspired-prototype.mmd +9 -0
- package/examples/styles/cloudflare-workers-inspired-prototype.style.json +110 -0
- package/examples/styles/cupertino-prototype.mmd +10 -0
- package/examples/styles/cupertino-prototype.style.json +63 -0
- package/examples/styles/mid-century-report.style.json +2 -2
- package/examples/styles/star-chart-atlas.style.json +2 -1
- package/examples/styles/transit-route-map.style.json +2 -2
- package/examples/styles/vercel-inspired-prototype.mmd +6 -0
- package/examples/styles/vercel-inspired-prototype.style.json +87 -0
- package/package.json +85 -45
- package/server.json +2 -2
- package/skills/agentic-mermaid-diagram-workflow/SKILL.md +17 -36
- package/skills/agentic-mermaid-diagram-workflow/references/cli.md +4 -5
- package/skills/agentic-mermaid-diagram-workflow/references/code-mode.md +15 -31
- package/skills/agentic-mermaid-diagram-workflow/references/sequence.md +10 -14
- package/skills/agentic-mermaid-live-editor/SKILL.md +100 -206
- package/TODO.md +0 -684
- package/dist/index-wjRjbsP-.d.ts +0 -1102
- package/docs/assets/improvements/box-start-connector.png +0 -0
- package/docs/assets/improvements/comment-dropped-lint.png +0 -0
- package/docs/assets/improvements/er-content-loss.png +0 -0
- package/docs/assets/improvements/fan-in-trunks.png +0 -0
- package/docs/assets/improvements/gantt-family.png +0 -0
- package/docs/assets/improvements/gantt-milestone-clamp.png +0 -0
- package/docs/assets/improvements/gantt-verify-seam.png +0 -0
- package/docs/assets/improvements/subgraph-container-edges.png +0 -0
- package/docs/assets/improvements/wrapper-frontmatter.png +0 -0
- package/docs/assets/improvements/wrapper-init-directive.png +0 -0
- package/docs/assets/improvements/wrapper-leading-comment.png +0 -0
- package/docs/assets/style-cookbook/mid-century-report.png +0 -0
- package/docs/assets/style-cookbook/star-chart-atlas.png +0 -0
- package/docs/assets/style-cookbook/transit-route-map.png +0 -0
- package/docs/contributing/adding-diagram-types.md +0 -144
- package/docs/contributing/diagram-family-citizenship.matrix.json +0 -2127
- package/docs/contributing/diagram-family-citizenship.md +0 -82
- package/docs/contributing/harvesting-upstream-tests.md +0 -126
- package/docs/contributing/lessons-learned.md +0 -276
- package/docs/contributing/releasing.md +0 -82
- package/docs/contributing/visual-review-evidence.md +0 -53
- package/docs/design/families/README.md +0 -26
- package/docs/design/families/architecture-beta.md +0 -48
- package/docs/design/families/flowchart-parser-conformance.md +0 -22
- package/docs/design/families/gantt-research.md +0 -137
- package/docs/design/families/gantt.md +0 -329
- package/docs/design/families/journey-agentic-cards-before.png +0 -0
- package/docs/design/families/journey-experience-curve-implementation.png +0 -0
- package/docs/design/families/journey-mermaid-classic-implementation.png +0 -0
- package/docs/design/families/journey-mermaid-classic-prototype.png +0 -0
- package/docs/design/families/journey-mermaid-classic-prototype.svg +0 -163
- package/docs/design/families/journey-mermaid-classic-prototype.svg.png +0 -0
- package/docs/design/families/journey-migration-parity.md +0 -439
- package/docs/design/families/journey-section-overlap-after.png +0 -0
- package/docs/design/families/journey-section-overlap-before.png +0 -0
- package/docs/design/families/journey-usage-research.md +0 -423
- package/docs/design/families/journey.md +0 -153
- package/docs/design/families/xychart.md +0 -85
- package/docs/design/system/README.md +0 -45
- package/docs/design/system/abstraction-audit.md +0 -224
- package/docs/design/system/abstraction-recommendations.md +0 -655
- package/docs/design/system/architecture.mmd +0 -29
- package/docs/design/system/architecture.svg +0 -137
- package/docs/design/system/layout-guarantees-and-robustness.md +0 -351
- package/docs/design/system/layout-rubric.md +0 -138
- package/docs/design/system/route-contracts.md +0 -848
- package/docs/design/system/source-preservation-ladder.md +0 -52
- package/docs/design/system/styles-rollout.md +0 -216
- package/docs/design/system/ugly-layouts.md +0 -119
- package/docs/layout-characterization/README.md +0 -245
- package/docs/layout-characterization/contact-sheet-families.md +0 -498
- package/docs/layout-characterization/contact-sheet.md +0 -638
- package/docs/layout-characterization/properties.md +0 -281
- package/docs/layout-characterization/visual-quality.md +0 -160
- package/docs/layout-characterization/visual-snapshots/architecture.svg +0 -79
- package/docs/layout-characterization/visual-snapshots/class.svg +0 -60
- package/docs/layout-characterization/visual-snapshots/er.svg +0 -61
- package/docs/layout-characterization/visual-snapshots/flowchart.svg +0 -58
- package/docs/layout-characterization/visual-snapshots/gantt.svg +0 -52
- package/docs/layout-characterization/visual-snapshots/journey.svg +0 -98
- package/docs/layout-characterization/visual-snapshots/pie.svg +0 -36
- package/docs/layout-characterization/visual-snapshots/quadrant.svg +0 -47
- package/docs/layout-characterization/visual-snapshots/sequence.svg +0 -59
- package/docs/layout-characterization/visual-snapshots/state.svg +0 -52
- package/docs/layout-characterization/visual-snapshots/timeline.svg +0 -64
- package/docs/layout-characterization/visual-snapshots/xychart.svg +0 -97
- package/docs/project/divergences.md +0 -98
- package/docs/project/dogfooding-docs-strategy.md +0 -66
- package/docs/project/hosted-mcp-cloudflare-plan.md +0 -338
- package/docs/project/lessons-learned.md +0 -1019
- package/docs/project/remove-role-styling-plan.md +0 -176
- package/examples/agent-improve-auth-flow.ts +0 -206
- package/examples/agent-loop.ts +0 -53
- package/examples/mcp-vs-cli-complex-diagrams.ts +0 -133
- package/llms.txt +0 -104
- package/skill-evals/README.md +0 -77
- package/skill-evals/fixtures/architecture-cache/input.mmd +0 -5
- package/skill-evals/fixtures/class-payment/input.mmd +0 -4
- package/skill-evals/fixtures/er-orders/input.mmd +0 -10
- package/skill-evals/fixtures/flowchart-cache/input.mmd +0 -3
- package/skill-evals/fixtures/gantt-release/input.mmd +0 -7
- package/skill-evals/fixtures/journey-checkout/input.mmd +0 -6
- package/skill-evals/fixtures/pie-exports/input.mmd +0 -5
- package/skill-evals/fixtures/quadrant-priorities/input.mmd +0 -11
- package/skill-evals/fixtures/sequence-retry/input.mmd +0 -8
- package/skill-evals/fixtures/state-lifecycle/input.mmd +0 -5
- package/skill-evals/fixtures/timeline-release/input.mmd +0 -6
- package/skill-evals/fixtures/xychart-growth/input.mmd +0 -5
- package/skill-evals/shared-benchmark.json +0 -510
- package/src/agent/analyze.ts +0 -157
- package/src/agent/apply.ts +0 -146
- package/src/agent/architecture-body.ts +0 -458
- package/src/agent/certificates.ts +0 -110
- package/src/agent/class-body.ts +0 -319
- package/src/agent/core.ts +0 -207
- package/src/agent/create.ts +0 -106
- package/src/agent/describe.ts +0 -368
- package/src/agent/er-body.ts +0 -224
- package/src/agent/facts.ts +0 -360
- package/src/agent/families-builtin.ts +0 -920
- package/src/agent/families.ts +0 -232
- package/src/agent/family-layouts.ts +0 -832
- package/src/agent/flowchart-body.ts +0 -372
- package/src/agent/flowchart-unsupported.ts +0 -243
- package/src/agent/font-coverage.ts +0 -288
- package/src/agent/gantt-body.ts +0 -495
- package/src/agent/index.ts +0 -10
- package/src/agent/journey-body.ts +0 -374
- package/src/agent/label-metrics.ts +0 -28
- package/src/agent/layout-to-rendered.ts +0 -63
- package/src/agent/mutate.ts +0 -97
- package/src/agent/mutation-ops.ts +0 -51
- package/src/agent/op-schema.ts +0 -443
- package/src/agent/parse.ts +0 -223
- package/src/agent/pie-body.ts +0 -270
- package/src/agent/png.ts +0 -171
- package/src/agent/quadrant-body.ts +0 -336
- package/src/agent/quality.ts +0 -363
- package/src/agent/readability-audit.ts +0 -92
- package/src/agent/rendered-route-audit.ts +0 -61
- package/src/agent/sequence-body.ts +0 -345
- package/src/agent/serialize.ts +0 -214
- package/src/agent/state-body.ts +0 -589
- package/src/agent/structural-count.ts +0 -150
- package/src/agent/timeline-body.ts +0 -280
- package/src/agent/trace-log.ts +0 -43
- package/src/agent/types.ts +0 -995
- package/src/agent/verify.ts +0 -603
- package/src/agent/xychart-body.ts +0 -472
- package/src/architecture/config.ts +0 -282
- package/src/architecture/layout.ts +0 -496
- package/src/architecture/parser.ts +0 -375
- package/src/architecture/renderer.ts +0 -611
- package/src/architecture/types.ts +0 -113
- package/src/ascii/ansi.ts +0 -423
- package/src/ascii/architecture.ts +0 -210
- package/src/ascii/canvas.ts +0 -443
- package/src/ascii/class-diagram.ts +0 -697
- package/src/ascii/converter.ts +0 -475
- package/src/ascii/draw.ts +0 -1693
- package/src/ascii/edge-bundling.ts +0 -328
- package/src/ascii/edge-routing.ts +0 -330
- package/src/ascii/er-diagram.ts +0 -439
- package/src/ascii/gantt.ts +0 -276
- package/src/ascii/grid.ts +0 -747
- package/src/ascii/index.ts +0 -196
- package/src/ascii/journey.ts +0 -169
- package/src/ascii/meta.ts +0 -244
- package/src/ascii/multiline-utils.ts +0 -80
- package/src/ascii/pathfinder.ts +0 -301
- package/src/ascii/pie.ts +0 -67
- package/src/ascii/quadrant.ts +0 -158
- package/src/ascii/reverse.ts +0 -216
- package/src/ascii/sequence.ts +0 -499
- package/src/ascii/shapes/circle.ts +0 -27
- package/src/ascii/shapes/corners.ts +0 -143
- package/src/ascii/shapes/diamond.ts +0 -27
- package/src/ascii/shapes/hexagon.ts +0 -27
- package/src/ascii/shapes/index.ts +0 -106
- package/src/ascii/shapes/rectangle.ts +0 -168
- package/src/ascii/shapes/rounded.ts +0 -27
- package/src/ascii/shapes/special.ts +0 -332
- package/src/ascii/shapes/stadium.ts +0 -110
- package/src/ascii/shapes/state.ts +0 -192
- package/src/ascii/shapes/types.ts +0 -73
- package/src/ascii/timeline.ts +0 -141
- package/src/ascii/types.ts +0 -312
- package/src/ascii/validate.ts +0 -120
- package/src/ascii/width.ts +0 -81
- package/src/ascii/wrap.ts +0 -131
- package/src/ascii/xychart.ts +0 -919
- package/src/browser.ts +0 -29
- package/src/class/layout.ts +0 -240
- package/src/class/parser.ts +0 -332
- package/src/class/renderer.ts +0 -638
- package/src/class/types.ts +0 -129
- package/src/cli/agent-instructions.ts +0 -1
- package/src/cli/am-bin.ts +0 -6
- package/src/cli/exit-codes.ts +0 -18
- package/src/cli/index.ts +0 -1132
- package/src/cli/init-agent.ts +0 -147
- package/src/cli/run-entrypoint.ts +0 -10
- package/src/color-resolver.ts +0 -152
- package/src/elk-instance.ts +0 -114
- package/src/er/layout.ts +0 -187
- package/src/er/parser.ts +0 -226
- package/src/er/renderer.ts +0 -792
- package/src/er/types.ts +0 -99
- package/src/family-rubric.ts +0 -425
- package/src/font-manifest.ts +0 -31
- package/src/gantt/layout.ts +0 -365
- package/src/gantt/parser.ts +0 -282
- package/src/gantt/pipeline.ts +0 -27
- package/src/gantt/renderer.ts +0 -414
- package/src/gantt/schedule.ts +0 -513
- package/src/gantt/types.ts +0 -255
- package/src/index.ts +0 -368
- package/src/journey/layout.ts +0 -554
- package/src/journey/parse-core.ts +0 -251
- package/src/journey/parser.ts +0 -101
- package/src/journey/renderer.ts +0 -917
- package/src/journey/types.ts +0 -144
- package/src/layout/geometry.ts +0 -182
- package/src/layout/pass.ts +0 -142
- package/src/layout/passes/index.ts +0 -3916
- package/src/layout-engine.ts +0 -1565
- package/src/layout-rubric.ts +0 -659
- package/src/mcp/artifacts.ts +0 -137
- package/src/mcp/dynamic-harness.ts +0 -31
- package/src/mcp/dynamic-modules.d.ts +0 -7
- package/src/mcp/facade.ts +0 -608
- package/src/mcp/harness-runtime.ts +0 -203
- package/src/mcp/hosted-server.ts +0 -468
- package/src/mcp/mcp-bin.ts +0 -3
- package/src/mcp/mcp-cli.ts +0 -82
- package/src/mcp/protocol.ts +0 -13
- package/src/mcp/sandbox.ts +0 -195
- package/src/mcp/sdk-decl.ts +0 -391
- package/src/mcp/server.ts +0 -362
- package/src/mcp/tool-surface.ts +0 -146
- package/src/mermaid-source.ts +0 -655
- package/src/multiline-utils.ts +0 -228
- package/src/parser.ts +0 -1031
- package/src/pie/layout.ts +0 -168
- package/src/pie/parser.ts +0 -136
- package/src/pie/renderer.ts +0 -234
- package/src/pie/types.ts +0 -82
- package/src/quadrant/layout.ts +0 -191
- package/src/quadrant/parser.ts +0 -183
- package/src/quadrant/renderer.ts +0 -322
- package/src/quadrant/types.ts +0 -113
- package/src/render-family-hooks.ts +0 -244
- package/src/renderer.ts +0 -1099
- package/src/route-contracts.ts +0 -2723
- package/src/scene/backend.ts +0 -76
- package/src/scene/fidelity.ts +0 -183
- package/src/scene/hybrid-backend.ts +0 -174
- package/src/scene/ir.ts +0 -181
- package/src/scene/marks.ts +0 -127
- package/src/scene/rough-backend.ts +0 -378
- package/src/scene/seed.ts +0 -46
- package/src/scene/style-registry.ts +0 -556
- package/src/sequence/layout.ts +0 -456
- package/src/sequence/parser.ts +0 -255
- package/src/sequence/renderer.ts +0 -604
- package/src/sequence/types.ts +0 -154
- package/src/shape-clipping.ts +0 -351
- package/src/shared/accessibility-directives.ts +0 -22
- package/src/shared/batched.ts +0 -61
- package/src/shared/color-math.ts +0 -93
- package/src/shared/svg-a11y.ts +0 -29
- package/src/shared/syntax-error.ts +0 -17
- package/src/shared/unicode-ranges.ts +0 -135
- package/src/styles.ts +0 -318
- package/src/svg-paths.ts +0 -28
- package/src/text-metrics.ts +0 -248
- package/src/theme.ts +0 -710
- package/src/timeline/layout.ts +0 -381
- package/src/timeline/parser.ts +0 -215
- package/src/timeline/renderer.ts +0 -538
- package/src/timeline/types.ts +0 -105
- package/src/types.ts +0 -462
- package/src/website-worker-modules.d.ts +0 -18
- package/src/xychart/axis-utils.ts +0 -109
- package/src/xychart/colors.ts +0 -124
- package/src/xychart/config.ts +0 -88
- package/src/xychart/layout.ts +0 -656
- package/src/xychart/parser.ts +0 -504
- package/src/xychart/renderer.ts +0 -640
- 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.
|