agentic-mermaid 0.1.2 → 0.3.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 +671 -0
- package/CHANGELOG.md +36 -0
- package/Instructions_for_agents.md +89 -0
- package/README.md +8 -8
- package/assets/hero.png +0 -0
- package/dist/agent-core.d.ts +2 -2
- package/dist/agent-core.js +2 -2
- package/dist/agent.d.ts +2 -2
- package/dist/agent.js +4 -4
- package/dist/agentic-mermaid-mcp.js +5 -5
- package/dist/am.js +8 -15
- package/dist/browser.global.js +777 -0
- package/dist/{chunk-WN3YDOBG.js → chunk-3QCXO4V6.js} +4 -4
- package/dist/{chunk-4QECACIA.js → chunk-AVCWKYII.js} +19 -19
- package/dist/{chunk-WI35L7IR.js → chunk-GILQUGFK.js} +2 -2
- package/dist/{chunk-QKDLMOJJ.js → chunk-HFD4MALA.js} +7 -9
- package/dist/{chunk-CDPKJ2BQ.js → chunk-UJNY65QS.js} +2 -2
- package/dist/{chunk-ZYMMKQ3I.js → chunk-WHYJLDYG.js} +299 -973
- package/dist/{index-D54dqSdC.d.ts → index-CLwYn6Im.d.ts} +7 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp-cli-SJVOGRA6.js +17 -0
- package/docs/README.md +35 -34
- package/docs/agent-api-cookbook.md +317 -0
- package/docs/agent-mutation-policy.md +104 -0
- package/docs/agent-workflow-examples.md +46 -0
- package/docs/api.md +418 -0
- package/docs/ascii.md +113 -0
- package/docs/browser.md +183 -0
- package/docs/code-mode-impact.md +214 -0
- package/docs/comparison.md +106 -0
- package/docs/computational-aesthetics-prototype-plan.md +252 -0
- package/docs/config.md +121 -0
- package/docs/custom-fonts.md +265 -0
- package/docs/custom-style-cookbook.md +275 -0
- package/docs/diagram-families.md +218 -0
- package/docs/features.md +154 -0
- package/docs/fork-differences.md +166 -0
- package/docs/getting-started.md +182 -0
- package/docs/issue-derived-test-cases.md +163 -0
- package/docs/mcp-code-mode-rationale.md +108 -0
- package/docs/mcp-http-transport.md +230 -0
- package/docs/mermaid-layout-complaints.md +851 -0
- package/docs/mutation-testing.md +285 -0
- package/docs/quality.md +224 -0
- package/docs/react.md +97 -0
- package/docs/style-authoring.md +336 -0
- package/docs/svg-semantic-contract.md +49 -0
- package/docs/testing-strategy.md +443 -0
- package/docs/theming.md +149 -0
- package/package.json +28 -9
- package/server.json +2 -2
- package/skills/agentic-mermaid-live-editor/SKILL.md +1 -1
- package/dist/mcp-cli-SBQSTJFC.js +0 -17
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
# Testing strategy — what we test, how, and why
|
|
2
|
+
|
|
3
|
+
This document describes the testing approach **as it exists today**: the
|
|
4
|
+
tools we run, what each one actually proves, and the reasoning behind the
|
|
5
|
+
shape of the suite. It is a map, not a wish list. Where a gate is weaker
|
|
6
|
+
than it looks, this document says so — an honest map is more useful than a
|
|
7
|
+
flattering one.
|
|
8
|
+
|
|
9
|
+
For the *definition* of "good looking" and the determinism guarantees, see
|
|
10
|
+
[`quality.md`](./quality.md). For mutation specifics, see
|
|
11
|
+
[`mutation-testing.md`](./mutation-testing.md). For the layout/visual
|
|
12
|
+
contracts, see [`layout-characterization/README.md`](./layout-characterization/README.md).
|
|
13
|
+
The current measured complexity-aware and registry-derived interaction
|
|
14
|
+
portfolio is specified in
|
|
15
|
+
[`project/complexity-aware-test-portfolio-plan.md`](./project/complexity-aware-test-portfolio-plan.md);
|
|
16
|
+
its immutable before and content-addressed candidate reports live under
|
|
17
|
+
`eval/test-portfolio/`.
|
|
18
|
+
|
|
19
|
+
## Local CI parity
|
|
20
|
+
|
|
21
|
+
Run `bun run quality:check` to execute the complete GitHub **Quality gates**
|
|
22
|
+
job locally. The command installs the frozen dependency graph and then runs
|
|
23
|
+
the dependency audit, font and website regeneration checks, evidence
|
|
24
|
+
freshness, sketch and whole-corpus audits, lint, repository-wide typechecking,
|
|
25
|
+
hero freshness, and the golden-drift guard. The workflow calls this same
|
|
26
|
+
entry point, so the local list and CI list cannot diverge.
|
|
27
|
+
|
|
28
|
+
Run `bun run evidence:check` for the narrower generated-evidence audit. It
|
|
29
|
+
executes every receipt-backed gallery plus palette-performance provenance,
|
|
30
|
+
continues after failures, and reports all stale artifacts together. Evidence
|
|
31
|
+
receipts hash their exact local source/fixture graph and the transitive Bun
|
|
32
|
+
lockfile closure of packages imported by that graph. Unrelated tools that are
|
|
33
|
+
present elsewhere in `bun.lock` do not invalidate visual evidence.
|
|
34
|
+
|
|
35
|
+
## The central problem: this is a partly non-testable program
|
|
36
|
+
|
|
37
|
+
Most of what this project does has a computable correct answer: did we drop
|
|
38
|
+
a node, does the source re-parse, is the output byte-identical across runs.
|
|
39
|
+
Those are easy to gate.
|
|
40
|
+
|
|
41
|
+
But the thing the product is *for* — a **readable, good-looking diagram** —
|
|
42
|
+
has no computable oracle. "Looks good" is not a function we can assert. In
|
|
43
|
+
the testing literature this is the **test oracle problem**, and a program
|
|
44
|
+
whose correctness can't be mechanically decided is a **"non-testable
|
|
45
|
+
program"** (Weyuker). Diagram aesthetics are the textbook case.
|
|
46
|
+
|
|
47
|
+
We do not pretend to solve this with one gate. The strategy is a
|
|
48
|
+
**portfolio of partial oracles**: many independent, mostly-cheap checks,
|
|
49
|
+
each catching a different class of regression, with the expensive
|
|
50
|
+
human/LLM judgment reserved for the axis nothing else can cover. The
|
|
51
|
+
sections below are organized by *oracle type* — the kind of correctness
|
|
52
|
+
each gate can actually establish — because knowing what proof a gate
|
|
53
|
+
provides is the whole game (Loop 19: "CI-green is not the same as
|
|
54
|
+
audit-clean").
|
|
55
|
+
|
|
56
|
+
## Oracle types and the gates that use them
|
|
57
|
+
|
|
58
|
+
| Oracle type | What it can prove | Our gates |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| **Specified** | Output matches an explicit contract | Tier-1 structural `verify`, capabilities/schema tests, doc-sync guards |
|
|
61
|
+
| **Derived (golden)** | Output matches a previously-blessed artifact | ASCII goldens, SVG snapshots, contact sheets, screenshot baselines |
|
|
62
|
+
| **Derived (differential)** | Our output agrees with an external reference corpus | mermaid-docs corpus, MermaidSeqBench, upstream-suite bench |
|
|
63
|
+
| **Metamorphic** | Related inputs produce related outputs (no ground truth needed) | round-trip idempotence, cross-process/cross-runtime determinism |
|
|
64
|
+
| **Pseudo / fault sensitivity** | Selected seeded faults make the intended tests fail | incremental Stryker gate, sabotage suite |
|
|
65
|
+
| **Heuristic / perceptual** | Geometry falls inside human-plausible bounds | `measureQuality`/`checkQuality`, ugly-detector, layout rubric, heuristic-tracker |
|
|
66
|
+
| **Human / model** | Subjective quality on the axis nothing else covers | LLM-as-judge (periodic), manual visual review |
|
|
67
|
+
|
|
68
|
+
The rest of this document walks each row: what it is, where it lives,
|
|
69
|
+
whether it gates per-PR, and what it does *not* prove.
|
|
70
|
+
|
|
71
|
+
## 1. Specified oracles — structural correctness
|
|
72
|
+
|
|
73
|
+
**Tier-1 `verify`** is the non-negotiable structural gate: no
|
|
74
|
+
`EMPTY_DIAGRAM`, `EDGE_MISANCHORED`, `OFF_CANVAS`, `GROUP_BREACH`,
|
|
75
|
+
`UNKNOWN_SHAPE`, or `LABEL_OVERFLOW`. It is reliable and universal across
|
|
76
|
+
families. **Tier-2** (geometric) and **Tier-3** (lint) are advisory.
|
|
77
|
+
|
|
78
|
+
Alongside it sit the **contract gates**: `am capabilities --json` schema
|
|
79
|
+
tests, `doc-sync`/`agent-doc-sync` tests, and the diagram-family
|
|
80
|
+
citizenship matrix. For an agent-native product the docs, schemas, CLI
|
|
81
|
+
help, and `llms.txt` *are* runtime surface (Loop 14), so they are tested
|
|
82
|
+
like code, not treated as prose.
|
|
83
|
+
|
|
84
|
+
**Runs:** every PR (`bun run test`, including the canonical 30-second per-test budget).
|
|
85
|
+
**Does not prove:** that the diagram is *visually* good. `verify.ok` is
|
|
86
|
+
structural only — stated repeatedly because it has burned us (the Auth
|
|
87
|
+
Flow episode, Loop 15).
|
|
88
|
+
|
|
89
|
+
## 2. Derived oracles — goldens and snapshots
|
|
90
|
+
|
|
91
|
+
Blessed-artifact comparison for output that is deterministic but has no
|
|
92
|
+
independent spec:
|
|
93
|
+
|
|
94
|
+
- **ASCII goldens** — `scripts/update-ascii-goldens.ts`, `testdata/{unicode,ascii}/*.txt`.
|
|
95
|
+
- **SVG snapshots** and **contact sheets** — per-family rendered output.
|
|
96
|
+
- **Screenshot baselines** — `e2e/screenshots/baseline-*.png`, diffed against
|
|
97
|
+
fresh Playwright renders with a per-channel threshold in `e2e/browser.test.ts`.
|
|
98
|
+
- **Snapshot drift sentinel** — CI flags any change under `testdata/` so a
|
|
99
|
+
golden never moves silently.
|
|
100
|
+
|
|
101
|
+
**Runs:** ASCII/SVG goldens per PR. The browser/screenshot e2e suite also runs
|
|
102
|
+
per PR — it is the five-lane `e2e` matrix in `ci.yml` (`needs: test`), including
|
|
103
|
+
an installed-tarball consumer run at the advertised Node 22 floor. Its
|
|
104
|
+
browser lane installs Chromium and runs `browser.test.ts` (screenshot diff)
|
|
105
|
+
plus the security contracts and the explicitly enabled theme/style/accessibility
|
|
106
|
+
browser suites. The browser lane builds the gitignored `website/public` tree once
|
|
107
|
+
before those contracts serve it (`browser.test.ts` builds only the separate root
|
|
108
|
+
`editor.html`). Each browser contract file then runs in its own Bun process so its
|
|
109
|
+
Chromium/server hooks cannot overlap and exhaust a constrained hosted runner. The
|
|
110
|
+
package and CI both invoke `e2e/run-browser-contracts.ts`, whose canonical file
|
|
111
|
+
list also fails if any contract executes zero positive tests, while independent
|
|
112
|
+
lanes run the CLI/single-binary, dist-artifact, and tarball-consumer suites.
|
|
113
|
+
Broad contact-sheet rendering is periodic/triggered rather than a per-PR sweep;
|
|
114
|
+
the committed citizenship manifest and pending review state remain cheap
|
|
115
|
+
per-PR contracts. Structured human review is recommended for visual releases
|
|
116
|
+
and remains hash-bound when recorded, but it is advisory rather than a package-publication gate.
|
|
117
|
+
**Does not prove:** that a *changed* golden is an improvement — only that
|
|
118
|
+
change was noticed. Judging the change still needs a human or the
|
|
119
|
+
before/after harness (`eval/layout-compare`).
|
|
120
|
+
|
|
121
|
+
## 2a. Registry-derived combinatorial conformance
|
|
122
|
+
|
|
123
|
+
`render-conformance-plan.test.ts` replaces the former exhaustive
|
|
124
|
+
family × Look × Palette docs-example matrix. The new family registry,
|
|
125
|
+
Style registries and mandatory family conformance profiles generate:
|
|
126
|
+
|
|
127
|
+
- exhaustive pure Look × Palette resolution;
|
|
128
|
+
- pairwise real-render coverage across family, Look, Palette, security,
|
|
129
|
+
background polarity and six complexity strata;
|
|
130
|
+
- higher-strength Look/Palette/background, family/backend/complexity,
|
|
131
|
+
palette-sensitive role, external-reference and contact-sheet obligations;
|
|
132
|
+
- a separate SVG/PNG/ASCII/Unicode portfolio with every-family Unicode stress.
|
|
133
|
+
|
|
134
|
+
An independently implemented tuple enumerator must report zero missing
|
|
135
|
+
obligations. Fake/removed-family, precedence, strict-reference, transparency,
|
|
136
|
+
seed and terminal sabotage probes guard fault sensitivity. A new `DiagramKind`
|
|
137
|
+
cannot compile without its minimal, representative, dense, text-stress,
|
|
138
|
+
family-risk and eval-corpus-outlier conformance material.
|
|
139
|
+
|
|
140
|
+
**Runs:** executable conformance and the bounded fault probes run per PR. The
|
|
141
|
+
larger citizenship/interaction/outlier contact sheets run when visual inputs
|
|
142
|
+
change, during periodic design review, or before release.
|
|
143
|
+
**Does not prove:** an unrendered three-way family/Look/Palette combination is
|
|
144
|
+
beautiful. The exact retained obligations are published by the candidate
|
|
145
|
+
report rather than implied by the word “exhaustive.”
|
|
146
|
+
|
|
147
|
+
## 3. Derived oracles — differential corpora
|
|
148
|
+
|
|
149
|
+
The strongest defense against our own blind spots is testing against
|
|
150
|
+
sources we did not write:
|
|
151
|
+
|
|
152
|
+
- **mermaid-docs corpus** (271 entries) — harvested from Mermaid's own
|
|
153
|
+
documentation; gates parse, verify, and round-trip.
|
|
154
|
+
- **MermaidSeqBench** (132 IBM-curated sequence diagrams) — parse rate,
|
|
155
|
+
structured-vs-opaque split, verify rate, round-trip stability.
|
|
156
|
+
- **upstream-suite bench** (`eval/mermaid-upstream-suite-bench/`, 736
|
|
157
|
+
executable cases / 746 imported blocks from pinned upstream commits) — with
|
|
158
|
+
a **ratchet** (`ratchet.json`) that fails the build if coverage drops or
|
|
159
|
+
per-family local-gap budgets grow. Mindmap/GitGraph use their declared
|
|
160
|
+
`f3dea583…` compatibility revision and account for all 26/69 official spec
|
|
161
|
+
blocks in a dedicated executable oracle.
|
|
162
|
+
|
|
163
|
+
These exist because our hand-written fixtures encode what we already knew
|
|
164
|
+
the parser modeled. Upstream examples are "adversarial in exactly the
|
|
165
|
+
right way" — they encode what real sources contain (wrapper-fidelity
|
|
166
|
+
lesson). The `@{shape}` silent-loss and ER `}o` bugs both escaped 2,200+
|
|
167
|
+
self-authored tests and were caught only by docs-derived sources.
|
|
168
|
+
|
|
169
|
+
**Runs:** every PR (corpus + seqbench + upstream-suite ratchet).
|
|
170
|
+
**Faithfulness count-oracle:** the corpus gate now also asserts that the
|
|
171
|
+
structured `{nodes, edges, groups}` tally survives a parse → serialize →
|
|
172
|
+
re-parse cycle for **every** renderable family
|
|
173
|
+
(`eval/shared/structural-count.ts`, gated in `agent-mermaid-corpus.test.ts`).
|
|
174
|
+
Round-trip *byte*-stability only proves serialize∘parse is idempotent; this
|
|
175
|
+
count check proves parse did not silently drop content — the ER `}o` class of
|
|
176
|
+
bug ("100% parse success is not faithfulness", Loop 17) now fails CI directly
|
|
177
|
+
rather than only where a hand-written oracle happened to bite.
|
|
178
|
+
|
|
179
|
+
## 4. Metamorphic oracles — relations instead of ground truth
|
|
180
|
+
|
|
181
|
+
When we can't say "the output is correct," we can say "these related runs
|
|
182
|
+
must agree." This sidesteps the oracle problem without a human:
|
|
183
|
+
|
|
184
|
+
- **Round-trip idempotence** — `parse → serialize → parse` is stable;
|
|
185
|
+
structured-mutation invariants via `fast-check` (the `property-*.test.ts`
|
|
186
|
+
suite).
|
|
187
|
+
- **Determinism** — three separate processes produce byte-identical layout
|
|
188
|
+
JSON; Bun and Node produce byte-identical JSON on the same source; ASCII
|
|
189
|
+
and PNG output hash-stable across 10+ repeated runs. CI builds the Node bundle
|
|
190
|
+
before the unit shards so the Bun↔Node contracts cannot silently skip in a
|
|
191
|
+
clean checkout.
|
|
192
|
+
- **Layout/faithfulness relations** — `property-layout-metamorphic.test.ts`
|
|
193
|
+
formalizes relations that were previously implicit: determinism (same source
|
|
194
|
+
⇒ identical metrics), node-id **relabeling invariance** (ids carry no
|
|
195
|
+
meaning), **disconnected-node monotonicity** (adding an isolated node adds
|
|
196
|
+
exactly one node and drops nothing), and **edge-add monotonicity**.
|
|
197
|
+
Statement-permutation invariance is deliberately *not* asserted — source
|
|
198
|
+
order is a stated design property, so permuting statements may legitimately
|
|
199
|
+
change geometry.
|
|
200
|
+
|
|
201
|
+
**Runs:** every PR.
|
|
202
|
+
**Does not prove:** cross-architecture equality (x86_64 hash vs ARM64 hash
|
|
203
|
+
is not compared). That is the natural next place to *extend* the metamorphic
|
|
204
|
+
set, since the technique is the same.
|
|
205
|
+
|
|
206
|
+
### Seed policy: pinned gates, rolling finders
|
|
207
|
+
|
|
208
|
+
Every fast-check suite runs at one pinned global seed, set process-wide by
|
|
209
|
+
`src/__tests__/fc-seed.preload.ts` (wired via `bunfig.toml` `[test].preload`;
|
|
210
|
+
default seed `20260702`). A red property in CI is therefore a real,
|
|
211
|
+
reproducible counterexample — never a seed lottery. The 2026-07 audit caught
|
|
212
|
+
three suites flaking on rolled seeds (the route-contracts certificate property
|
|
213
|
+
at ~1 CI run in 7, issue #83; the parser Cartesian-product property; both now
|
|
214
|
+
pin the exact seed that exposed their bug through per-assert options, without
|
|
215
|
+
mutating process-global configuration).
|
|
216
|
+
|
|
217
|
+
- Reproduce a specific roll: `AM_FC_SEED=<int> bun test <file>`.
|
|
218
|
+
- Finder mode (deliberate randomness): `AM_FC_SEED=random bun run test`.
|
|
219
|
+
- The policy is itself gated: `fc-seed-policy.test.ts` fails if the preload is
|
|
220
|
+
unwired and source-checks every test file so suite-specific seeds cannot use
|
|
221
|
+
process-global overrides in another shard; `zzz-fc-seed-policy-epilogue.test.ts`
|
|
222
|
+
also catches runtime drift within its own process.
|
|
223
|
+
|
|
224
|
+
Pinning is for *holding* known ground; randomness is for *finding* new
|
|
225
|
+
counterexamples ("an invariant enforced by a random property is a lottery,
|
|
226
|
+
not a gate" — `docs/contributing/lessons-learned.md`). Rolling seeds belong in
|
|
227
|
+
finder lanes: the deep-fuzz scripts under `eval/`, or deliberate
|
|
228
|
+
`AM_FC_SEED=random` sweeps. Before the pin was frozen, every unpinned suite was
|
|
229
|
+
swept across 48/24/12 seeds (scaled by runtime; 1,368 suite-runs total) with
|
|
230
|
+
zero failures — the pin does not freeze a known-bad ticket, and the sweep is
|
|
231
|
+
repeatable from the same knob.
|
|
232
|
+
|
|
233
|
+
## 5. Pseudo-oracles — do selected seeded faults make the suite fail?
|
|
234
|
+
|
|
235
|
+
Tests can be green while missing faults. Two bounded gates challenge selected
|
|
236
|
+
behaviors:
|
|
237
|
+
|
|
238
|
+
- **Incremental mutation testing (Stryker)** — mutates the small, pure
|
|
239
|
+
faithfulness counter on every PR. A full run takes about one minute and its
|
|
240
|
+
measured floor is enforced. Broader ASCII, route, and family configs remain
|
|
241
|
+
available as opt-in survivor harvests, not scheduled gates. A *survived*
|
|
242
|
+
mutant is a test gap until classified; the historical catalogs record
|
|
243
|
+
reviewed performance guards and unreachable-by-convention cases. Mutation
|
|
244
|
+
testing has earned its keep when focused: it falsified an audit assumption
|
|
245
|
+
and found dead code the unit tests couldn't.
|
|
246
|
+
- **Sabotage suite** (`eval/sabotage/route-regressions.ts`) — deliberately
|
|
247
|
+
reverts a fixed bug in a detached worktree and asserts the suite goes
|
|
248
|
+
**red**, proving five named route/link regression tests actually bite.
|
|
249
|
+
|
|
250
|
+
The broad scheduled mutation matrix was retired after 26 consecutive scheduled
|
|
251
|
+
runs produced no success and its final repair grew to 39 coverage workers (41
|
|
252
|
+
jobs total). The configs still
|
|
253
|
+
emit useful local scores and JSON reports, but have no break floors and carry no
|
|
254
|
+
acceptance authority. `docs/mutation-testing.md` records the commands, survivor
|
|
255
|
+
history, retained operational evidence, and re-enrollment criteria.
|
|
256
|
+
|
|
257
|
+
**Why this matters:** line coverage is reported per-PR as one merged LCOV
|
|
258
|
+
artifact assembled from the three unit shards, but it is a weak
|
|
259
|
+
adequacy signal — coverage is not strongly correlated with fault detection
|
|
260
|
+
once suite size is controlled (Inozemtseva & Holmes, ICSE 2014), whereas
|
|
261
|
+
mutant detection *is* correlated with real-fault detection (Just et al.,
|
|
262
|
+
FSE 2014). That supports focused mutation as one useful instrument, not mutation
|
|
263
|
+
score as a universal truth. Behavioral, property, differential, sabotage,
|
|
264
|
+
coverage, and mutation evidence answer different questions.
|
|
265
|
+
|
|
266
|
+
## 6. Heuristic / perceptual oracles — geometry inside human bounds
|
|
267
|
+
|
|
268
|
+
This is where we approximate aesthetics deterministically:
|
|
269
|
+
|
|
270
|
+
- **`measureQuality` / `checkQuality`** — edge crossings, label
|
|
271
|
+
legibility, whitespace balance, label-edge proximity, aspect ratio,
|
|
272
|
+
gated by `QualityBounds`. Cheap and deterministic; covers every registered
|
|
273
|
+
renderable family via `RenderedLayout` adapters.
|
|
274
|
+
- **ugly-detector** (`eval/ugly-detector/`) — geometric defect detection:
|
|
275
|
+
diagonal segments, floating endpoints, edges through nodes, hitches.
|
|
276
|
+
- **layout rubric / visual-rubric** — hard violations (must be 0) plus soft
|
|
277
|
+
thresholds (crossings, bends, port-anchored rate).
|
|
278
|
+
- **heuristic-tracker** — baseline-comparison of routing metrics with
|
|
279
|
+
improvement/regression deltas, now gated per PR (`heuristic-tracker.test.ts`):
|
|
280
|
+
hard violations must stay 0 and no tracked example may regress on a soft
|
|
281
|
+
metric without a reviewed `baseline.json` update in the same change.
|
|
282
|
+
- **route-contract tripwires** — `ROUTE_*` codes that must stay 0; any hit
|
|
283
|
+
means the layout pipeline regressed, not the diagram.
|
|
284
|
+
|
|
285
|
+
**Runs:** `measureQuality`, ugly-detector, the layout rubric, and the
|
|
286
|
+
heuristic-tracker ratchet all gate per PR.
|
|
287
|
+
**Bound provenance (Move 6):** each `QualityBounds` band now carries an
|
|
288
|
+
explicit basis — `edgeCrossings` is `evidence` (the one aesthetic with strong
|
|
289
|
+
human-subject support: Purchase 1997/2002), `labelLegibility` is `derived`
|
|
290
|
+
(labels must physically fit), `whitespaceBalance`/`labelEdgeProximity` are
|
|
291
|
+
`chosen` (plausible but unvalidated), and `aspectRatio` is a `sanity`
|
|
292
|
+
guardrail. `checkQuality` returns `ranked` violations tagged primary /
|
|
293
|
+
secondary / sanity so a consumer weights a crossings violation above a
|
|
294
|
+
whitespace one (`BOUND_PROVENANCE` in `src/agent/quality.ts`).
|
|
295
|
+
**Does not prove:** that the diagram is good to a *human* eye. The bands
|
|
296
|
+
are honest approximations with headroom — "we do not claim our metrics
|
|
297
|
+
match a human designer's eye; they catch the worst regressions"
|
|
298
|
+
(`quality.md`). Provenance makes the calibration honest; it does not make a
|
|
299
|
+
`chosen` band correct.
|
|
300
|
+
|
|
301
|
+
## 7. Human / model oracles — the axis nothing else covers
|
|
302
|
+
|
|
303
|
+
- **LLM-as-judge** (`eval/llm-judge/judge.ts`) — scores readability,
|
|
304
|
+
faithfulness, aesthetics (1–5) on a stratified corpus sample.
|
|
305
|
+
- **Manual visual review** — required reproducible artifacts for
|
|
306
|
+
layout/rendering changes (`contributing/visual-review-evidence.md`).
|
|
307
|
+
|
|
308
|
+
**Runs:** the real LLM judge is **periodic / pre-release only** — model
|
|
309
|
+
spend plus nondeterminism make it unfit for a per-PR gate. In CI it is
|
|
310
|
+
replaced by a deterministic mock.
|
|
311
|
+
**Protocol hardening (Move 1):** the readability/aesthetics axes of the CI
|
|
312
|
+
mock are still derived from the perceptual metrics (it is a wiring stub), but
|
|
313
|
+
the **faithfulness** axis now comes from `independentFaithfulness()` — a
|
|
314
|
+
structural parse → serialize → re-parse count check that does *not* consult
|
|
315
|
+
`measureQuality`, removing the circularity on that axis. The real judge has
|
|
316
|
+
primitives for the documented LLM-judge biases (Zheng et al., NeurIPS 2023):
|
|
317
|
+
`judgePairwiseDebiased` scores both orders and trusts only an agreeing verdict
|
|
318
|
+
(position bias), `assertJudgeIndependence` refuses a judge from the same model
|
|
319
|
+
family that authored the diagram (self-enhancement), and `JudgeReference`
|
|
320
|
+
threads a golden anchor (reference-guided scoring).
|
|
321
|
+
**Known limitation:** the mock's readability/aesthetics axes remain
|
|
322
|
+
metric-derived, so they cannot independently validate those metrics — only a
|
|
323
|
+
real judge run can.
|
|
324
|
+
|
|
325
|
+
## What runs where
|
|
326
|
+
|
|
327
|
+
`ci.yml` is the source of truth for what gates each PR — read it rather than a
|
|
328
|
+
table here, which would drift. In broad strokes:
|
|
329
|
+
|
|
330
|
+
- **Per-PR (`ci.yml`):** the three-shard unit/property suite (`bun run test`) —
|
|
331
|
+
Tier-1 verify, goldens, the differential + faithfulness + metamorphic gates,
|
|
332
|
+
`measureQuality`/whole-corpus ugly-detector/layout-rubric, the heuristic-tracker ratchet,
|
|
333
|
+
the corpus/seqbench/upstream benches — plus the high/critical dependency audit,
|
|
334
|
+
type check, the hero check, the
|
|
335
|
+
golden-drift gate, the parallel browser/CLI/binary/fuzz e2e matrix, the fast
|
|
336
|
+
incremental mutation lane, and the independent focused sabotage lane. A final
|
|
337
|
+
`CI complete` job waits for the required test aggregate, every E2E matrix job,
|
|
338
|
+
and mutation, providing one protectable result that cannot turn green early.
|
|
339
|
+
- **Manual / periodic:** opt-in broad Stryker survivor harvests,
|
|
340
|
+
`layout-compare` before/after, the benchmark vs competitors, and the real
|
|
341
|
+
LLM-as-judge run.
|
|
342
|
+
|
|
343
|
+
## Boundary-contract matrix — lessons applied from the 2026-07 agent audit
|
|
344
|
+
|
|
345
|
+
The PR #162 audit exposed a blind spot that family/layout coverage did not
|
|
346
|
+
address: a contract could be correct in one helper and false at another public
|
|
347
|
+
boundary. The review of
|
|
348
|
+
[`adewale/testing-best-practices`](https://github.com/adewale/testing-best-practices)
|
|
349
|
+
led to four concrete changes in how this suite treats those seams:
|
|
350
|
+
|
|
351
|
+
| Risk boundary | Previous false confidence | Required evidence now |
|
|
352
|
+
|---|---|---|
|
|
353
|
+
| JSON-RPC wire lexemes | hosted integer example only | one shared exact-id codec; property tests over unsafe integer, decimal, exponent, batch, nested-id, sentinel-collision, and malformed-text cases; real local HTTP and installed stdio-bin checks |
|
|
354
|
+
| CLI bootstrap/package | formatter helper unit test | walking-skeleton E2E against the real `am` and MCP source entrypoints in a copied checkout with Bun auto-install explicitly disabled |
|
|
355
|
+
| cache before dispatch | normalized-key examples | non-idempotent `execute` is uncacheable; remaining keys retain the complete raw argument object so a warm valid request cannot mask an invalid one; warm-cache regression tests inject the collision |
|
|
356
|
+
| docs/catalog claims | selected-subset presence checks | ordered exact equality against runtime registries, uniqueness, and existence of every catalog target; public `llms.txt` is checked after generation |
|
|
357
|
+
| sequence semantics | global message count | one shared branch-aware message-context model feeds describe, facts, verify/layout and typed edits; unsupported nested semantics must emit a warning |
|
|
358
|
+
| sockets/proxies/chunks | helper could return early | socket tests fail when bind/startup fails, proxy-origin SSE is read from the real stream, and split UTF-8 chunks are exercised at the byte boundary |
|
|
359
|
+
|
|
360
|
+
These follow the research's trust-boundary lens, walking-skeleton smoke tests,
|
|
361
|
+
property-based parser contracts, exact doc-sync, fault injection, and
|
|
362
|
+
correctness-by-construction guidance. Shared validators/codecs/context models
|
|
363
|
+
replace repeated per-layer checks; tests remain at the untyped HTTP/stdio/CLI
|
|
364
|
+
boundaries where TypeScript cannot enforce the wire contract.
|
|
365
|
+
|
|
366
|
+
The dated provenance and prevention analysis, including the reviews of PRs
|
|
367
|
+
#157 and #160, is recorded in
|
|
368
|
+
[`project/agent-interface-contract-audit-2026-07.md`](./project/agent-interface-contract-audit-2026-07.md).
|
|
369
|
+
|
|
370
|
+
The validation rule for future cross-surface changes is: enumerate every
|
|
371
|
+
public consumer first, then require at least one non-vacuous proof at the
|
|
372
|
+
lowest honest tier and one shipped-boundary proof where packaging, transport,
|
|
373
|
+
or generation can change the result. A test that silently returns because its
|
|
374
|
+
environment is unavailable is not evidence for a CI-required capability.
|
|
375
|
+
|
|
376
|
+
## Why the suite is shaped this way
|
|
377
|
+
|
|
378
|
+
- **Cheap-and-deterministic gates per PR; expensive-and-noisy gates
|
|
379
|
+
off it.** Anything that is slow (full mutation), costly (live model), or
|
|
380
|
+
nondeterministic (real LLM judge, timing benchmarks) is deliberately not
|
|
381
|
+
a merge blocker, because a flaky or budget-dominating gate trains people
|
|
382
|
+
to ignore it.
|
|
383
|
+
- **Differential corpora over hand-written fixtures for compatibility
|
|
384
|
+
claims.** Self-authored tests share the author's blind spots; sources we
|
|
385
|
+
did not write do not.
|
|
386
|
+
- **Metamorphic relations where there is no oracle.** Determinism and
|
|
387
|
+
round-trip laws assert correctness without a blessed answer.
|
|
388
|
+
- **Mutation/sabotage to keep the suite honest**, because green tests and a
|
|
389
|
+
high coverage number are not the same as a suite that detects faults.
|
|
390
|
+
- **Layered verification (Tier 1/2/3)** so the reliable structural signal
|
|
391
|
+
is never diluted by the advisory geometric/lint signals.
|
|
392
|
+
|
|
393
|
+
## Honest gaps (current, not aspirational)
|
|
394
|
+
|
|
395
|
+
These are real today and are the natural targets for *enhancing* existing
|
|
396
|
+
gates rather than adding new machinery:
|
|
397
|
+
|
|
398
|
+
1. The per-PR aesthetic signal is still the weakest gate; the metrics are
|
|
399
|
+
admittedly rough. The CI LLM mock's readability/aesthetics axes remain
|
|
400
|
+
metric-derived (only its faithfulness axis is now independent), so it
|
|
401
|
+
cannot validate those metrics — only a real periodic judge run can.
|
|
402
|
+
2. Automatic mutation assurance is deliberately narrow: PR feedback uses the
|
|
403
|
+
fast incremental faithfulness-counter lane plus five focused sabotage probes.
|
|
404
|
+
Broad mutation configs are manual diagnostics because the retired scheduled
|
|
405
|
+
matrix did not justify its runner and maintenance cost. Line coverage is
|
|
406
|
+
framed as a finder, not a headline score.
|
|
407
|
+
3. The benchmark is not on the PR gate (timing variance). Browser/screenshot
|
|
408
|
+
e2e and the heuristic-tracker ratchet now are.
|
|
409
|
+
4. Determinism is proven Bun↔Node on one architecture; cross-architecture
|
|
410
|
+
byte equality is not asserted.
|
|
411
|
+
5. `QualityBounds` thresholds are now provenance-tagged, but the `chosen`
|
|
412
|
+
bands are still not validated against human-perception evidence.
|
|
413
|
+
6. Golden movement still needs human judgment, but the snapshot drift sentinel
|
|
414
|
+
blocks unless the change is committed with the explicit `[approve-goldens]`
|
|
415
|
+
review token; the token proves acknowledgment, not visual quality.
|
|
416
|
+
|
|
417
|
+
The deepest gap is structural, and `project/lessons-learned.md` (Loop 13)
|
|
418
|
+
names it: every quality signal here is self-generated. The portfolio above
|
|
419
|
+
produces breadth and internal consistency; it cannot substitute for a real
|
|
420
|
+
external consumer. One boundary is now an exception: the MCP transport is
|
|
421
|
+
exercised by the reference `@modelcontextprotocol/sdk` client
|
|
422
|
+
(`src/__tests__/mcp-client-interop.test.ts`, per
|
|
423
|
+
`project/mcp-client-interop-verification-plan.md`) — a consumer we did not
|
|
424
|
+
write — over a real socket (hosted) and a real subprocess (stdio). That
|
|
425
|
+
closes the self-generated loop for the one boundary where a canonical
|
|
426
|
+
external consumer exists off the shelf; every other signal above remains
|
|
427
|
+
self-generated.
|
|
428
|
+
|
|
429
|
+
## References
|
|
430
|
+
|
|
431
|
+
- Barr, Harman, McMinn, Shahbaz, Yoo — *The Oracle Problem in Software
|
|
432
|
+
Testing: A Survey*, IEEE TSE 2015.
|
|
433
|
+
- Chen et al. — *Metamorphic Testing: A Review of Challenges and
|
|
434
|
+
Opportunities*, ACM Computing Surveys 2018.
|
|
435
|
+
- Purchase — *Which Aesthetic Has the Greatest Effect on Human
|
|
436
|
+
Understanding?* (GD 1997) and *Metrics for Graph Drawing Aesthetics*
|
|
437
|
+
(JVLC 2002).
|
|
438
|
+
- Inozemtseva & Holmes — *Coverage Is Not Strongly Correlated with Test
|
|
439
|
+
Suite Effectiveness*, ICSE 2014.
|
|
440
|
+
- Just et al. — *Are Mutants a Valid Substitute for Real Faults in Software
|
|
441
|
+
Testing?*, FSE 2014.
|
|
442
|
+
- Zheng et al. — *Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena*,
|
|
443
|
+
NeurIPS 2023.
|
package/docs/theming.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Palettes and theming
|
|
2
|
+
|
|
3
|
+
Agentic Mermaid themes start from a two-color contract (`bg`, `fg`) and can be enriched with optional semantic slots. This keeps diagrams usable in live editors, documentation pages, and agent-generated artifacts without requiring a large design-token object.
|
|
4
|
+
|
|
5
|
+
> **Palette is the registry kind.** Every built-in theme registers as a
|
|
6
|
+
> palette-only *style*, so its stable input name works anywhere a style is accepted and
|
|
7
|
+
> composes with full looks by stacking: `{ style: ['hand-drawn', 'dracula'] }`
|
|
8
|
+
> renders hand-drawn geometry with the Dracula palette. See
|
|
9
|
+
> [docs/style-authoring.md](./style-authoring.md) for the full model.
|
|
10
|
+
|
|
11
|
+
## Two-color foundation
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { renderMermaidSVG } from 'agentic-mermaid'
|
|
15
|
+
|
|
16
|
+
const svg = renderMermaidSVG(source, {
|
|
17
|
+
bg: '#0B1020',
|
|
18
|
+
fg: '#E5E7EB',
|
|
19
|
+
})
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
From those colors the renderer derives contrast-aware node fills, labels, edges, and accents. This is the safest default for agents: pick readable foreground/background colors and let the renderer complete the palette.
|
|
23
|
+
|
|
24
|
+
## Enriched mode
|
|
25
|
+
|
|
26
|
+
Use extra slots when your product already has semantic tokens:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const svg = renderMermaidSVG(source, {
|
|
30
|
+
bg: 'var(--canvas)',
|
|
31
|
+
fg: 'var(--text)',
|
|
32
|
+
line: 'var(--line)',
|
|
33
|
+
accent: 'var(--accent)',
|
|
34
|
+
muted: 'var(--muted)',
|
|
35
|
+
surface: 'var(--surface)',
|
|
36
|
+
border: 'var(--border)',
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Slots:
|
|
41
|
+
|
|
42
|
+
| Slot | Meaning |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `bg` | SVG canvas/background. |
|
|
45
|
+
| `fg` | Primary text. |
|
|
46
|
+
| `line` | Connectors and strokes. |
|
|
47
|
+
| `accent` | Arrowheads and emphasis. |
|
|
48
|
+
| `muted` | Secondary labels. |
|
|
49
|
+
| `surface` | Node/card fill. |
|
|
50
|
+
| `border` | Node/card border. |
|
|
51
|
+
|
|
52
|
+
## CSS custom properties
|
|
53
|
+
|
|
54
|
+
CSS variables are first-class values:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const svg = renderMermaidSVG(source, {
|
|
58
|
+
bg: 'var(--diagram-bg)',
|
|
59
|
+
fg: 'var(--diagram-fg)',
|
|
60
|
+
accent: 'var(--diagram-accent)',
|
|
61
|
+
embedFontImport: false,
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Because the generated SVG references variables directly, host pages can switch light/dark themes without re-rendering the diagram.
|
|
66
|
+
|
|
67
|
+
## Built-in Palettes
|
|
68
|
+
|
|
69
|
+
`theme` is not a registry kind; discovery returns `kind: "palette"`.
|
|
70
|
+
Discover current Palette identities and stable input names with
|
|
71
|
+
`knownStyleDescriptors()` or `am styles`; this guide does not copy the registry.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { renderMermaidSVG } from 'agentic-mermaid'
|
|
75
|
+
|
|
76
|
+
const svg = renderMermaidSVG(source, { style: 'tokyo-night' })
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Custom themes
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import type { DiagramColors } from 'agentic-mermaid'
|
|
83
|
+
|
|
84
|
+
export const myTheme: DiagramColors = {
|
|
85
|
+
bg: '#111827',
|
|
86
|
+
fg: '#F9FAFB',
|
|
87
|
+
line: '#64748B',
|
|
88
|
+
accent: '#38BDF8',
|
|
89
|
+
muted: '#94A3B8',
|
|
90
|
+
surface: '#1F2937',
|
|
91
|
+
border: '#334155',
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Then pass it to any renderer:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
renderMermaidSVG(source, myTheme)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Shiki compatibility
|
|
102
|
+
|
|
103
|
+
Turn a Shiki or VS Code theme into a diagram palette:
|
|
104
|
+
|
|
105
|
+
```ts no-run
|
|
106
|
+
import { fromShikiTheme, renderMermaidSVG } from 'agentic-mermaid'
|
|
107
|
+
import githubDark from 'shiki/themes/github-dark.mjs'
|
|
108
|
+
|
|
109
|
+
const colors = fromShikiTheme(githubDark)
|
|
110
|
+
const svg = renderMermaidSVG(source, colors)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`fromShikiTheme(theme)` reads the editor background/foreground and token colors, then maps them to Agentic Mermaid's semantic slots.
|
|
114
|
+
|
|
115
|
+
## Style + Palette stacks
|
|
116
|
+
|
|
117
|
+
Use `style` for renderer treatment and palettes, not Mermaid source edits:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
const svg = renderMermaidSVG(source, {
|
|
121
|
+
style: ['publication-figure', 'zinc-light'],
|
|
122
|
+
seed: 2,
|
|
123
|
+
})
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
For per-element emphasis, keep using Mermaid-native directives such as `classDef`, `class`, `style`, and `linkStyle`. See [`api.md`](./api.md) for the full `RenderOptions` table.
|
|
127
|
+
|
|
128
|
+
## Security and fonts
|
|
129
|
+
|
|
130
|
+
Default SVG output can include Google Fonts `@import` rules. The selected font
|
|
131
|
+
gets one only when it is a single plain family name; class and ER diagrams can
|
|
132
|
+
also import JetBrains Mono for monospace labels. CSS-variable references and
|
|
133
|
+
font stacks do not produce an import for the selected family. For offline,
|
|
134
|
+
strict, or agent-generated artifacts, use either:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
renderMermaidSVG(source, { embedFontImport: false })
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
or strict mode:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
renderMermaidSVG(source, { security: 'strict' })
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`security: 'strict'` disables external-fetch references and is the recommended default for untrusted diagrams.
|
|
147
|
+
|
|
148
|
+
For custom Style font selection, browser faces, PNG `fontDirs`, fallbacks, and
|
|
149
|
+
output-surface differences, see [Fonts in custom styles](./custom-fonts.md).
|