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.
Files changed (54) hide show
  1. package/AGENT_NATIVE.md +671 -0
  2. package/CHANGELOG.md +36 -0
  3. package/Instructions_for_agents.md +89 -0
  4. package/README.md +8 -8
  5. package/assets/hero.png +0 -0
  6. package/dist/agent-core.d.ts +2 -2
  7. package/dist/agent-core.js +2 -2
  8. package/dist/agent.d.ts +2 -2
  9. package/dist/agent.js +4 -4
  10. package/dist/agentic-mermaid-mcp.js +5 -5
  11. package/dist/am.js +8 -15
  12. package/dist/browser.global.js +777 -0
  13. package/dist/{chunk-WN3YDOBG.js → chunk-3QCXO4V6.js} +4 -4
  14. package/dist/{chunk-4QECACIA.js → chunk-AVCWKYII.js} +19 -19
  15. package/dist/{chunk-WI35L7IR.js → chunk-GILQUGFK.js} +2 -2
  16. package/dist/{chunk-QKDLMOJJ.js → chunk-HFD4MALA.js} +7 -9
  17. package/dist/{chunk-CDPKJ2BQ.js → chunk-UJNY65QS.js} +2 -2
  18. package/dist/{chunk-ZYMMKQ3I.js → chunk-WHYJLDYG.js} +299 -973
  19. package/dist/{index-D54dqSdC.d.ts → index-CLwYn6Im.d.ts} +7 -4
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/mcp-cli-SJVOGRA6.js +17 -0
  23. package/docs/README.md +35 -34
  24. package/docs/agent-api-cookbook.md +317 -0
  25. package/docs/agent-mutation-policy.md +104 -0
  26. package/docs/agent-workflow-examples.md +46 -0
  27. package/docs/api.md +418 -0
  28. package/docs/ascii.md +113 -0
  29. package/docs/browser.md +183 -0
  30. package/docs/code-mode-impact.md +214 -0
  31. package/docs/comparison.md +106 -0
  32. package/docs/computational-aesthetics-prototype-plan.md +252 -0
  33. package/docs/config.md +121 -0
  34. package/docs/custom-fonts.md +265 -0
  35. package/docs/custom-style-cookbook.md +275 -0
  36. package/docs/diagram-families.md +218 -0
  37. package/docs/features.md +154 -0
  38. package/docs/fork-differences.md +166 -0
  39. package/docs/getting-started.md +182 -0
  40. package/docs/issue-derived-test-cases.md +163 -0
  41. package/docs/mcp-code-mode-rationale.md +108 -0
  42. package/docs/mcp-http-transport.md +230 -0
  43. package/docs/mermaid-layout-complaints.md +851 -0
  44. package/docs/mutation-testing.md +285 -0
  45. package/docs/quality.md +224 -0
  46. package/docs/react.md +97 -0
  47. package/docs/style-authoring.md +336 -0
  48. package/docs/svg-semantic-contract.md +49 -0
  49. package/docs/testing-strategy.md +443 -0
  50. package/docs/theming.md +149 -0
  51. package/package.json +28 -9
  52. package/server.json +2 -2
  53. package/skills/agentic-mermaid-live-editor/SKILL.md +1 -1
  54. 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.
@@ -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).