Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Visual Feedback Loop

ASecurity

Render → see → critique → edit → re-render: the discipline (and a deterministic referee) that lets an agent inspect its OWN rendered output — a web page, a dashboard, a Power BI / Tableau report — and iterate toward correctness/pixel-perfection against objective stopping signals instead of 'looks better'. The referee (driver.py) merges the pbir-layout-engine layout linter with agent-captured console/Lighthouse evidence into one pass/fail verdict. Use when building or refining any visual surfa...

7 stars
0 votes
0 copies
0 views
Added 9/23/2026
ai-agentspythonrustgogitsecurityperformance

Works with

climcp

Security Analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned 9/23/2026

$npx -y skills add mcorbett51090/RavenClaude --skill visual-feedback-loop --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Visual Feedback Loop?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Visual Feedback Loop
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mcorbett51090-visual-feedback-loop/badge)](https://www.skillsdirectory.com/skills/mcorbett51090-visual-feedback-loop)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: visual-feedback-loop
description: "Render → see → critique → edit → re-render: the discipline (and a deterministic referee) that lets an agent inspect its OWN rendered output — a web page, a dashboard, a Power BI / Tableau report — and iterate toward correctness/pixel-perfection against objective stopping signals instead of 'looks better'. The referee (driver.py) merges the pbir-layout-engine layout linter with agent-captured console/Lighthouse evidence into one pass/fail verdict. Use when building or refining any visual surface; the standalone canon is knowledge/visual-feedback-loop.md."
---

# Skill: visual-feedback-loop

## What this is

A render-loop **referee** plus the discipline that wraps it. The agent that builds
a visual surface should not work blind: it renders, **sees** its own output, judges
it against the intent **and** objective signals, edits, and re-renders — until the
signals pass. This skill provides the deterministic "are we done yet?" half so the
loop **converges** instead of wandering on subjective taste.

The full conceptual canon — the loop, the two ways to "see", the surface→mechanism
map, the security rules — lives in
[`../../knowledge/visual-feedback-loop.md`](../../knowledge/visual-feedback-loop.md).
This SKILL is the operating reference for the runnable piece.

## The two ways an agent "sees"

| Mode | How | Best for |
|---|---|---|
| **Visual** (pixels) | Drive a real browser via the `chrome-devtools-mcp` server → `take_screenshot` (the model literally sees it), `list_console_messages`, `lighthouse_audit` | Web pages, web dashboards, embedded BI — catching "looks wrong" |
| **Structural** (coordinates) | Read the layout definition's exact numbers — PBIR JSON `x/y/width/height` via the [`pbir-layout-engine`](../pbir-layout-engine/SKILL.md) linter; the DOM/accessibility tree for web | **Power BI / Tableau pixel-perfection** (layout is just numbers — more reliable than vision), and any surface where the definition is inspectable |

**Structural-first for BI.** For Power BI / Fabric / Tableau the *primary* loop is
structural (the coordinate linter), because (a) layout correctness is exact
arithmetic, not a judgment, and (b) screenshotting a BI report needs it
published/embedded + authenticated, which the agent often can't reach. Screenshots
are the *secondary* check for what coordinates can't show (did the theme apply, did
conditional formatting fire, does it overlap once real data loads). For **web**, the
screenshot is first-class.

## The referee — `driver.py`

`driver.py` is **NOT** a browser driver. It cannot navigate Chrome or run
Lighthouse — that is the agent's job via `chrome-devtools-mcp`. It is the
**referee**: given the evidence the agent has captured, it merges it into one
verdict with an objective `next_action`. It earns its existence by fanning **three
independent evidence sources** into one verdict — something the layout linter alone
structurally can't do.

### CLI contract

```text
python3 plugins/ravenclaude-core/skills/visual-feedback-loop/driver.py <config.json>
```

`<config.json>` (every path is repo-relative, `..`-free, inside the repo root):

```json
{
  "surface": "web | web-dashboard | pbir | fabric | tableau | bi",
  "layout": "path/to/page.json",          // optional → delegated to pbir-layout-engine
  "parity": {                              // optional → structural diff vs. a known-good exemplar
    "candidate": "path/to/failing/visual.json",
    "reference": "path/to/confirmed-working/visual.json"
  },
  "console": "path/to/console.json",       // optional → agent-captured browser console
  "lighthouse": "path/to/lighthouse.json", // optional → agent-captured Lighthouse run
  "design_schema": {                       // optional → offline "declares the same design system?" floor
    "candidate": "path/to/candidate-design-schema.json",
    "reference": "path/to/reference-design-schema.json"
  },
  "ssim": "path/to/ssim.json",             // optional → agent-captured {"ssim_score": <0..1 float>}
  "thresholds": {                          // optional — overrides the defaults
    "lighthouse_accessibility_min": 90,
    "lighthouse_performance_min": 80,
    "lighthouse_best_practices_min": 80,
    "max_console_errors": 0,
    "ssim_min": 0.90,
    "design_ratio_tolerance": 0.05
  }
}
```

**The `parity` gate — diff against a known-good exemplar** (a structural diff
surfacer, **not** a render oracle — it is only as good as the reference you pick).
A visual can be *perfectly placed* yet render **blank** because its render skeleton
is missing something its working twin has. Point `candidate` at the suspect
`visual.json` and `reference` at a confirmed-working `visual.json` **of the same
`visualType`**. The gate is **asymmetric**: it **fails**
(`next_action: match-reference-exemplar`) on what the candidate is **MISSING**
relative to the exemplar — a missing query role (`Values`/`Data`/`Indicator`), a
dropped objects key (e.g. a `card` that dropped `labels` and substituted
`calloutValue`), or a missing per-item `$id` — and **passes benign additions** (an
extra cosmetic object key, an optional role). It reports `not_captured` (never a
false fail) for a different `visualType`, a non-PBIR shape, a **self-reference**,
or a **degenerate exemplar** (no query role — it refuses to launder a bad reference
into a pass). Highest-leverage move when a deploy renders blank with no error:
replicate the nearest *genuinely-working* exemplar instead of guess-and-check.
(PBIR `visual.json` today; the *technique* generalizes — see the canon.)

**Design-schema mimicry — two fidelity mechanisms, honestly labelled.** When cloning
a reference's *design craft* (spacing scale, type scale, elevation, breakpoints,
components) onto your own brand, the referee gains two independent checks:

- **`design_schema` — the offline structural FLOOR (NOT fidelity).** A per-dimension
  **asymmetric** diff of a candidate `design-schema.json` against a reference one:
  *"does the candidate declare the same design system?"* It **fails** on what the
  candidate is **MISSING** relative to the reference — a different spacing base-unit, a
  type ratio outside `design_ratio_tolerance`, fewer elevation levels, missing
  breakpoints, a missing component recipe — and **passes benign additions** (an extra
  breakpoint/component/shadow), exactly like the parity gate. Each divergence is
  localized as a `{dimension, expected, actual}` delta. A missing/unparseable/non-schema
  file → `not_captured` (absence is not failure). **This is a stdlib "same design
  system?" sanity check — it does NOT and cannot compare pixels.** Every value it reads
  self-declares `capture_method` (`static` = parsed declared CSS, no browser).
- **`ssim` — the browser-captured fidelity GATE.** Reads `{"ssim_score": <float>}`
  (the Lighthouse-evidence pattern) and passes iff `ssim_score >= ssim_min`. This is the
  *only* pixel-fidelity signal, and it exists **only when a browser tool captured it**.
  The score is **domain-clamped**: a value that is non-finite (NaN/inf) or outside
  `[0,1]` is corrupt/hostile evidence → a determinate **error**, **never** a pass; an
  absent field → `not_captured`. (The same clamp now guards `lighthouse` category scores
  — a page-injected `5.0` no longer rescales to a fake pass.)

**LOUD degradation — a green verdict with `ssim` absent reads as "fidelity unverified".**
When the structural floor passes but no `ssim` pass verified fidelity, the referee does
**not** emit a bare ship: `next_action` is `capture-ssim-evidence` and `notes` carries
`"visual fidelity not verified — no browser tool"`. Structural-clean is never mistaken
for pixel-faithful.

> **Security invariant (SSIM).** The SSIM score MUST be computed **out-of-page** over
> harness-controlled screenshot buffers (the browser/MCP layer), **NEVER** via
> `page.evaluate` inside the measured page — a page that can compute its own fidelity
> number can forge a pass. `driver.py` reads the captured *number* only and clamps it to
> `[0,1]`; it never trusts a page-controllable value as fidelity.

> **Stateless-loop boundary (read this LOUD).** `driver.py` is **stateless per
> invocation** — it builds its gates fresh from one config and holds **no** iteration
> history. **The determinate structural + `ssim` gate is the only stopping proof; one
> pass is NOT convergence.** Non-improving-iteration patience (did this edit actually
> move the score? are we oscillating?) is the **agent's** cross-iteration job — the
> referee cannot and does not track it.

**Agent-captured evidence shapes** (the contract you fill from `chrome-devtools-mcp`):

- `console.json` — `{"messages": [{"level": "error|warning|info"}, ...]}` (the
  driver counts `level == "error"`; it never reads the message text).
- `lighthouse.json` — Lighthouse's native shape `{"categories": {"accessibility":
  {"score": 0.96}, "performance": {"score": 0.85}, ...}}` (scores are 0–1; the
  driver surfaces them as 0–100 and compares to the threshold, and now **clamps** each
  score to `[0,1]` — a non-finite / out-of-domain value is skipped, never a fake pass).
- `ssim.json` — `{"ssim_score": <float 0..1>}` (a browser/harness-computed structural
  similarity index vs. the reference render; the driver reads the number only, clamps it
  to `[0,1]`, and never echoes page content — see the SSIM security invariant above).

### Exit codes & verdict

| Exit | Meaning |
|---|---|
| `0` | `passed: true` (clean) **or** `passed: null` (nothing determinate to judge / needs more evidence / manual review). **Absence of a browser tool is NOT a failure.** |
| `1` | `passed: false` — a determinate gate failed |
| `2` | I/O, parse, oversize (>5 MiB), or path-rejection (`..` / outside repo) — the purity-contract failure |

The JSON envelope: `{schema_version, driver_version, surface, passed, gates[],
next_action, notes}`. `passed` is a **pure function of the determinate gates**
(`pass`/`fail`/`error`); `not_captured` and `degraded` gates are excluded — so
"evidence not captured yet" and "tooling absent" are first-class states, never
silent failures. `next_action` is the loop's instruction: `ship` /
`capture-runtime-evidence` / `capture-ssim-evidence` / `fix-layout` /
`match-reference-exemplar` / `match-design-schema` / `improve-visual-fidelity` /
`fix-console-errors` / `improve-accessibility` / `manual-visual-review`.

### How the layout gate maps the linter's exit codes

`driver.py` calls `pbir-layout-engine/lint.py` as a **subprocess** (`--format json`),
never an import — it treats the linter as a CLI with a versioned envelope, so the
linter's internals are not a dependency, and it asserts the linter's
`schema_version` matches what it was built against (loud on drift). The mapping:

| linter exit | driver gate status | meaning |
|---|---|---|
| `0` | `pass` | layout clean |
| `1` | `fail` | a layout check fired |
| `2` | `error` (→ overall fail) | the layout JSON itself is broken |
| `3` | `degraded` (excluded from `passed`) | the PBIR `visualType` enum reference is absent/unparseable — e.g. `ravenclaude-core` installed without `power-platform`. Could-not-verify, **not** a failure. |

## The loop (how the agent uses this)

1. Build / edit the surface.
2. **See it.** Web: via `chrome-devtools-mcp` → `navigate_page`, `take_screenshot`
   (look at it), save `list_console_messages` → `console.json` and
   `lighthouse_audit` → `lighthouse.json`. BI: read the PBIR page JSON.
3. **Referee.** Run `driver.py <config.json>` pointing at the evidence + layout.
4. Read `passed` / `next_action`. `false` → do the `next_action` and loop.
   `null` → capture the missing evidence (or do the named manual review) and loop.
   `true` + `next_action: ship` → done.
5. The screenshot is for *your* eyes (the model's visual judgment); the referee is
   the objective floor. Use both — vision catches "ugly", the referee catches
   "wrong", and the referee is what tells you when to **stop**.

## Security invariants (load-bearing — see the knowledge file for the full rules)

- **Path safety.** Every input path is resolved through the same rule as the layout
  linter (reject `..`, reject outside-repo). Reimplemented in `driver.py` (not
  imported, to stay decoupled from the linter's internals); **Gate 100 asserts the
  two guards reject the same traversal input** so they can't drift.
- **Size ceiling.** A 5 MiB cap is enforced *before* `json.load` on every file — a
  malicious page can write an unbounded `console.json`.
- **No-echo.** The verdict carries **only** driver-derived primitives — booleans,
  counts, numeric scores/thresholds, and fixed-vocabulary strings. It **never**
  echoes raw console text, Lighthouse titles, or page content: a hostile page can
  write fake "instructions" to the console, and the model reads this verdict back as
  trusted context. The driver reads *numbers* out of evidence, never prose.
- **MCP adoption gate.** `chrome-devtools-mcp` drives a **live** browser
  (stateful, side-effecting); it is **recommended-not-bundled** and its adoption is
  `security-reviewer`-gated. Do **not** point a credentialed/networked render loop
  at attacker-influenced URLs — render untrusted dashboards against synthetic/fixture
  data, or in an isolated profile with no credentials. Launch with
  `--no-usage-statistics`. Screenshots/evidence write to
  **`.ravenclaude/runs/<session>/visual-evidence/`** (already git-ignored) and are never
  committed (a dashboard can render real PII/secrets).

## Proven by Gate 100

[`scripts/audit-gates.sh`](../../../../scripts/audit-gates.sh) Gate 100 +
[`hooks/tests/test-gate100-visual-feedback-loop.sh`](../../hooks/tests/test-gate100-visual-feedback-loop.sh)
\+ the fixtures under [`tests/fixtures/visual-feedback-loop/`](../../../../tests/fixtures/visual-feedback-loop/)
are the bidirectional floor: good fixtures pass, bad fixtures fail, a `..` config is
rejected (and the linter rejects the same shape — path-guard parity), and an
always-pass mutant lets a known-bad through (teeth).

## Output Contract

When a reviewer critiques a render-loop integration in a PR, the response ends with
the cross-plugin Structured Output JSON block per
[`../structured-output/SKILL.md`](../structured-output/SKILL.md).

Attribution

mcorbett51090mcorbett51090
View sourceSee grades on GitHubMore from mcorbett51090 →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Terse caveman voice: answer first, fluff gone, every technical fact kept. Use for /caveman, "caveman mode", "talk like caveman", "be brief", "less tokens". Stays on until "stop caveman" or "normal mode".

1100021 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

698461 votes

Writing Skills

Create and manage Claude Code skills in HASH repository following Anthropic best practices. Use when creating new skills, modifying skill-rules.json, understanding trigger patterns, working with hooks, debugging skill activation, or implementing progressive disclosure. Covers skill structure, YAML frontmatter, trigger types (keywords, intent patterns), UserPromptSubmit hook, and the 500-line rule. Includes validation and debugging with SKILL_DEBUG. Examples include rust-error-stack, cargo-dep...

3931 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3421 votes

catchup

Recovers the conversation and failed tool calls of a previous Codex, Amp, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Grok Build, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", asks to recover/summarize a previous session before continuing, or asks to diagnose or report a catchup failure. Do NOT use for the current conversation, git history, or any non-agent log.

741 votes
View all in ai-agents →