From e67bc1d68c29def4d89247e37414106f58cef78f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 4 Oct 2026 21:05:02 +0000 Subject: [PATCH 1/2] refactor(skill): replace always-loaded stubs with registry pickers SKILL.md is now a router. Eight 13-line shape stubs are gone. The gold HTML is the contract. Thick references stay for editorial, podcast, execution-log, and deck-review. Co-authored-by: Todd Dailey --- SKILL.md | 253 ++++++------------------------ perf/baseline.json | 127 +++------------ references/shapes/comparison.md | 12 -- references/shapes/dashboard.md | 12 -- references/shapes/developer.md | 12 -- references/shapes/document.md | 12 -- references/shapes/network-map.md | 12 -- references/shapes/runbook.md | 12 -- references/shapes/timeline.md | 12 -- references/shapes/triage-board.md | 12 -- scripts/check-contracts.mjs | 18 +-- scripts/perf_harness.py | 13 +- tests/test_perf_harness.py | 2 +- 13 files changed, 82 insertions(+), 427 deletions(-) delete mode 100644 references/shapes/comparison.md delete mode 100644 references/shapes/dashboard.md delete mode 100644 references/shapes/developer.md delete mode 100644 references/shapes/document.md delete mode 100644 references/shapes/network-map.md delete mode 100644 references/shapes/runbook.md delete mode 100644 references/shapes/timeline.md delete mode 100644 references/shapes/triage-board.md diff --git a/SKILL.md b/SKILL.md index 7acc771..088d35c 100644 --- a/SKILL.md +++ b/SKILL.md @@ -27,24 +27,12 @@ If multiple inputs possible, ask which. ### URL handling -Rendering a URL means producing an artifact that does justice to the source — not a few quotes and a link-out button. The failure mode is consistent and demoralizing: WebFetch returns a ~1–2KB model-summarized digest of an article that may be 5,000 or 50,000 words, and rendering against that digest produces a TOC + a handful of quote cards + a "read full article ↗" button. Zero HTML-native features. Zero entity index. Zero synthesis. The 2026-05-25 *Magnifica Humanitas* v1 incident named this: "you summarized a 43,000 word article into a few quotes and a TOC." +Never render off a WebFetch summary. Magnifica (2026-05-25) shipped a TOC plus quotes from a 43,000 word article that way. -The pipeline that doesn't fail: - -1. **`curl -sL "" -o /tmp/-raw.html`** — get the actual bytes. WebFetch is summarization, not fetching. -2. **Parse structure into JSON.** Strip script/style; extract the body; segment paragraphs; pull numbered paragraphs, section headings, footnotes — whatever structure the source carries. Save to `/tmp/-structured.json`. -3. **Dispatch a synthesis subagent** against the structured JSON with a tight schema. The subagent reads the actual paragraphs (not just headings) and returns: - - `thesis` (one sentence) + `thesis_quote` (verbatim, ≤40 words) + `thesis_quote_ref` (¶N) - - `takeaways[]` — 6–10 numbered claims, each ≤30 words, **arguing position** not category label - - `claims[]` — 10–15 claim cards: `topic` (4–10 word arguing headline), `body` (1–3 sentence mechanism), `evidence_paras` (real ¶ numbers in the source), `evidence_note` - - `entities` grouped by category (predecessor documents with real URLs, people, concepts, etc.), each with the paragraph numbers where they appear - - `themes[]` — 8–12 with frequency counts via string-search on the paragraph texts - - `chapter_summaries[]` if the source has chapter structure -4. **Render the editorial-shape (or appropriate-shape) artifact** with the full feature set: italic thesis pull-quote, stacked numbered takeaways, chapter-grouped claim cards whose Evidence ¶ links scroll-flash the matching paragraph in a full annotated-text section below, SVG theme bar chart, structural map, full body with anchor IDs, entity inspector with click-to-highlight-all-mentions, search with prev/next nav, hide-sidebar toggle, theme toggle. - -**"Render as an external link" is not an exemption.** The user may want a prominent CTA out to the source; that does not mean skip the synthesis. The artifact orients the reader, indexes the entities, and lets them navigate the document — the external link is one of the affordances, not the entire artifact. - -**Size sanity:** a serious editorial-shape render of long-form (≥5,000 source words) is rarely under ~50KB. Under 30KB on long-form input is a smell — likely zero HTML-native features and rendering off a summary. +1. `curl -sL "" -o /tmp/-raw.html` +2. Parse the body into `/tmp/-structured.json` (paragraphs, headings, footnotes). +3. Synthesize thesis, takeaways, claim cards, entities, and themes from that JSON. +4. Render the matching shape. Long-form (≥5,000 source words) under 30KB is a smell. ## Output @@ -54,33 +42,12 @@ The pipeline that doesn't fail: - Footer shows the artifact path and generated/updated timestamp. - Generated artifacts should be self-contained and make no external network requests by default. - Include `` in `` so local serving does not trigger a noisy `/favicon.ico` request. -- **Social card (always, in ``)** so a hosted artifact unfurls as a titled preview: - - **Required tags:** ``, `og:title`, `og:description`, `og:type`, `og:site_name`, `twitter:card`. - - **Sourcing:** `og:title` = the artifact ``. `og:description` = one plain sentence (≤155 chars) describing *what the artifact is*, derived from the subtitle/thesis — not a production count, not "an HTML artifact about X". `og:type` = `article` for content, `website` for an index/gallery. `twitter:card` = `summary`. - - **Omit `og:url`** (final URL unknown at generation time; scrapers fall back to the request URL). - - **`og:image` is conditional.** Omit by default — it would need a hosted image file and breaks self-containment for `file://` artifacts. BUT when the artifact is shipped to a portal that hosts a sibling thumbnail (e.g. an `og-card.png` next to the entry), emit `og:image` + `twitter:image` as a relative path and upgrade `twitter:card` to `summary_large_image`. Generate the thumbnail from a 1200×630 HTML+inline-SVG template using the artifact's own design tokens, rendered via `chrome --headless --window-size=1200,630 --screenshot=…`. - - Inert on `file://`, ~6 lines, costs nothing — unconditional, not publish-gated. -- If metadata mentions supporting context, label it as context/provenance. Do not present another file as canonical. -- Any copy-as-prompt action must target the current `.html` artifact path, not a notes file, source file, or parallel document. - -### Sharing (optional) - -The skill emits a self-contained `.html` file. Serve it however you want: locally, `uv run python -m http.server`, Tailscale Serve, Tailscale Funnel, GitHub Pages, S3. - -A common pattern is two tiers: a `private/` subdir for tailnet-only viewing and a `public/` subdir explicitly synced to a public host only when the user uses a trigger phrase like "publish this" or "make it public". +- **Social card (always, in `<head>`):** `description`, `og:title`, `og:description`, `og:type`, `og:site_name`, `twitter:card`. Omit `og:url`. Omit `og:image` unless a sibling hosted thumbnail exists. +- Any copy-as-prompt action must target the current `.html` artifact path. -### Sensitivity check (when publishing publicly) +### Sharing and publish scan -Before pushing an artifact to public hosting, scan the rendered HTML for: -- Credential strings (`password`, `token`, `secret`, `key`, base64-like blobs) -- Private LAN IPs (192.168.*, 10.*, 172.16-31.*) — usually OK in moderation but flag clusters -- MAC addresses -- File paths starting with `/Users/`, `/home/`, `~/` -- Internal hostnames (`.local`, `.tailXXXX.ts.net`) -- Personal info (phone numbers, addresses, account numbers) -- External asset URLs that would leak document opens or private context to a third party - -If anything matches, list it to the user before publishing. They decide what to redact. +Serve the file however you want. Before a public publish, list credential strings, LAN IPs, `/Users/` paths, and personal info to the user. ## The bar (read this every time) @@ -179,23 +146,6 @@ Cost: ~20 lines of JS. The artifact gains a real edit loop. Different content wants different bones. Pick the shape first from content signals, then design inside it. The content-matched-shapes idea comes from [`clockless-org/html-anything`](https://github.com/clockless-org/html-anything); the visual treatment here is mine. -### Shape selection - -| Shape | Pick when the artifact is… | -|---|---| -| **`dashboard`** | Network scans, system reports, device lists, ops data — anything tabular with categories that benefit from filtering | -| **`document`** | Plans, specs, briefings, essays, brainstorms, meeting notes — prose-heavy where you'd read paragraph-to-paragraph | -| **`timeline`** | Dated logs, diaries, retrospectives, project histories, trip journals | -| **`runbook`** | Disaster recovery, deploy guides, machine rebuilds — sequential procedure being *executed* not read | -| **`comparison`** | "X vs Y vs Z" decision matrices, model comparisons, vendor pickers | -| **`network-map`** | People/relationships, brain backlinks, dependencies — connections matter | -| **`triage-board`** | Bucketing items into 3-5 columns (Now/Next/Later/Cut), inbox triage, GTD reorg | -| **`developer`** | PR writeups, code review, "explain this code" — annotated diff with severity findings | -| **`editorial`** | Argument-driven long-form where the reader absorbs a sustained position — deep essays, research synthesis, analytical memos, argument-driven briefings | -| **`execution-log`** | Live or snapshot execution telemetry — branch status, plan progress, event stream, observed state | -| **`deck-review`** | Slide deck review/approval surfaces — status memo, slide preview, per-slide notes, send-back workflow | -| **`podcast`** | A podcast episode rendered as a briefing — bottom line, takeaways, claims, terms — alongside a transcript browser. Consumes `episode.package.json` from the podcastextract pipeline. | - **Auto-pick rules:** - Source has >5 tables of similar shape → `dashboard` - Source is mostly headings + paragraphs navigated as reference (spec/plan/notes) → `document` @@ -209,80 +159,24 @@ Different content wants different bones. Pick the shape first from content signa Explicit user override always wins. -### Shape contracts - -#### `dashboard` -- **Register:** Instrument. **Layout:** max-width 1280px, 1.4fr/1fr two-column grid, sticky controls row. -- **Required:** search, toggleable category chips, dense table (mono first col), stat tiles, inline-SVG donut, status pills, one topology/diagram. -- **HTML-native ≥3:** live filter, click cross-highlight, inline charts, sortable headers, copy buttons. **Avoid:** narrow centered column, single-column scroll, generic h2/p/table. -- **Detailed contract:** load [`references/shapes/dashboard.md`](references/shapes/dashboard.md) before building. - -#### `document` -- **Register:** Reading. **Layout:** max-width 880px, single column, sticky TOC sidebar; prose measure ≤46rem. -- **Required:** TL;DR block, sticky TOC with scroll-spy, footnote pattern, inline dense tables, note/warn callouts, per-`h2` copy-as-prompt. -- **HTML-native ≥3:** TOC scroll-spy, per-section copy, collapsible appendix, click-to-expand footnotes. **Avoid:** dashboard multi-column grid, line length >70ch, "executive summary card" headers, category-label titles. -- **Detailed contract:** load [`references/shapes/document.md`](references/shapes/document.md) before building. - -#### `editorial` -- **Register:** Reading. **Layout:** studio ~1320px, narrow–wide–narrow three-zone — context rail ~18rem · prose ≤50rem (~75ch) · entity inspector ~18rem. Inspector drops <1180px, single column <820px. Rails follow §Layout sticky-rail/footer/scrollbox invariants. -- **Required:** italic left-aligned thesis pull-quote (no left-handle bar); stacked numbered takeaways (mono numerals, not a tile grid); claim cards (arguing headline + mechanism + **linked** `Evidence:` line); right-rail entity inspector; hide-sidebar focus toggle; "read next" list; per-section copy-as-prompt. -- **HTML-native ≥3:** in-text `<mark>` search with prev/next nav; click-entity cross-highlight; per-section copy; hide-sidebar mode; scroll-spy. **Avoid:** artifact-counting stat tiles; left-handle bars; category-label titles; prose >75ch; **unlinked `Evidence:` when source URLs were available**. -- **Detailed contract:** load [`references/shapes/editorial.md`](references/shapes/editorial.md) before building — load-bearing cross-link scroll semantics, search-nav contract, evidence-linking self-check, narrative-cards-not-tables rule. - -#### `timeline` -- **Register:** Reading; mono small-caps for date/kicker lines. **Layout:** vertical spine left (~140px), event cards right (~720px), max-width ~1000px, sticky year/month group headers. -- **Required:** spine line + date-marker dots, event cards (timestamp/title/body/tags), sticky group headers, jump-to-date picker, per-event "copy as quote". -- **HTML-native ≥3:** search across events with `<mark>`, date-range filter pills, cluster-collapse (month/year → count), tag-chip filter, per-event copy. **Avoid:** CV-style animations, wall of dates with no spine, cards so wide they read as paragraphs. -- **Detailed contract:** load [`references/shapes/timeline.md`](references/shapes/timeline.md) before building. - -#### `runbook` -- **Register:** Instrument, prominent mono code blocks. **Layout:** sticky "Step X of N" + progress bar header; max-width 960px single column; step cards stacked. An *instrument being executed*, not reading material. -- **Required:** step card `[number][checkbox][title]` + expandable body (header keyboard-operable, `tabindex=0` + keydown); code block with per-block copy (load-bearing); "expected output" collapsibles; branch markers; sticky progress bar; "I'm stuck" copy-as-prompt. -- **HTML-native ≥3:** per-code-block copy (visible fallback, never offscreen), live progress tracking, "stuck" prompt generator, conditional step bodies. **Avoid:** plain numbered list with code blocks; looking like `document` shape. -- **Detailed contract:** load [`references/shapes/runbook.md`](references/shapes/runbook.md) before building. - -#### `comparison` -- **Register:** Instrument. **Layout:** items as **columns**, criteria as **rows** (the axis flip vs dashboard); sticky item-name header row; weight column left; aggregate-winner row bottom; max-width 1280px. -- **Required:** column-header item cards, criterion rows with per-item values, per-row winner highlight (tint + ★), weight inputs (full-height +/− stepper), live-recomputing aggregate footer, red→amber→green numeric scale (paired with text), copy-as-recommendation. -- **HTML-native ≥3:** live weight tuning recomputes winners, column sort by aggregate, "must-have" criterion toggle, copy-as-recommendation. **Avoid:** dashboard layout (entities as rows) — the axis flip *is* the shape. -- **Detailed contract:** load [`references/shapes/comparison.md`](references/shapes/comparison.md) before building. - -#### `network-map` -- **Register:** Instrument. **Layout:** big graph canvas center (60–70%), entity-detail right rail (~280px), filter chips top. The graph IS the primary view. -- **Required:** SVG canvas with positioned nodes (hand-positioned ≤30; vanilla force-directed only at 30+), edges, node sizing by importance, cluster color-coding (paired with label/shape), right-rail entity card updating on click, top filter chips, search-to-focus, click-node-to-focus (dim others, highlight direct edges). -- **Focus-state invariant (load-bearing):** search-to-focus and click-to-focus write the *same* focused-node state — both dim non-neighbors, light direct edges, update the inspector; a `× clear focus` resets. -- **HTML-native ≥3:** click-to-focus, hover-edge-highlight, search-to-focus, cluster toggle, shortest-path. **Avoid:** a table of names with a connections column (that's a dashboard). -- **Detailed contract:** load [`references/shapes/network-map.md`](references/shapes/network-map.md) before building. - -#### `triage-board` -- **Register:** Instrument. **Layout:** title + brief instruction + 3–5 horizontal column boards (Now/Next/Later/Cut), cards inside columns, sticky export bar at bottom. The horizontal layout WITH drag IS the shape. -- **Required:** column headers with live count, draggable cards (HTML5 DnD, **vanilla — no framework**), per-card one-line rationale input, pre-sorted suggested distribution at load, sticky copy/export + copy-as-prompt bar, undo. Provide a touch fallback (move buttons/`<select>`) under ~700px. -- **HTML-native ≥3:** drag-between-columns, live column counts, copy-as-prompt exporting assignments, undo, filter/search across cards. **Avoid:** vertical lists with status badges (dashboard); a framework drag library. -- **Detailed contract:** load [`references/shapes/triage-board.md`](references/shapes/triage-board.md) before building. - -#### `developer` -- **Register:** Instrument, mono 13px for code. **Layout:** title + PR/commit metadata strip + risk callouts + annotated diff body + summary footer; max-width 1280px; optional files-changed left rail. The annotated diff + severity findings IS the shape. -- **Required:** syntax highlighting via **local CSS classes** (no CDN/Prism), per-file diffs with `+`/`−` gutter tints, inline margin annotations anchored to lines (not a sidebar), severity-coded finding cards (severity carried 3 ways, never color-only), files-changed nav, copy-link-to-finding. -- **HTML-native ≥3:** local-CSS syntax highlighting, severity-color findings + "show nits" toggle, jump-to-file, click-to-copy a finding, side-by-side before/after. **Avoid:** generic document with code blocks (that's `document`); findings in a sidebar; runtime tokenizers. -- **Detailed contract:** load [`references/shapes/developer.md`](references/shapes/developer.md) before building. - -#### `execution-log` -- **Register:** Instrument. **Layout:** compact observed-state bar + invariant hero + phase/status strip + progress primitive + log-stream table; max-width 1280px; optional live source clearly labeled. -- **Required:** run metadata, phase cards with state carried by icon/text/color, progress donut or bar, search, level/status filter chips, timestamped log stream, snapshot/export affordance. The phase strip plus log stream IS the shape. -- **HTML-native ≥3:** live search/filter, level toggles, inline progress SVG, row expand/copy, pause/resume live tail, copy snapshot. **Avoid:** raw terminal dump; artifact-counting hero stats; hiding live-source failure; filters with no clear path. -- **Detailed contract:** load [`references/shapes/execution-log.md`](references/shapes/execution-log.md) before building. - -#### `deck-review` -- **Register:** Hybrid (status memo Reading; slide browser + notes workflow Instrument). **Layout:** sticky review topbar with status/slides/send-back views; slide preview and talk track stay synchronized; notes are local state. -- **Required:** status context, slide preview, previous/next + dots, per-slide presenter notes/talk track, per-slide reviewer textarea, visible notes count, export/mail/copy fallback, clear-notes control. Optional encrypted payload gate uses WebCrypto only. -- **HTML-native ≥3:** tabbed views, slide navigation, localStorage notes, textarea capture, encrypted unlock, copy/mail export, cross-highlight current slide. **Avoid:** unencrypted confidential payloads, remote note submission, screenshot-only slide review, mailto truncation without warning. -- **Detailed contract:** load [`references/shapes/deck-review.md`](references/shapes/deck-review.md) before building. - -#### `podcast` -- **Register:** Hybrid (briefing thesis Reading, rest compact Instrument; transcript Instrument). **Input:** `episode.package.json` from podcastify/podcast-transformer (deterministic — produced by `bin/render-podcast`, not hand-authored). **Output:** two sibling docs linked by topbar folder tabs — `podcast-at-a-glance.html` + `annotated-transcript.html`. -- **Required:** topbar folder tabs, hide-sidebar focus mode, opt-in theme toggle, episode hero, host/guest cards, italic thesis card, numbered takeaways, claim cards with Evidence lines, grouped term inspector, read-next list, speaker turns, chapter rail, out-of-grid colophon footer. -- **Hard invariants:** identical topbar brand + toggle slot across both docs; folder tabs are plain links with `aria-current="page"` (no ARIA tab roles); transcript has `<h1 class="sr-only">`; sticky rails are bounded scrollboxes; footer outside the grid; never fabricate term URLs. -- **Detailed contract:** load [`references/shapes/podcast.md`](references/shapes/podcast.md) before changing `bin/render-podcast`, the canonical podcast examples, mobile/topbar behavior, or generated podcast output. +The picker table is generated from `contracts/shapes.json`. Checklist and podcast-transcript are not shapes. + +| id | Pick when | Register | Gold file | Then load | +|---|---|---|---|---| +| `dashboard` | Network scans, system reports, device lists, ops data — anything tabular with categories that benefit from filtering | instrument | `examples/dashboard.html` | copy the example | +| `document` | Plans, specs, briefings, essays, brainstorms, meeting notes — prose-heavy where you'd read paragraph-to-paragraph | reading | `examples/document.html` | copy the example | +| `timeline` | Dated logs, diaries, retrospectives, project histories, trip journals | reading | `examples/timeline.html` | copy the example | +| `runbook` | Disaster recovery, deploy guides, machine rebuilds — sequential procedure being executed not read | instrument | `examples/runbook.html` | copy the example | +| `comparison` | X vs Y vs Z decision matrices, model comparisons, vendor pickers | instrument | `examples/comparison.html` | copy the example | +| `network-map` | People/relationships, brain backlinks, dependencies — connections matter | instrument | `examples/network-map.html` | copy the example | +| `triage-board` | Bucketing items into 3–5 columns (Now/Next/Later/Cut), inbox triage, GTD reorg | instrument | `examples/triage-board.html` | copy the example | +| `developer` | PR writeups, code review, explain this code — annotated diff with severity findings | instrument | `examples/developer.html` | copy the example | +| `editorial` | Argument-driven long-form where the reader absorbs a sustained position | reading | `examples/editorial.html` | `references/shapes/editorial.md` then the example | +| `execution-log` | Live or snapshot execution telemetry — branch status, plan progress, event stream | instrument | `examples/execution-log.html` | `references/shapes/execution-log.md` then the example | +| `deck-review` | Slide deck review/approval — status memo, slide preview, per-slide notes | hybrid | `examples/deck-review.html` | `references/shapes/deck-review.md` then the example | +| `podcast` | A podcast episode rendered as a briefing alongside a transcript browser | hybrid | `examples/podcast.html` | `references/shapes/podcast.md` then the example | + +Do not invent a shape. Copy the gold HTML for a stub shape. Load the thick reference before building `editorial`, `podcast`, `execution-log`, or `deck-review`. ## Sub-patterns (within shapes, not standalone shapes) @@ -326,51 +220,20 @@ Reach for the right one *before* writing SVG. The 2026-05-25 omega-3 dashboard s **Self-check before drawing:** if a label needs leader lines or "smart placement heuristics" to not collide, the primitive is wrong — switch. -Each primitive's full contract (Required / Interaction / Avoid) lives in `references/primitives/<name>.md` — **load that file before implementing or changing the primitive.** The picker above + the one-line "pick when" below are enough to choose; the reference carries the locked details. Reference implementations: `examples/primitives/`. - -### Charts - -#### Donut — categorical status mix -- **Pick when:** 2–5 mutually-exclusive categories where proportion-at-a-glance beats absolute counts. -- **Contract:** [`references/primitives/donut.md`](references/primitives/donut.md) +The primitive picker is generated from `contracts/primitives.json`. Load `references/primitives/<id>.md` before implementing a primitive. Copy the matching file under `examples/primitives/`. -#### Bar — ranked top-N -- **Pick when:** ≤12 items where order matters and a single magnitude per item is the signal. -- **Contract:** [`references/primitives/bar.md`](references/primitives/bar.md) - -#### Sparkline — stat-tile cluster -- **Pick when:** 2–4 hero metrics where each metric is the subject; current value + recent-trend shape is the one-glance read. -- **Contract:** [`references/primitives/sparkline.md`](references/primitives/sparkline.md) - -#### Stacked bar — composition over time -- **Pick when:** categories sum to a meaningful whole each period (buckets, traffic mix, deal stages); 7–30 periods. -- **Contract:** [`references/primitives/stacked-bar.md`](references/primitives/stacked-bar.md) - -#### Topology — inline service graph -- **Pick when:** connections are the point and node count is ≤30 (≤10 inline; 10–30 hand-positioned). -- **Contract:** [`references/primitives/topology.md`](references/primitives/topology.md) - -#### Scatter — two-dimension trade-off -- **Pick when:** two genuinely independent, well-spread dimensions across ≥10 points where the *relationship* (trade-off, outliers, quadrants) is the signal — not a ranking. If points cluster tight on either axis or there are ≤8, labels collide: rank with `bar` instead (the 2026-05-25 omega-3 failure). -- **Contract:** [`references/primitives/scatter.md`](references/primitives/scatter.md) - -### Tables - -#### Dense ops table -- **Pick when:** flat list of records keyed by an identifier; ≥6 rows; filter/sort is the central interaction. The instrument-register workhorse. -- **Contract:** [`references/primitives/dense-ops-table.md`](references/primitives/dense-ops-table.md) - -#### Comparison matrix — items as columns -- **Pick when:** "X vs Y vs Z" decision matrix with shared criteria and live weight tuning; 2–5 items, 3–10 criteria. -- **Contract:** [`references/primitives/comparison-matrix.md`](references/primitives/comparison-matrix.md) - -#### Annotated diff — code review primitive -- **Pick when:** code changes / security findings / any line-anchored critique where adjacency to the source beats a side panel. -- **Contract:** [`references/primitives/annotated-diff.md`](references/primitives/annotated-diff.md) - -#### Log stream — chronological event table -- **Pick when:** a tail of timestamped events with a categorical level; newest-first reading; payloads worth expanding. -- **Contract:** [`references/primitives/log-stream.md`](references/primitives/log-stream.md) +| id | Pick when | Gold file | +|---|---|---| +| `donut` | 2–5 mutually-exclusive categories where proportion-at-a-glance beats absolute counts | `examples/primitives/01-donut.html` | +| `bar` | ≤12 items where order matters and a single magnitude per item is the signal | `examples/primitives/02-bar.html` | +| `sparkline` | 2–4 hero metrics where each metric is the subject | `examples/primitives/03-sparkline.html` | +| `stacked-bar` | categories sum to a meaningful whole each period; 7–30 periods | `examples/primitives/04-stacked-bar.html` | +| `topology` | connections are the point and node count is ≤30 | `examples/primitives/05-topology.html` | +| `dense-ops-table` | flat list of records keyed by an identifier; ≥6 rows; filter/sort is the central interaction | `examples/primitives/06-table-ops.html` | +| `comparison-matrix` | X vs Y vs Z decision matrix with shared criteria and live weight tuning | `examples/primitives/07-table-comparison.html` | +| `annotated-diff` | code changes or line-anchored critique where adjacency to the source beats a side panel | `examples/primitives/08-diff.html` | +| `log-stream` | a tail of timestamped events with a categorical level; newest-first reading | `examples/primitives/09-logs.html` | +| `scatter` | two genuinely independent, well-spread dimensions across ≥10 points | `examples/primitives/10-scatter.html` | ### Cross-cutting rules (primitives only) @@ -385,19 +248,7 @@ One palette, two registers, determined entirely by shape. The **reading register Slightly warm and non-corporate, but keep public artifacts professional by default. -`index.html` is the canonical component gallery for maintaining the design system, but do **not** read the whole gallery during normal renders. For ordinary artifact generation, use the compact shape contracts and tokens in this skill. Open `index.html` only when the user asks to inspect the design system, when changing the design system, or when a visual/detail decision is genuinely blocked. - -### Performance defaults - -- Pick the shape quickly. If the artifact is procedural, choose `runbook` without a long comparison pass. -- Treat the shape log as the planning budget. Once the source signals clearly pick a shape, start writing from that shape contract instead of rereading the gallery or comparing every possible layout. -- Reuse the shape contract, token names, and copy-as-prompt helper instead of inventing a new CSS/JS system for each artifact. -- Keep normal artifacts to the primitives the shape needs. A runbook usually needs progress, checkboxes, copy-code buttons, collapsible troubleshooting, and stuck-copy-as-prompt. It does not need swatches, charts, topology, tables, and every shared component unless the artifact calls for them. -- Use a scaffold-first build order: `<head>` metadata + blank data favicon, design tokens, shape layout, required primitives, copy-as-prompt, footer. Fill content into that scaffold. This avoids polishing prose before the artifact has its HTML-native spine. -- Open only the specific primitive reference needed (`examples/primitives/07-table-comparison.html`, etc.) when implementing an unfamiliar primitive. Do not open `index.html` or the full gallery as a default generation step. -- For updates to an existing artifact, diff and patch the smallest behavioral surface that satisfies the request. Preserve working element IDs, localStorage keys, and copy-as-prompt state formats unless changing them is the point. -- Use cheap verification gates during normal generation: no external requests, no body horizontal scroll at ~375px, visible form controls ≥16px on phones, tables/graphs inside internal scroll containers, copy-as-prompt names the current HTML path and writes the textarea before clipboard attempt. Reserve full multi-page/gallery audits for changes to this repo or the design system. -- For private/internal artifacts where speed matters more than portability, ask before using a shared local CSS/JS runtime instead of inlining all boilerplate. Self-contained remains the default for public or shareable artifacts. +Do not open `index.html` during a normal render. Copy the gold example and the tokens below. ### Layout, density, and interactivity (detailed contract on demand) @@ -469,20 +320,8 @@ Dark (via prefers-color-scheme): ### Anti-patterns -- Still exactly three faces (serif, sans, mono); no fourth -- Drop shadows on everything; gradients for their own sake -- Emoji explosion (1-3 across the whole doc, not one per heading) -- Generic AI-report decoration: redundant boxes, ornamental "Key Insights" headers -- Centered body text outside of titles -- Tailwind utility-class soup (hand-written CSS reads better) -- Narrow centered columns on data-heavy content -- Excessive whitespace as a stand-in for design taste -- Running a generic document converter and calling it done -- Pulling in a JS framework. Single file, vanilla JS. -- External fonts, analytics, or CDN assets in private artifacts unless the user explicitly approves them. -- Copy/export buttons that imply another canonical format. Prefer "copy section", "copy recommendation", "copy board state", or "copy as prompt"; avoid "copy as markdown" unless the user explicitly wants a one-off export. -- No left-handle accent bars. A 3–4px accent-colored vertical line on the left edge of a card or quote is the visual fingerprint of AI-generated layouts. Convey emphasis through type, whitespace, horizontal rules, color, and position. A horizontal rule (top border) between sections is fine; a pull-quote may use larger italic type without a bar. -- Identical-tile grids read as SaaS dashboards; cards stack as columns by default. Only lay out as a grid of equal tiles when the data is genuinely parallel and comparison across tiles is the point. +- Three faces only. No JS framework. No CDN fonts. No copy-as-markdown. +- No left-handle accent bars. No artifact-counting hero stats. No category-label titles. ## Rendering process @@ -495,12 +334,12 @@ Dark (via prefers-color-scheme): REJECTED: <1-2 shapes considered but wrong, with one-word reasons> PRIMITIVES: <3-5 primitives this shape needs from §Canonical primitives> DIMENSIONS: <which of the 8 dimensions, aiming for ≥4> - READ: <reference files to load now: references/shapes/<shape>.md (every shape has one) + references/primitives/<name>.md for each chosen primitive + references/design.md for any multi-zone/sticky-rail/search-heavy build> + READ: <examples/<shape>.html always; references/shapes/<shape>.md only for editorial, podcast, execution-log, deck-review; references/primitives/<name>.md for each chosen primitive; references/design.md for any multi-zone/sticky-rail/search-heavy build> ``` - Run the auto-pick table from §Shape selection against the real content first; do not invent a shape; do not pick `editorial` for tabular data with filterable categories (that is `dashboard`). + Run the auto-pick rules against the real content first; do not invent a shape; do not pick `editorial` for tabular data with filterable categories (that is `dashboard`). - **Then actually Read the files named in `READ:` before step 3 — this is not optional.** Every shape (`references/shapes/<shape>.md`) and every primitive (`references/primitives/<name>.md`) has a detailed contract loaded on demand, not carried in this skill; the compact inline stubs above are enough to *pick* and scaffold but NOT to *build correctly*. Building any shape or an unfamiliar primitive from the inline stub alone reintroduces the exact omega-3 / *Magnifica* failure modes the detailed contracts exist to prevent. + **Then actually Read the files named in `READ:` before step 3 — this is not optional.** The gold HTML is the contract for stub shapes. Thick references stay for `editorial`, `podcast`, `execution-log`, and `deck-review`. Building from this picker table alone reintroduces the omega-3 / Magnifica failure modes. 3. **Plan the instrument:** - What's the central interaction? (filter? compare? execute? explore?) diff --git a/perf/baseline.json b/perf/baseline.json index 6b33037..3a16425 100644 --- a/perf/baseline.json +++ b/perf/baseline.json @@ -1,31 +1,23 @@ { "skill_md": { - "total_bytes": 49339, - "total_tokens": 12241, + "total_bytes": 29794, + "total_tokens": 7394, "sections": [ { - "name": "Page shapes (pick before designing)", - "tokens": 3291 + "name": "Rendering process", + "tokens": 1643 }, { "name": "Design system", - "tokens": 2074 - }, - { - "name": "Rendering process", - "tokens": 1677 + "tokens": 1153 }, { "name": "Canonical primitives (charts and tables)", - "tokens": 1478 + "tokens": 1110 }, { - "name": "Artifact forms", - "tokens": 833 - }, - { - "name": "Output", - "tokens": 773 + "name": "Page shapes (pick before designing)", + "tokens": 881 }, { "name": "Copy-as-prompt", @@ -39,6 +31,14 @@ "name": "Sub-patterns (within shapes, not standalone shapes)", "tokens": 489 }, + { + "name": "Artifact forms", + "tokens": 253 + }, + { + "name": "Output", + "tokens": 240 + }, { "name": "The 8 information dimensions HTML can carry", "tokens": 207 @@ -56,100 +56,11 @@ "tokens": 66 } ], - "contracts": [ - { - "contract": "podcast", - "tokens": 289 - }, - { - "contract": "editorial", - "tokens": 283 - }, - { - "contract": "network-map", - "tokens": 253 - }, - { - "contract": "developer", - "tokens": 238 - }, - { - "contract": "deck-review", - "tokens": 229 - }, - { - "contract": "Log stream \u2014 chronological event table", - "tokens": 223 - }, - { - "contract": "triage-board", - "tokens": 217 - }, - { - "contract": "runbook", - "tokens": 211 - }, - { - "contract": "execution-log", - "tokens": 208 - }, - { - "contract": "comparison", - "tokens": 207 - }, - { - "contract": "timeline", - "tokens": 181 - }, - { - "contract": "document", - "tokens": 150 - }, - { - "contract": "dashboard", - "tokens": 141 - }, - { - "contract": "Scatter \u2014 two-dimension trade-off", - "tokens": 110 - }, - { - "contract": "Annotated diff \u2014 code review primitive", - "tokens": 68 - }, - { - "contract": "Dense ops table", - "tokens": 67 - }, - { - "contract": "Comparison matrix \u2014 items as columns", - "tokens": 66 - }, - { - "contract": "Sparkline \u2014 stat-tile cluster", - "tokens": 63 - }, - { - "contract": "Stacked bar \u2014 composition over time", - "tokens": 63 - }, - { - "contract": "Topology \u2014 inline service graph", - "tokens": 57 - }, - { - "contract": "Donut \u2014 categorical status mix", - "tokens": 55 - }, - { - "contract": "Bar \u2014 ranked top-N", - "tokens": 49 - } - ], + "contracts": [], "oversized_contracts": [] }, "references": { - "total_tokens": 13603, - "file_count": 23 + "total_tokens": 9928, + "file_count": 15 } } diff --git a/references/shapes/comparison.md b/references/shapes/comparison.md deleted file mode 100644 index 14adb70..0000000 --- a/references/shapes/comparison.md +++ /dev/null @@ -1,12 +0,0 @@ -# Comparison Shape Reference - -Load before authoring or changing a `comparison`-shape artifact, or `examples/comparison.html`. The compact stub in `SKILL.md` §Page shapes is enough to pick the shape and scaffold; this file holds the build detail. **The axis flip is the shape** — get it wrong and you've built a dashboard. - -- **Register:** Instrument (sans display + sans body; see §Design system / Density). -- **Use for:** "X vs Y vs Z" decision matrices, model comparisons, vendor pickers — a trade-off across >2 options against shared criteria. -- **Layout:** items as **columns**, criteria as **rows** (this is the inversion vs dashboard, which puts entities in rows). A sticky header row carries the item names; a weight column sits on the far left; an aggregate-winner row sits at the bottom. Max-width 1280px. -- **First viewport:** title + TL;DR + the matrix with the weight column visible and the aggregate row in view. -- **Required primitives:** column-header item cards; criterion rows with a per-item value in each cell; per-row winner highlighting (background tint + ★); a weight input per criterion with a full-height +/− stepper flanking it; an aggregate-score footer that **live-recomputes** as weights change; a red→amber→green value scale for numerics (paired with text/position so it's not color-only); a "copy as recommendation" round-trip prompt with a visible textarea fallback. See `references/primitives/comparison-matrix.md`. -- **Density:** instrument defaults; dense cells (6–10px padding). -- **HTML-native ≥3:** live weight tuning recomputes per-row and overall winners; column sort by aggregate; a "must-have" criterion toggle that disqualifies failing items; copy-as-recommendation. -- **Avoid:** dashboard-style layout (entities as rows) — the axis flip *is* the point; tiny native spinner buttons; unbounded weights; "copy as markdown" implying another canonical format. diff --git a/references/shapes/dashboard.md b/references/shapes/dashboard.md deleted file mode 100644 index dfd81cf..0000000 --- a/references/shapes/dashboard.md +++ /dev/null @@ -1,12 +0,0 @@ -# Dashboard Shape Reference - -Load before authoring or changing a `dashboard`-shape artifact, or `examples/dashboard.html`. The compact stub in `SKILL.md` §Page shapes is enough to pick the shape and scaffold; this file holds the build detail. - -- **Register:** Instrument (sans display + sans body; see §Design system / Density). -- **Use for:** network scans, system reports, device lists, ops data — anything tabular with categories that benefit from filtering. -- **Layout:** max-width 1280px; a `1.4fr / 1fr` two-column grid (dense table left, charts/inspector right); a sticky controls row pinned at the top holding search + chip filters. Drops to single column under ~820px — and a sticky top bar is always safe, but any tall sticky side rail must follow the sticky-rail/scrollbox invariants in §Layout principles. -- **First viewport:** hero with stats tiles + search + chip filters + a one-line TL;DR. The tiles are content only when the metric *is* the subject (open ports, devices on file) — never artifact-production counts. -- **Required primitives:** search input; toggleable category chips (each with a visible active state + a global `× clear`); dense ops table with a mono first column; stat tiles; an inline-SVG donut; status pills (outline + dot + label, never color-only); one inline-SVG topology/diagram. Reach for the matching `references/primitives/*.md` when implementing one you're unsure of. -- **Density:** instrument defaults; `8px 10px` table cell padding, or `4–8px` for the compact variant. -- **HTML-native ≥3:** live filter; clickable cross-highlights (click a donut slice or chip → filter the table); inline charts; column-sort; copy buttons. Counts always reflect the underlying data, not the filtered view. -- **Avoid:** narrow centered column; generous whitespace; single-column scroll; generic h2/p/`<table>` layout with no interaction. If flattening to static text only loses an SVG, it's styled prose — add real filtering/cross-highlight. diff --git a/references/shapes/developer.md b/references/shapes/developer.md deleted file mode 100644 index e77b681..0000000 --- a/references/shapes/developer.md +++ /dev/null @@ -1,12 +0,0 @@ -# Developer Shape Reference - -Load before authoring or changing a `developer`-shape artifact, or `examples/developer.html`. The compact stub in `SKILL.md` §Page shapes is enough to pick the shape and scaffold; this file holds the build detail. **The annotated diff + severity findings are the shape** — a generic document with code blocks is `document`. - -- **Register:** Instrument (sans display + sans body; see §Design system / Density); mono at 13px for code. -- **Use for:** PR writeups, code review, "explain this code" — line-anchored critique where adjacency to the source beats a side panel. -- **Layout:** title + a PR/commit metadata strip + risk callouts at top + the annotated diff body + a summary footer. Max-width 1280px. Optional left rail with a "files changed" navigator. -- **First viewport:** PR summary, a severity-coded findings count (critical / warning / nit), and the top risk callout. -- **Required primitives:** syntax-highlighted code using a tiny set of **local CSS classes** (no Prism, no CDN tokenizer); per-file diffs with `+`/`−` line gutters in soft green/red tints; inline margin annotations anchored to specific lines (not a sidebar); severity-coded finding cards; a files-changed navigator; copy-link-to-finding buttons. See `references/primitives/annotated-diff.md`. -- **Severity travels three ways** (left bar + badge fill + a strip count) so it survives colorblind viewing and greyscale screenshots — never color alone. -- **HTML-native ≥3:** local-CSS syntax highlighting (no external renderer); severity-color findings with a "show nits" toggle; jump-to-file; click-to-copy an individual finding (or `file:line`); optional side-by-side before/after. -- **Avoid:** a generic document with code blocks (that's `document`); findings in a sidebar divorced from the lines they discuss; color-only severity; runtime tokenizers / CDN syntax highlighters. diff --git a/references/shapes/document.md b/references/shapes/document.md deleted file mode 100644 index 2360012..0000000 --- a/references/shapes/document.md +++ /dev/null @@ -1,12 +0,0 @@ -# Document Shape Reference - -Load before authoring or changing a `document`-shape artifact, or `examples/document.html`. The compact stub in `SKILL.md` §Page shapes is enough to pick the shape and scaffold; this file holds the build detail. - -- **Register:** Reading (serif display + serif body, warm cream paper, terracotta accent; see §Design system / Density). -- **Use for:** plans, specs, briefings, essays, brainstorms, meeting notes — prose-heavy reference read paragraph-to-paragraph and navigated by section. (For a *sustained argument* with named entities worth a reference rail, use `editorial` instead.) -- **Layout:** max-width 880px, single column, with a sticky TOC sidebar on desktop. Prose measure capped at `46rem` (~70ch) even when chrome is wider. Real horizontal rules between sections, not boxed borders. -- **First viewport:** title, byline, a required TL;DR block, and a visible TOC. -- **Required primitives:** TL;DR block; sticky TOC sidebar with scroll-spy (the TOC follows the sticky-rail/scrollbox invariants in §Layout principles); footnote pattern (click-to-expand); inline dense tables where data is genuinely tabular; callouts (note/warn); a per-section copy-as-prompt button hover-revealed on each `h2`, targeting the current `.html` path. -- **Density:** reading defaults — 17–18px serif body, line-height 1.6, section gap ~2.5rem. -- **HTML-native ≥3:** TOC scroll-spy; per-section copy buttons; collapsible appendix; click-to-expand footnotes; optional in-text `<mark>` search (with prev/next nav if a count is shown). -- **Avoid:** dashboard-style multi-column grid; line length past ~70ch; corporate "executive summary card" headers; left-handle accent bars; category-label section titles ("Overview", "Key Points") — headlines should argue a position. diff --git a/references/shapes/network-map.md b/references/shapes/network-map.md deleted file mode 100644 index 6e683d4..0000000 --- a/references/shapes/network-map.md +++ /dev/null @@ -1,12 +0,0 @@ -# Network-map Shape Reference - -Load before authoring or changing a `network-map`-shape artifact, or `examples/network-map.html`. The compact stub in `SKILL.md` §Page shapes is enough to pick the shape and scaffold; this file holds the build detail. **The graph is the primary view** — if you find yourself rendering a table of names with "connections: X, Y, Z", you've built a dashboard. - -- **Register:** Instrument (sans display + sans body; see §Design system / Density). -- **Use for:** people/relationships, brain backlinks, dependencies — anything where the *connections* carry the meaning. -- **Layout:** a big graph canvas center (60–70% width), an entity-detail right rail (~280px), filter chips along the top. The right rail follows the sticky-rail/scrollbox invariants in §Layout principles. -- **First viewport:** the graph fitted to the viewport, filter chips above, a "click for details" hint. -- **Required primitives:** an SVG canvas with positioned nodes (hand-positioned for ≤30; a small vanilla force-directed sim only at 30+); edges; node sizing by importance; cluster color-coding (paired with label/shape, never color-only); a right-rail entity card that updates on click; top filter chips; search to focus; click-node-to-focus (dim non-neighbors, highlight direct edges). See `references/primitives/topology.md` for the inline-graph contract. -- **Focus-state invariant (load-bearing):** search-to-focus and click-to-focus MUST write the *same* focused-node state — both dim non-neighbors, highlight direct edges, and update the right-rail inspector. A search result that only highlights the SVG while leaving stale inspector text is broken. A visible `× clear focus` resets it. -- **HTML-native ≥3:** click-to-focus; hover-edge-highlight; search-to-focus; cluster toggle; optional shortest-path finder. -- **Avoid:** rendering as a table of names with a connections column (that's a dashboard); force-directed wiggle on small graphs; edges drawn center-to-center poking out of nodes; an unstyled SVG with no surrounding chrome. diff --git a/references/shapes/runbook.md b/references/shapes/runbook.md deleted file mode 100644 index df156f9..0000000 --- a/references/shapes/runbook.md +++ /dev/null @@ -1,12 +0,0 @@ -# Runbook Shape Reference - -Load before authoring or changing a `runbook`-shape artifact, or `examples/runbook.html`. The compact stub in `SKILL.md` §Page shapes is enough to pick the shape and scaffold; this file holds the build detail. A runbook is an *instrument being executed*, not reading material — that distinction drives every choice below. - -- **Register:** Instrument (sans display + sans body; see §Design system / Density), with prominent mono code blocks. -- **Use for:** disaster recovery, deploy guides, machine rebuilds — a sequential procedure being *run*, step by step, while something is on fire. -- **Layout:** a sticky header with "Step X of N" + a progress bar; max-width 960px single column; step cards stacked vertically. The sticky progress bar is a full-width top bar (always safe per §Layout principles). -- **First viewport:** title + a scope/danger callout + the progress bar + step 1 visible. -- **Required primitives:** step card with `[number] [checkbox] [title]` + an expandable body; a code block with a per-block copy button (load-bearing — running a runbook means copying commands); "expected output" collapsible callouts; branch markers ("if X, jump to step Y"); the sticky progress bar; an "I'm stuck" copy-as-prompt that names the current `.html` path and the current step. -- **Step headers must be keyboard-operable:** `tabindex="0"` + a keydown handler (Enter/Space toggles the body), not click-only. -- **HTML-native ≥3:** per-code-block copy button (with a *visible* fallback, never an offscreen textarea); live progress tracking as checkboxes are ticked; the "stuck" prompt generator; conditional/collapsible step bodies. -- **Avoid:** a plain numbered list with code blocks; making it look like `document` shape (this is an instrument); hiding the copy-code fallback offscreen. diff --git a/references/shapes/timeline.md b/references/shapes/timeline.md deleted file mode 100644 index 3f79a73..0000000 --- a/references/shapes/timeline.md +++ /dev/null @@ -1,12 +0,0 @@ -# Timeline Shape Reference - -Load before authoring or changing a `timeline`-shape artifact, or `examples/timeline.html`. The compact stub in `SKILL.md` §Page shapes is enough to pick the shape and scaffold; this file holds the build detail. - -- **Register:** Reading (serif display + serif body; see §Design system / Density). Mono small-caps for date/kicker lines (e.g. `SAN FRANCISCO · 14 MAY 2021`). -- **Use for:** dated logs, diaries, retrospectives, project histories, trip journals — anything whose primary structure is *when*. -- **Layout:** a vertical spine on the left (~140px column for date markers + dots), event cards on the right (~720px). Max-width ~1000px. Sticky year/month group headers as you scroll. -- **First viewport:** title + a date-range scrubber + a search input + the most recent N events. -- **Required primitives:** the vertical spine line; date-marker dots on the spine; event cards (timestamp / title / body / tags); sticky year/month group headers; a jump-to-date picker; a per-event "copy as quote" button targeting the current `.html` path. -- **Density:** reading defaults; dense event cards (8–10px padding), ~12px gap between events. Cards stay narrow enough not to read as document paragraphs. -- **HTML-native ≥3:** live text search across events with `<mark>` highlight; date-range filter pills; cluster-collapse (month/year → count, expandable); tag-chip filter; per-event copy. Every filter has a visible `× clear`; counts reflect all events, not the filtered view. -- **Avoid:** decorative CV-style scroll animations; a wall of dates with no spine; event cards so wide they read as prose paragraphs. diff --git a/references/shapes/triage-board.md b/references/shapes/triage-board.md deleted file mode 100644 index 225526e..0000000 --- a/references/shapes/triage-board.md +++ /dev/null @@ -1,12 +0,0 @@ -# Triage-board Shape Reference - -Load before authoring or changing a `triage-board`-shape artifact, or `examples/triage-board.html`. The compact stub in `SKILL.md` §Page shapes is enough to pick the shape and scaffold; this file holds the build detail. **The horizontal columns WITH drag are the shape** — vertical lists with status badges are a dashboard. - -- **Register:** Instrument (sans display + sans body; see §Design system / Density). -- **Use for:** bucketing items into 3–5 columns (Now / Next / Later / Cut), inbox triage, GTD reorg. -- **Layout:** title + a brief instruction + 3–5 column boards laid out horizontally, cards inside columns, a sticky export bar at the bottom (a full-width bottom bar is always safe per §Layout principles). -- **Required primitives:** column headers with a live count; draggable cards (HTML5 drag-and-drop, **vanilla — no React-DnD or any framework**); a per-card one-line rationale `<input>`; a pre-sorted suggested distribution at load (don't start with everything in one pile); a sticky copy/export + "copy as prompt" bar; undo. -- **Density:** instrument defaults; cards ~80–120px tall, columns ~240–320px wide. -- **HTML-native ≥3:** drag-between-columns; live column counts; copy-as-prompt exporting the final assignments (names the current `.html` path, delimits state as data, visible textarea fallback); undo; filter/search across all cards. -- **Mobile:** drag is hard on touch — provide a fallback (a per-card column `<select>` or move buttons) so the board is operable under ~700px where columns stack. -- **Avoid:** vertical lists with status badges (dashboard); a framework drag library; starting with no suggested distribution. diff --git a/scripts/check-contracts.mjs b/scripts/check-contracts.mjs index 15fcd40..504e167 100644 --- a/scripts/check-contracts.mjs +++ b/scripts/check-contracts.mjs @@ -19,6 +19,7 @@ const FEATURE_KEYS = new Set([ "hide_sidebar", ]); const NOT_SHAPES = new Set(["checklist", "podcast-transcript"]); +const THICK_SHAPES = new Set(["editorial", "podcast", "execution-log", "deck-review"]); const STALE_COUNTS = [ "Nine page shapes", "nine chart and table primitives", @@ -83,8 +84,10 @@ for (const shape of shapes) { } if (!shape.example) note(`${shape.id}: missing example`); else if (!exists(shape.example)) note(`${shape.id}: missing ${shape.example}`); - if (!shape.reference) note(`${shape.id}: missing reference`); - else if (!exists(shape.reference)) note(`${shape.id}: missing ${shape.reference}`); + if (THICK_SHAPES.has(shape.id)) { + if (!shape.reference) note(`${shape.id}: missing reference`); + else if (!exists(shape.reference)) note(`${shape.id}: missing ${shape.reference}`); + } } const primitiveIds = new Set(); @@ -128,8 +131,6 @@ for (const shape of shapes) { if (shape.href && !gallery.includes(shape.href)) { note(`${shape.id}: gallery missing link ${shape.href}`); } - const heading = new RegExp(`#### \`${shape.id}\``); - if (skill && !heading.test(skill)) note(`${shape.id}: SKILL.md missing compact contract`); } for (const primitive of primitives) { @@ -138,12 +139,6 @@ for (const primitive of primitives) { if (primitive.href && !primGallery.includes(primitive.href)) { note(`${primitive.id}: primitives gallery missing link ${primitive.href}`); } - if (primitive.skillHeading) { - const heading = new RegExp(primitive.skillHeading, "i"); - if (skill && !heading.test(skill)) { - note(`${primitive.id}: SKILL.md missing contract ${primitive.skillHeading}`); - } - } } if (harness) { @@ -167,6 +162,9 @@ if (exists("references/shapes")) { const id = name.slice(0, -3); if (!shapeIds.has(id)) note(`references/shapes/${name} is not in contracts/shapes.json`); } + for (const id of THICK_SHAPES) { + if (!exists(`references/shapes/${id}.md`)) note(`${id}: missing thick reference`); + } } if (exists("examples/podcast.html")) { diff --git a/scripts/perf_harness.py b/scripts/perf_harness.py index 95e5ff3..d55dc2b 100644 --- a/scripts/perf_harness.py +++ b/scripts/perf_harness.py @@ -482,6 +482,9 @@ def analyze_self_contained() -> dict: # 6. Shape registry coverage # --------------------------------------------------------------------------- +THICK_SHAPES = {"editorial", "podcast", "execution-log", "deck-review"} + + def analyze_shapes() -> dict: skill = SKILL.read_text(encoding="utf-8") gallery = (EXAMPLES / "index.html").read_text(encoding="utf-8") @@ -493,15 +496,15 @@ def analyze_shapes() -> dict: has_example = example_path.exists() has_reference = reference_path.exists() has_gallery_link = spec["href"] in gallery - has_contract = re.search(rf"#### `{re.escape(name)}`", skill) is not None + has_contract = f"`{name}`" in skill if not has_example: violations.append(f"{name}: missing {spec['file']}") - if not has_reference: + if name in THICK_SHAPES and not has_reference: violations.append(f"{name}: missing references/shapes/{name}.md") if not has_gallery_link: violations.append(f"{name}: gallery missing link {spec['href']}") if not has_contract: - violations.append(f"{name}: SKILL.md missing compact contract") + violations.append(f"{name}: SKILL.md missing picker-table id") rows.append({ "name": name, "file": spec["file"], @@ -527,13 +530,13 @@ def analyze_primitives() -> dict: file_path = REPO / spec["file"] has_file = file_path.exists() has_gallery_link = spec["href"] in gallery - has_contract = re.search(spec["contract"], skill, re.I) is not None + has_contract = f"`{name}`" in skill if not has_file: violations.append(f"{name}: missing {spec['file']}") if not has_gallery_link: violations.append(f"{name}: gallery missing link {spec['href']}") if not has_contract: - violations.append(f"{name}: SKILL.md missing contract {spec['contract']}") + violations.append(f"{name}: SKILL.md missing picker-table id") rows.append({ "name": name, "file": spec["file"], diff --git a/tests/test_perf_harness.py b/tests/test_perf_harness.py index 0110287..efbb68b 100644 --- a/tests/test_perf_harness.py +++ b/tests/test_perf_harness.py @@ -99,7 +99,7 @@ def test_source_document_contract_is_clean(): def test_references_dir_is_measured(): h = _load_harness() refs = h.analyze_references() - assert refs["file_count"] >= 20 + assert refs["file_count"] >= 14 assert refs["total_tokens"] > 0 # The committed baseline must stay slim: only content-derived fields. baseline = json.loads(BASELINE.read_text()) From 98a7c6d71f7516603228ad2f1ad37febca35dbe0 Mon Sep 17 00:00:00 2001 From: Cursor Agent <cursoragent@cursor.com> Date: Sun, 4 Oct 2026 22:11:41 +0000 Subject: [PATCH 2/2] fix(contracts): drop deleted stub reference paths Thin shapes no longer list missing references/shapes files. The checker fails if any listed path is gone, and SKILL.md pickWhen must match the JSON. Harness complete count follows the thick-only reference rule. Co-authored-by: Todd Dailey <twidtwid@users.noreply.github.com> --- SKILL.md | 8 ++++---- contracts/shapes.json | 24 ++++++++---------------- perf/baseline.json | 8 ++++---- scripts/check-contracts.mjs | 12 ++++++++---- scripts/perf_harness.py | 7 +++++-- tests/test_linters.py | 22 ++++++++++++++++++++++ 6 files changed, 51 insertions(+), 30 deletions(-) diff --git a/SKILL.md b/SKILL.md index 088d35c..98d5f94 100644 --- a/SKILL.md +++ b/SKILL.md @@ -172,8 +172,8 @@ The picker table is generated from `contracts/shapes.json`. Checklist and podcas | `triage-board` | Bucketing items into 3–5 columns (Now/Next/Later/Cut), inbox triage, GTD reorg | instrument | `examples/triage-board.html` | copy the example | | `developer` | PR writeups, code review, explain this code — annotated diff with severity findings | instrument | `examples/developer.html` | copy the example | | `editorial` | Argument-driven long-form where the reader absorbs a sustained position | reading | `examples/editorial.html` | `references/shapes/editorial.md` then the example | -| `execution-log` | Live or snapshot execution telemetry — branch status, plan progress, event stream | instrument | `examples/execution-log.html` | `references/shapes/execution-log.md` then the example | -| `deck-review` | Slide deck review/approval — status memo, slide preview, per-slide notes | hybrid | `examples/deck-review.html` | `references/shapes/deck-review.md` then the example | +| `execution-log` | Live or snapshot execution telemetry — branch status, plan progress, event stream, observed state | instrument | `examples/execution-log.html` | `references/shapes/execution-log.md` then the example | +| `deck-review` | Slide deck review/approval surfaces — status memo, slide preview, per-slide notes, send-back workflow | hybrid | `examples/deck-review.html` | `references/shapes/deck-review.md` then the example | | `podcast` | A podcast episode rendered as a briefing alongside a transcript browser | hybrid | `examples/podcast.html` | `references/shapes/podcast.md` then the example | Do not invent a shape. Copy the gold HTML for a stub shape. Load the thick reference before building `editorial`, `podcast`, `execution-log`, or `deck-review`. @@ -226,14 +226,14 @@ The primitive picker is generated from `contracts/primitives.json`. Load `refere |---|---|---| | `donut` | 2–5 mutually-exclusive categories where proportion-at-a-glance beats absolute counts | `examples/primitives/01-donut.html` | | `bar` | ≤12 items where order matters and a single magnitude per item is the signal | `examples/primitives/02-bar.html` | -| `sparkline` | 2–4 hero metrics where each metric is the subject | `examples/primitives/03-sparkline.html` | +| `sparkline` | 2–4 hero metrics where each metric is the subject; current value + recent-trend shape is the one-glance read | `examples/primitives/03-sparkline.html` | | `stacked-bar` | categories sum to a meaningful whole each period; 7–30 periods | `examples/primitives/04-stacked-bar.html` | | `topology` | connections are the point and node count is ≤30 | `examples/primitives/05-topology.html` | | `dense-ops-table` | flat list of records keyed by an identifier; ≥6 rows; filter/sort is the central interaction | `examples/primitives/06-table-ops.html` | | `comparison-matrix` | X vs Y vs Z decision matrix with shared criteria and live weight tuning | `examples/primitives/07-table-comparison.html` | | `annotated-diff` | code changes or line-anchored critique where adjacency to the source beats a side panel | `examples/primitives/08-diff.html` | | `log-stream` | a tail of timestamped events with a categorical level; newest-first reading | `examples/primitives/09-logs.html` | -| `scatter` | two genuinely independent, well-spread dimensions across ≥10 points | `examples/primitives/10-scatter.html` | +| `scatter` | two genuinely independent, well-spread dimensions across ≥10 points where the relationship is the signal | `examples/primitives/10-scatter.html` | ### Cross-cutting rules (primitives only) diff --git a/contracts/shapes.json b/contracts/shapes.json index 59afa27..66e5d08 100644 --- a/contracts/shapes.json +++ b/contracts/shapes.json @@ -6,8 +6,7 @@ "register": "instrument", "requiredFeatures": ["search", "table", "copy_as_prompt"], "example": "examples/dashboard.html", - "href": "dashboard.html", - "reference": "references/shapes/dashboard.md" + "href": "dashboard.html" }, { "id": "document", @@ -15,8 +14,7 @@ "register": "reading", "requiredFeatures": [], "example": "examples/document.html", - "href": "document.html", - "reference": "references/shapes/document.md" + "href": "document.html" }, { "id": "timeline", @@ -24,8 +22,7 @@ "register": "reading", "requiredFeatures": ["filtering"], "example": "examples/timeline.html", - "href": "timeline.html", - "reference": "references/shapes/timeline.md" + "href": "timeline.html" }, { "id": "runbook", @@ -33,8 +30,7 @@ "register": "instrument", "requiredFeatures": ["copy_as_prompt", "toggle"], "example": "examples/runbook.html", - "href": "runbook.html", - "reference": "references/shapes/runbook.md" + "href": "runbook.html" }, { "id": "comparison", @@ -42,8 +38,7 @@ "register": "instrument", "requiredFeatures": ["table", "copy_as_prompt"], "example": "examples/comparison.html", - "href": "comparison.html", - "reference": "references/shapes/comparison.md" + "href": "comparison.html" }, { "id": "network-map", @@ -51,8 +46,7 @@ "register": "instrument", "requiredFeatures": ["search", "inline_svg"], "example": "examples/network-map.html", - "href": "network-map.html", - "reference": "references/shapes/network-map.md" + "href": "network-map.html" }, { "id": "triage-board", @@ -60,8 +54,7 @@ "register": "instrument", "requiredFeatures": ["drag", "copy_as_prompt"], "example": "examples/triage-board.html", - "href": "triage-board.html", - "reference": "references/shapes/triage-board.md" + "href": "triage-board.html" }, { "id": "developer", @@ -69,8 +62,7 @@ "register": "instrument", "requiredFeatures": ["table", "textarea"], "example": "examples/developer.html", - "href": "developer.html", - "reference": "references/shapes/developer.md" + "href": "developer.html" }, { "id": "editorial", diff --git a/perf/baseline.json b/perf/baseline.json index 3a16425..00e87e4 100644 --- a/perf/baseline.json +++ b/perf/baseline.json @@ -1,7 +1,7 @@ { "skill_md": { - "total_bytes": 29794, - "total_tokens": 7394, + "total_bytes": 29935, + "total_tokens": 7429, "sections": [ { "name": "Rendering process", @@ -13,11 +13,11 @@ }, { "name": "Canonical primitives (charts and tables)", - "tokens": 1110 + "tokens": 1134 }, { "name": "Page shapes (pick before designing)", - "tokens": 881 + "tokens": 892 }, { "name": "Copy-as-prompt", diff --git a/scripts/check-contracts.mjs b/scripts/check-contracts.mjs index 504e167..ab38038 100644 --- a/scripts/check-contracts.mjs +++ b/scripts/check-contracts.mjs @@ -84,10 +84,8 @@ for (const shape of shapes) { } if (!shape.example) note(`${shape.id}: missing example`); else if (!exists(shape.example)) note(`${shape.id}: missing ${shape.example}`); - if (THICK_SHAPES.has(shape.id)) { - if (!shape.reference) note(`${shape.id}: missing reference`); - else if (!exists(shape.reference)) note(`${shape.id}: missing ${shape.reference}`); - } + if (THICK_SHAPES.has(shape.id) && !shape.reference) note(`${shape.id}: missing reference`); + if (shape.reference && !exists(shape.reference)) note(`${shape.id}: missing ${shape.reference}`); } const primitiveIds = new Set(); @@ -128,6 +126,9 @@ try { for (const shape of shapes) { if (!shape.id) continue; if (!skill.includes(shape.id)) note(`${shape.id}: SKILL.md does not name this id`); + if (shape.pickWhen && !skill.includes(shape.pickWhen)) { + note(`${shape.id}: SKILL.md pickWhen does not match contracts/shapes.json`); + } if (shape.href && !gallery.includes(shape.href)) { note(`${shape.id}: gallery missing link ${shape.href}`); } @@ -136,6 +137,9 @@ for (const shape of shapes) { for (const primitive of primitives) { if (!primitive.id) continue; if (!skill.includes(primitive.id)) note(`${primitive.id}: SKILL.md does not name this id`); + if (primitive.pickWhen && !skill.includes(primitive.pickWhen)) { + note(`${primitive.id}: SKILL.md pickWhen does not match contracts/primitives.json`); + } if (primitive.href && !primGallery.includes(primitive.href)) { note(`${primitive.id}: primitives gallery missing link ${primitive.href}`); } diff --git a/scripts/perf_harness.py b/scripts/perf_harness.py index d55dc2b..4259855 100644 --- a/scripts/perf_harness.py +++ b/scripts/perf_harness.py @@ -513,6 +513,9 @@ def analyze_shapes() -> dict: "has_reference": has_reference, "has_gallery_link": has_gallery_link, "has_contract": has_contract, + "complete": has_example and has_gallery_link and has_contract and ( + has_reference if name in THICK_SHAPES else True + ), }) return {"clean": not violations, "violations": violations, "shapes": rows} @@ -737,8 +740,8 @@ def to_markdown(report: dict, baseline: dict | None) -> str: "- " + ("clean — zero external resource requests" if report["self_contained"]["clean"] else f"VIOLATIONS: {report['self_contained']['violations']}")] L += ["", "## Shape coverage", - f"- registry: {sum(1 for s in shapes['shapes'] if s['has_example'] and s['has_reference'] and s['has_gallery_link'] and s['has_contract'])}/{len(shapes['shapes'])} complete", - "- " + ("clean — every shape has an example, gallery link, reference, and compact contract" if shapes["clean"] + f"- registry: {sum(1 for s in shapes['shapes'] if s['complete'])}/{len(shapes['shapes'])} complete", + "- " + ("clean — every shape has an example, gallery link, and compact contract; thick shapes keep a reference" if shapes["clean"] else f"VIOLATIONS: {shapes['violations']}")] L += ["", "## Primitive coverage", f"- registry: {sum(1 for p in prim['primitives'] if p['has_file'] and p['has_gallery_link'] and p['has_contract'])}/{len(prim['primitives'])} complete", diff --git a/tests/test_linters.py b/tests/test_linters.py index 8556b43..b72fc66 100644 --- a/tests/test_linters.py +++ b/tests/test_linters.py @@ -189,6 +189,28 @@ def test_check_contracts_fails_when_json_drops_shape_still_named_in_skill(tmp_pa assert dropped["id"] in out or "expected 12 shapes" in out +def test_check_contracts_fails_when_json_lists_missing_stub_reference(tmp_path): + import json + import shutil + + (tmp_path / "contracts").mkdir() + shapes = json.loads((REPO / "contracts" / "shapes.json").read_text()) + stub = next(s for s in shapes["shapes"] if s["id"] == "dashboard") + stub["reference"] = "references/shapes/dashboard.md" + (tmp_path / "contracts" / "shapes.json").write_text(json.dumps(shapes)) + shutil.copy(REPO / "contracts" / "primitives.json", tmp_path / "contracts" / "primitives.json") + for name in ("SKILL.md", "examples", "references", "scripts"): + (tmp_path / name).symlink_to(REPO / name) + r = subprocess.run( + [NODE, str(REPO / "scripts" / "check-contracts.mjs"), "--root", str(tmp_path)], + cwd=tmp_path, + capture_output=True, + text=True, + ) + assert r.returncode == 1, r.stdout + r.stderr + assert "dashboard.md" in (r.stdout + r.stderr) + + # --- review-contracts.mjs ----------------------------------------------------- def test_review_contracts_clean():