Skip to content

Commit 1f4da76

Browse files
carlrannabergclaude
andcommitted
step 5 recalibrated: vendor + slim detector + split rules into detector vs review-prompt
Wholesale rewrite of the detector after a 4-subagent audit surfaced substantial gaps in the original 26-rule set. Key reframe: not every "slop rule" should be a deterministic detector rule. Some are vocab-unbounded, brand-and-register-dependent, or structural-pattern judgment calls that LLM judgment in the /visualize:review prompt handles better than regex thresholds. # What got split DETECTOR (kept, all mechanical): fossil: lorem-ipsum, citation-artifact (incl. 【N†source】 / ASSISTANT: / model_assistant: variants), agent-attribution ("Generated by Claude / by AI / with AI assistance") slop: generic-gradient (now incl. radial- / conic-gradient + OKLCH- aware corridor detection via culori), gradient-text, dark-glow (blur threshold lowered to 12px; chroma via culori), glassmorphism (NEW: backdrop-filter + translucent surface), emoji-heading (first char only), monotonous-spacing, bounce-easing (now catches spring-style y1>1 overshoot), side-tab (now matches longhand border-X-width/-color), system-default-font (brand-aware, fires only when DESIGN.md fonts declared) a11y: missing-alt (incl. lazy-alt detection: "image" / "DSC_0042" / " " / filenames), missing-lang (rejects lang=""), empty-link, low-contrast (REAL — culori-powered WCAG ratio; skips transparent / .is-pending / opacity:0), heading-structure (NEW: missing h1 / multiple h1 / skipped levels) meta: missing-title, missing-favicon, missing-og (rejects empty content=""), external-script (allowlist now mermaid + chart.js on jsdelivr, plus unpkg + displaydev per spec; PROMOTED to error severity per oracle review) perf: layout-thrash (catches `transition: all` plus width/height/ top/left; progress-bar shapes allowlisted via class-name regex), img-no-dimensions (NEW: CLS risk) MOVED TO REVIEW-PROMPT (LLM judgment in SKILL.md): - fossil/mock-identity (real names vs invented unbounded) - fossil/slop-opener (vocab unbounded — "leverage / streamline / empower / cutting-edge / In conclusion / TL;DR:") - fossil/sycophant-footer (vocab unbounded — "Best, Claude / Hopefully this addresses / Generated with AI") - fossil/imperative-tricolon (sloppy vs poetic — judgment call) - slop/ai-color-palette (genuine palette vs brand identity) - slop/icon-tile-stack (structural pattern, register-dependent) - slop/center-everything (Bauhaus / Swiss / academic FP) - slop/uppercase-body (acronyms vs body — HTML / POST / WCAG flooded the corpus with false positives) - triple-feature-card / trust-signal slop / infinite animation / responsive overflow (no clean detection pattern) # Vendored libs visualize/scripts/vendor/node-html-parser.mjs (~381KB, MIT) + visualize/scripts/vendor/culori.mjs (~95KB, MIT). Bundled inline via esbuild from npm packages node-html-parser@7.1.0 and culori@4.0.2. No package.json, no `npm install` step — `npx skills add display-dev/ visualize` brings everything. Re-bundle command documented in visualize/scripts/vendor/README.md. # Bug fixes from subagent audit - Per-locator dedup collapsed N findings to 1 when locator was static. Fixed by including the rule's finding-index in the dedup key. - brandProfile never flowed through CLI. Now auto-loads DESIGN.md from any ancestor of the artifact path; --brand <path> overrides. - a11y/low-contrast was a placeholder emitting noisy info-skip lines. Now actually computes contrast via culori; skips intentionally-invisible selectors (.is-pending / transparent / opacity:0 / .visually-hidden). - meta/external-script allowlist updated to spec (unpkg + displaydev added; promoted from warn to error per oracle). - CSS comments now stripped before selector matching (prevented false positive on slide-deck where the brand-overlay comment matched a perf/layout-thrash rule). # Smoke results - Adversarial slop fixture (lorem-ipsum + gradient text + AI palette + citation comment + missing alt + missing title + dark glow + …): 8 errors / 4 warnings / 2 info. --strict exits 2. - Full 46-file template catalogue: 0 errors. Only 1 legitimate warning remains (api-reference template's real h1→h4 heading skip — worth fixing in a separate template-polish pass, not a detector issue). - detect.mjs runs <30ms per template (well under the 100ms budget). # What's new in /visualize:review SKILL.md section "Review verb procedure" rewritten as two distinct layers: (1) deterministic detect.mjs findings, (2) LLM judgment with the moved rules listed explicitly so the agent reads them and applies register awareness / brand context / semantic judgment the detector deliberately doesn't try. Replaces the 26-rule single-layer detector from c6a2408. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c6a2408 commit 1f4da76

22 files changed

Lines changed: 40516 additions & 2269 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,14 @@ jobs:
4141
run: node visualize/scripts/check-library-policy.mjs --quiet
4242

4343
- name: Detector smoke — every template passes --strict
44-
# Runs detect.mjs against every template.html (canonical + variants)
45-
# in --strict --fast mode. Any error-severity finding fails the build.
46-
# The canonical templates ARE the reference; if a template fails the
47-
# detector, the rule set or the template is wrong.
44+
# Runs detect.mjs against every template.html (canonical + variants).
45+
# Any error-severity finding fails the build. Includes WCAG contrast
46+
# math via the vendored culori library.
4847
run: |
4948
for f in $(find visualize/templates -name '*.html'); do
50-
if ! node visualize/scripts/detect.mjs --strict --fast "$f" > /dev/null; then
49+
if ! node visualize/scripts/detect.mjs --strict "$f" > /dev/null; then
5150
echo "detector failed on $f"
52-
node visualize/scripts/detect.mjs --strict --fast "$f"
51+
node visualize/scripts/detect.mjs --strict "$f"
5352
exit 1
5453
fi
5554
done

‎CONTRIBUTING.md‎

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,44 @@ Drop a new directory under `visualize/themes/<slug>/` with `tokens.css` (CSS cus
2222

2323
## Anti-slop detector
2424

25-
Templates and (where applicable) brand-overlay-applied artifacts must pass the deterministic anti-slop detector:
25+
Templates must pass the deterministic detector:
2626

2727
```sh
28-
node visualize/scripts/detect.mjs --strict --fast path/to/artifact.html
28+
node visualize/scripts/detect.mjs --strict path/to/artifact.html
2929
```
3030

31-
26 rules across 5 categories: `fossil` (lorem-ipsum, mock identity, citation artifacts, sycophant footers, stock LLM openers), `slop` (system-default fonts, generic gradients, gradient text, AI palette, dark glow, icon-tile stacks, emoji headings, center-everything, monotonous spacing, bounce easing, uppercase body, side-tab), `a11y` (missing alt, missing lang, empty link, low contrast), `meta` (missing title, favicon, OG tags, external script not on allowlist), `perf` (layout-thrash transitions).
31+
~23 rules across 5 categories — all mechanical patterns regex or `node-html-parser` can identify reliably, plus WCAG contrast computed via the vendored `culori` library:
3232

33-
CI runs the detector against every template file in `--strict --fast` mode; any `error`-severity finding fails the build. `--fast` skips the contrast check (which needs `jsdom`; ships as info-skip otherwise).
33+
- **fossil** — lorem-ipsum, citation artifacts (turn0search0 / `<!-- claude: ` / 【N†source】 / ASSISTANT:), AI-attribution disclaimers ("Generated by Claude / with AI assistance")
34+
- **slop** — generic gradients (purple→pink / indigo→cyan), gradient text, dark glow (text-shadow with high blur + chroma), glassmorphism (backdrop-filter + translucent), emoji headings, monotonous spacing, bounce easing, side-tab, system-default fonts (when brand-aware)
35+
- **a11y** — missing alt (including lazy alts like `alt="image"` / `alt="DSC_0042"`), missing/empty lang, empty links, WCAG-AA contrast, heading structure (missing h1, multiple h1, skipped levels)
36+
- **meta** — missing title, missing favicon, missing OG / Twitter card, external script outside the library-policy allowlist
37+
- **perf** — layout-thrash (transition on width/height/top/left, plus `transition: all`), img without width/height (CLS risk)
3438

35-
Output formats: human-readable default, `--json` for NDJSON. Programmatic use:
39+
CI runs `node detect.mjs --strict <file>` against every template; any `error`-severity finding fails the build.
40+
41+
**Rules deliberately NOT in detect.mjs** — vocab-unbounded fossils (mock identity beyond the obvious / slop openers / sycophant footers / imperative-tricolon), brand-and-register-dependent slop (ai-color-palette / icon-tile-stack / center-everything / uppercase-body), and structural-pattern slop the detector can't distinguish from legitimate design (triple-feature-card / trust-signal slop / scroll-jacking / responsive overflow). Those live in the `/visualize:review` LLM-judge prompt in `SKILL.md` § "Review verb procedure" — the agent reads them when reviewing an artifact and applies judgment the detector can't.
42+
43+
The split is deliberate: mechanical patterns get detector rules (cheap, deterministic, CI-enforced); judgment-shaped patterns get prompt instructions (require register awareness, brand context, semantic understanding).
44+
45+
### CLI
46+
47+
- Output: human-readable default; `--json` for NDJSON.
48+
- `--strict` — exit 2 on any error finding.
49+
- `--skip slop/emoji-heading,a11y/missing-alt` — comma-separated rule IDs to skip.
50+
- `--config .visualize-detect.json` — config file (severity overrides + skip list).
51+
- `--brand DESIGN.md` — explicit brand profile path (auto-loaded from any ancestor of the artifact otherwise).
52+
53+
### Programmatic
3654

3755
```js
3856
import { detect } from './visualize/scripts/detect.mjs';
39-
const findings = await detect({ path: './artifact.html', skip: ['slop/emoji-heading'] });
57+
const findings = await detect({ path: './artifact.html', skip: ['slop/emoji-heading'], brandProfile: { fonts: ['sohne'] } });
4058
```
4159

42-
Configuration via `.visualize-detect.json` at the project root (severity overrides, skip lists). See the spec § "Anti-slop detector backs the `review` verb" for the rule contracts and the F103 audit history.
60+
### Vendored dependencies
61+
62+
`detect.mjs` imports two libraries from `visualize/scripts/vendor/`: `node-html-parser` (real DOM walking) and `culori` (WCAG contrast). Both bundled inline (no `npm install` required after `npx skills add display-dev/visualize`). See `visualize/scripts/vendor/README.md` for re-bundle instructions when upstream releases.
4363

4464
## Library / CDN policy
4565

‎hermes/design/visualize/SKILL.md‎

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -105,29 +105,54 @@ The combination of universal HTTP fallback + unauthenticated publish path means
105105

106106
## Review verb procedure
107107

108-
`/visualize:review <path>` always runs in two layers:
108+
`/visualize:review <path>` runs in two distinct layers — deterministic checks via `detect.mjs`, then LLM judgment via the rules below. Each layer catches what the other can't reliably see.
109109

110-
**Layer 1: deterministic findings via `detect.mjs`.** The agent shells out to:
110+
### Layer 1 — deterministic findings via `detect.mjs`
111111

112112
```sh
113-
node $SKILL_DIR/scripts/detect.mjs --json --fast <path-to-artifact.html>
113+
node $SKILL_DIR/scripts/detect.mjs --json <path-to-artifact.html>
114114
```
115115

116-
NDJSON output, one finding per line plus a trailing summary line. Each finding has `{ ruleId, category, severity, locator, message, snippet?, suggestion? }`. Severities: `error` / `warn` / `info`. Categories: `fossil` / `a11y` / `meta` / `slop` / `perf`. The 26 rules cover the failure modes the agent reliably reaches for — gradient text, AI palette, generic gradients, dark glow, fossil openers, citation artifacts, sycophant footers, accessibility basics, publishing-hygiene meta tags. Errors are ship-blockers per universal law 3; warns + infos are taste-calls the agent triages.
116+
NDJSON: one finding per line + trailing summary. Each finding carries `{ ruleId, category, severity, locator, message, snippet?, suggestion? }`. The detector auto-loads `DESIGN.md` from any ancestor of the artifact path so the brand-aware rules (currently `slop/system-default-font`) work; pass `--brand <path>` to override.
117117

118-
`--fast` skips the contrast check (which needs jsdom; ships as info-skip otherwise). Drop `--fast` for the full check when jsdom is installed.
118+
The detector ships ~23 rules — all of them mechanical: HTML/CSS patterns regex can identify or that `node-html-parser` walks reliably, plus WCAG contrast computed via `culori`. Categories: `fossil` (placeholders, citation artifacts, AI-attribution disclaimers), `slop` (gradients, gradient text, glow, glassmorphism, emoji headings, monotonous spacing, bounce easing, side-tab, font when brand-aware), `a11y` (alt, lang, empty link, low contrast, heading structure), `meta` (title, favicon, OG, external script), `perf` (layout thrash, missing img dimensions).
119119

120-
**Layer 2: LLM judgment on top.** Once the agent has the deterministic findings, it adds the qualitative reading the detector can't see:
120+
### Layer 2 — LLM judgment in this prompt
121121

122-
- Brand-profile-overrides per universal law 1: a `slop/system-default-font` finding on a brand that explicitly declares Inter is a category error — demote to info.
123-
- Aggregate-as-tell per universal law 4: 6 `slop/center-everything` info findings = ship-blocker; 1 = taste-call.
124-
- Genre-reflex check: did the artifact fall into its template's stock aesthetic (red banners on Postmortem; purple gradients on AI essay)?
125-
- Hierarchy + scanability: does the eye land in the right place first?
126-
- The 6 universal laws as the override grid for any single-rule finding.
122+
Things detect.mjs deliberately doesn't try to catch — register-dependence, vocab-unboundedness, semantic-structure calls. The agent must read the artifact and assess:
127123

128-
**Output to the user:** prioritised list, errors first, with the brand-overlay context applied. The agent never just dumps the NDJSON — it interprets.
124+
**Fossil-shape failures** (vocab is unbounded, so no detector list):
129125

130-
`--strict` exits 2 on any error finding (for CI consumers). The `/visualize:review` verb itself never sets `--strict` — it's an audit, not a gate.
126+
- **Mock identity** beyond the obvious — fictitious customer quotes ("Sarah Johnson, VP Marketing"), placeholder phone / address / company / brand names that aren't in the obvious set (Acme, Globex, Anytown, TBD, XXX-XXX-XXXX).
127+
- **Slop openers** — "leverage / streamline / empower / unlock / robust / holistic / seamless / cutting-edge / In conclusion / TL;DR:" packed into the opening paragraphs.
128+
- **Sycophant footers** — "I hope you find this useful", "Happy to clarify", "Hopefully this addresses your needs", "Best, Claude", anything signed by an AI.
129+
- **Imperative tricolon** ("Ship faster. Build smarter. Scale forever.") — three commands ending in periods, regardless of content. Can't be redeemed.
130+
131+
**Brand- and register-dependent slop** (mechanical detector would false-positive):
132+
133+
- **AI color palette** — is the primary / accent stack a generic purple-pink-cyan trio, OR is it the brand's actual identity?
134+
- **Icon-tile-stack** — every section heading has a rounded-square icon above it. Genuine AI section-marker tell, or is this a deliberate icon-led design system?
135+
- **Center-everything** — body prose centered, section after section. False positive for Bauhaus / Swiss-poster / academic preprint registers; real signal for AI-essay register.
136+
- **Uppercase body** — long all-caps passages. False positive for brutalist / letterpress / show-bill registers; real signal otherwise. Acronyms (HTML / POST / REST / WCAG) don't count.
137+
138+
**Structural-pattern slop** the detector can't reliably distinguish from legitimate design:
139+
140+
- **Triple-feature-card** — 3-column grid of `<h3>adjective + noun</h3>` blocks with one paragraph each. The AI-landing canonical pattern.
141+
- **Trust-signal slop** — "Backed by leading investors", "10,000+ teams", template testimonial stack.
142+
- **Infinite animations / parallax / scroll-jacking** — sometimes deliberate, often not.
143+
- **Responsive overflow** — fixed min-width above 320px; `100vw + padding`; long button text that mobile-clips. Detector can't simulate viewport.
144+
145+
### Override grid (apply to BOTH layers)
146+
147+
- **Universal law 1** — brand profile overrides any single-rule finding. A `slop/system-default-font` hit on a brand that declares Inter is a category error; demote.
148+
- **Universal law 4** — info-severity rules ship-block in aggregate. 6 `slop/emoji-heading` infos across a presentation = pattern; 1 = taste-call.
149+
- **Universal law 6** — the reader is the judge. Does each finding make the reader's job harder, or just satisfy a rule?
150+
151+
### Output to the user
152+
153+
Prioritised list. Errors first (detector + the unbounded-vocab fossils the agent catches in layer 2). Warnings + info aggregated by category, with the brand-overlay context applied. Never dump raw NDJSON; always interpret.
154+
155+
`--strict` exits 2 on any error finding — for CI consumers, not the `/visualize:review` verb itself.
131156

132157
## Brand profile
133158

0 commit comments

Comments
 (0)