Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions docs/adr/0014-story-diagrams-verified-claims.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# ADR-0014 — Story diagrams: the architecture and flow slots answer the reader's real questions, with claims the tool verifies

Status: Accepted + Implemented (renderers, verifier, `make-diagrams` wiring, brain schema, tests); **not yet validated across a corpus of real builds** — see Consequences.
Date: 2026-10-10
Amends: ADR-0005 (Station 4), ADR-0012 (grounded radial architecture), INV-23 (every image teaches)

## Context

The owner reviewed the architecture and flow graphics the pipeline produced for `realiti4/claude-swap`
(the page, and the README PR) and called them "vanilla … not particularly interesting, nor even
particularly helpful". Two specific misses:

1. The flow diagram said "pick account with most quota left" as one box in a five-box vertical list.
It never showed *which* account would be picked, or that the choice is made among several — the
one thing a reader needs to see.
2. The owner asked, in conversation, "does this work for Claude and Codex?" — and the explainer could
not answer. Nothing in the pipeline extracts *what it works with*.

Root cause, from `tools/make-diagrams.mjs`: `buildArchModel` and `buildFlowModel` lay out the
dependency graph and the entrypoints mechanically. They describe *how the code is arranged*. A repo
with one module and zero internal edges (claude-swap: "1 module, 0 internal edges") falls back to the
authored 3–5 card concept row — the vertical list. The renderers have no vocabulary for a *choice* or
for *compatibility*, so no prompt can make them show either.

The owner then reviewed two hand-authored graphics and said: "these should be the default … this is
the level and style I want associated with each one."

## Decision

Add two diagram types, drawn from small brain-authored models, that **replace** the architecture and
flow slots when present and verified (`src/story-diagrams.mjs`):

- **`visuals.systemMap`** → `architectureDiagram`. Inputs → one engine (2–4 steps) → surfaces with
their real commands, plus a **WORKS WITH** strip and a **RUNS ON** row.
- **`visuals.decision`** → `flowDiagram`. Candidates drawn as gauges against a threshold; the active,
skipped, picked and eligible ones marked; the handoff animated.

Same slots, same files (`assets/architecture.svg`, `assets/flow.svg`), same `build.json` keys — so
`assemble-page`, `readme-enhance`, the grader and the lightbox are unchanged.

### Claims are verified by the tool, never taken from the brain

This is the load-bearing rule. A picture of "works with Codex ✓" that the brain hallucinated is worse
than the vanilla one. So `make-diagrams` checks against the cloned repo (`repo.clonePath`):

| Claim | Brain supplies | The tool does |
|---|---|---|
| `yes` / `partial` | `file` + a verbatim `quote` | drops it unless the quote is really in that file |
| `no` | `terms` (1–5 words) | greps README + source itself; keeps a "no" only on **zero** matches, and records "searched … 0 matches" |
| decision rule | `rule: { file, quote }` | drops the whole decision unless the quote is in the file |

A "no" is therefore a *measurement the tool made*, not an assertion. A term that is mentioned drops
the pill — "unsupported" would be a guess. If nothing survives, the slot falls back to the grounded
graph diagram (ADR-0011: never fail a build for this). Evidence is recorded in `build.json`
(`visuals.<slot>.storyEvidence` / `.storyNotes`).

### Style

The reference is the owner-approved claude-swap pair (`tests/fixtures/story-claude-swap.mjs`): dark
card on any theme, real commands in monospace, numbers on the gauges, one idea per picture. Motion is
CSS only, gated on `prefers-reduced-motion`, and the **static state is the end of the story** so a
still image or a reduced-motion reader still gets all of it. No scripts, no `foreignObject`, no
external references.

## Consequences

- **Mobile legibility is the known weakness.** Both canvases are ~1100 px wide; on a 312 px column
the 13 px labels render near 3.7 px. The page's tap-to-zoom lightbox covers it for now, and
`legibilityOf` does not see CSS-sized text, so these report `null`. A portrait layout is the
follow-up; do not claim mobile parity until it exists.
- **Not yet validated on a corpus.** Two repos' worth of evidence exists (claude-swap, hand-authored).
Whether the brain authors good `systemMap` / `decision` models unprompted across many repos is
unmeasured. The fallback keeps that safe; the gain is unproven until measured.
- **First tool→`src/` import.** Tools were self-contained; `make-diagrams` now imports
`../src/story-diagrams.mjs`. Tests that copy the tool elsewhere must mirror `tools/` + `src/`
(done in `diagram-form-diversity.test.mjs`).
- **`decision` fits choosing systems** (routers, pickers, schedulers); `systemMap` fits pipelines. A
repo with neither should omit the keys — the prompt says so — and keeps the graph diagram.
- Numbers on the decision gauges are illustrative, and the diagram says so in its subtitle and
`<desc>`; the *rule* is real and verified.

## Verification (2026-10-10)

- 23 unit tests (`tests/story-diagrams.test.mjs`): rendering, XML validity, no script/foreignObject,
reduced-motion gating, scale of the gauges, truncation, escaping, and **guards proven by mutation**
(removing the evidence/quote check fails 2 tests).
- End-to-end: `make-diagrams` on the claude-swap build with an injected fabricated claim — the
fabricated "Cursor" pill was dropped (`quote not found in README.md`), the real claims and the
zero-hit Codex search were kept, `xmllint` clean.
- Real brain run on claude-swap (`--only visual-brief`, then `make-diagrams`): the brain authored both
models unprompted — including a Codex "no" — and the tool confirmed the quotes and a zero-hit search.
The first run hit `max_tokens=6000` (the larger schema), so the visual-brief cap is now 10000.
- Full suite: 234 tests, 0 fail (dependencies installed).
18 changes: 18 additions & 0 deletions skills/explainmyrepo/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,24 @@ parallel.
bulleted list, not a structure):
`"rows": [{ "items": ["your sentence", "tokenizer", "add · skip · subtract", "384 numbers"], "connect": true }]`

- **STORY DIAGRAMS (`visuals.systemMap`, `visuals.decision`) — answer the questions a reader
actually has (ADR-0014).** The dependency graph says how the code is arranged; it cannot say
*what this plugs into*, *what it works with*, or *how it decides*. Author these two when the repo
has a real answer, and OMIT them when it does not:
- `systemMap` → replaces the architecture slot: inputs → one engine (2–4 steps) → the surfaces that
drive it (with their real commands) + a **works-with** strip + **runs-on**. Include the hosts,
editors and platforms a reader would WONDER about (does it work with Codex? Windows?), each as
`yes`/`partial` with `file` + a VERBATIM `quote`, or `no` with `terms` to search.
- `decision` → replaces the flow slot, for repos that CHOOSE among candidates by a rule (pickers,
routers, schedulers, rankers): 2–6 candidates as gauges against a threshold, one picked, one
skipped for a stated reason, and `rule: { file, quote }` pointing at the source line that
defines it.
**You do not get to assert these.** `make-diagrams` checks every quote against the cloned repo and
runs the "no" searches itself; whatever it cannot confirm is dropped, and if nothing survives the
slot keeps the grounded graph diagram. So quote, do not paraphrase, and prefer fewer true claims.
Shapes are in `src/story-diagrams.mjs` (`validateSystemMap`, `validateDecision`) and a worked
example is `tests/fixtures/story-claude-swap.mjs`.

- **THE HERO ANIMATION (`visuals.heroAnim`) — motion that PERFORMS the argument.** Author this for
every repo that has a "trick" (almost all of them do). It renders above the fold as a 9s loop and
it is the only motion on the page. **Animate the ARGUMENT, never the chrome.** Crawling dashed
Expand Down
13 changes: 10 additions & 3 deletions src/brain.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,9 @@ Author the visual brief. Return JSON:
"flow": {
"altText": "one-line description of the runtime/process flow diagram",
"rows": [ { "items": ["3 to 5 SHORT labels naming the runtime steps, in order", "..."], "connect": true } ]
}
},
"systemMap": "OPTIONAL — omit this key unless the brief shows a real inputs -> one engine -> surfaces pipeline. Shape: { \"title\": \"What X plugs into\", \"subtitle\": \"...\", \"inputs\": { \"label\": \"YOUR ACCOUNTS\", \"items\": [{ \"name\": \"<=40 chars\", \"sub\": \"...\" }] (1-5), \"note\": \"optional, 2 short lines separated by a newline\" }, \"engine\": { \"name\": \"...\", \"sub\": \"...\", \"steps\": [{ \"title\": \"<=30\", \"sub\": \"<=80\" }] (2-4) }, \"outputs\": { \"label\": \"THREE WAYS TO DRIVE IT\", \"streamLabel\": \"one event stream\", \"items\": [{ \"name\": \"...\", \"cmd\": \"a real command\", \"sub\": \"...\" }] (1-4) }, \"worksWith\": { \"items\": [ { \"name\": \"Claude Code CLI\", \"status\": \"yes\", \"file\": \"README.md\", \"quote\": \"a VERBATIM line from that file, copied from the brief\" }, { \"name\": \"Codex\", \"status\": \"no\", \"terms\": [\"codex\"] } ] (1-6), \"noNote\": \"not mentioned anywhere in the README or source\", \"runsOn\": [\"macOS\", \"Windows\"] } }",
"decision": "OPTIONAL — omit this key unless the repo visibly CHOOSES among candidates by a rule (picks, ranks, routes, selects). Shape: { \"title\": \"The moment X picks one\", \"subtitle\": \"Illustrative numbers. ...\", \"threshold\": { \"value\": 90, \"label\": \"switch point\" }, \"qualify\": { \"value\": 83, \"label\": \"to qualify\" } or null, \"candidates\": [{ \"name\": \"<=24\", \"value\": 0-100, \"status\": \"active|skipped|picked|eligible\", \"note\": \"short\" }] (2-6, at most one active and one picked), \"steps\": [{ \"title\": \"<=40\", \"sub\": \"<=60\" }] (2-4), \"caption\": \"one line\", \"rule\": { \"file\": \"path\", \"quote\": \"a VERBATIM line of source that defines the rule, copied from the brief\" } }"
}
}
THE SWAP TEST — the single rule that decides whether an image ships (INV-22 / ADR-0008):
Expand Down Expand Up @@ -288,8 +290,9 @@ DIAGRAM RULES (bigIdea + insight are DRAWN as real glassmorphic concept-cards jo
- Use ONE row with "connect": true for a SEQUENCE (cards joined top-to-bottom by arrows). Use MULTIPLE rows (each "connect": false) for parallel/grouped ideas drawn without an arrow between groups.
- bigIdea = the central mechanism in 3-6 cards (how the pieces combine to do the one big thing). insight = the single clever move in 2-4 cards. Keep BOTH distinct from the architecture diagram — do not just relist every module.
- architecture.rows and flow.rows are a REQUIRED FALLBACK, not decoration (issue #17.4, pacphi). Those two diagrams are normally drawn from the real KB dep-graph and entrypoints. But when a repo's dependency graph is TRIVIAL (a docs vault, a single-module tool — 0 internal edges), a dependency map would be a picture of nothing, so make-diagrams REFUSES to draw one and falls back to your rows instead. Without them the build stops dead at Station 4. Author them for every repo; they cost you two lines and they are the difference between a page and a crash.
- architecture.rows = the CONCEPT of how the thing is built (the 3-5 parts a reader must hold in their head), NOT the package wiring — the wiring is what the grounded renderer already draws when it can. flow.rows = the runtime steps in order.`;
const out = await callClaudeJSON({ apiKey, env: ctx?._env, model, system, user, maxTokens: 6000 });
- architecture.rows = the CONCEPT of how the thing is built (the 3-5 parts a reader must hold in their head), NOT the package wiring — the wiring is what the grounded renderer already draws when it can. flow.rows = the runtime steps in order.
- systemMap and decision (ADR-0014) are the BETTER answer to "what does it plug into / how does it decide" and REPLACE the architecture / flow drawing when present and verified. They are optional: omit rather than invent. THE TOOL VERIFIES EVERY CLAIM against the real repo and silently drops what it cannot confirm: a "yes"/"partial" must carry a file and a VERBATIM quote that is in that file in the brief; a "no" must list the search terms the tool will grep (use the other hosts / editors / platforms a reader would wonder about — the tool performs the search, you do not assert the absence). Use real commands and real numbers from the source; never paraphrase a quote.`;
const out = await callClaudeJSON({ apiKey, env: ctx?._env, model, system, user, maxTokens: 10000 });
if (!out?.hero?.prompt) throw new Error('authorVisualBrief: missing hero.prompt');
const okRows = (d) => d && Array.isArray(d.rows) && d.rows.length
&& d.rows.every((r) => r && Array.isArray(r.items) && r.items.length
Expand Down Expand Up @@ -327,6 +330,10 @@ export function visualsSlotFromBrief(brief) {
// could author a perfectly good fallback and the pipeline would still die at Station 4.
architectureDiagram: conceptModel(brief.diagrams.architecture, 'How it is built'),
flowDiagram: conceptModel(brief.diagrams.flow, 'What happens to your data'),
// ADR-0014: optional evidence-backed story models. Carried through UNCHANGED — make-diagrams
// validates and VERIFIES them against the cloned repo before anything is drawn.
...(brief.diagrams.systemMap && typeof brief.diagrams.systemMap === 'object' ? { systemMap: brief.diagrams.systemMap } : {}),
...(brief.diagrams.decision && typeof brief.diagrams.decision === 'object' ? { decision: brief.diagrams.decision } : {}),
};
}

Expand Down
Loading
Loading