Site Design Audit → Wireframes → Decision Ledger
Purpose
Turn a local clone of a website into: (1) hashed evidence pinned to a git commit, (2) findings that each cite a rule and point to likely source files, (3) as-is and to-be wireframes in Figma, (4) decision records explaining every change, (5) human approval records, and (6) implementation briefs for approved decisions that Claude Code or Codex carries out in a separate session. The git ledger is the source of truth. Figma is a projection of it. This skill designs and records; it never edits the site's source.
Intended workflow
- The student clones their site's repo (from GitHub, or the repo a Vercel project deploys from) and runs it locally. Students are expected to have education access to the Figma MCP.
- This skill captures from the local dev server, audits, and builds wireframes in Figma.
- A human triages findings, reviews decisions, and approves.
- This skill writes a handoff brief per approved decision. Claude Code or Codex implements each one on its own branch. This skill recaptures to verify.
Commands
The skill folder has a command, fwd, and a CLAUDE.md that says how to answer it. When the user types help, check, init or tutorial, follow CLAUDE.md:
helprunsfwd help(setup checks plus one next step) and, with the Figma tools present,whoamifor the seat. Missing things first; one next step; no changes made on the student's behalf.tutorialruns the seven-step first tutorial (redesign your home page). It is this skill's own phases, driven step by step.fwd approve|reject|defer <D-id>is the student's, run in their own terminal. It writes the approval file and the ledger event. Non-negotiable 1 applies: never run it, never writeaudit/approvals/.fwd ledger verifychecks the hash chain and that each approval file matches the ledger.
Non-negotiables
- Only a human may set a decision to approved, rejected, or deferred, or reopen one. Never write those states. Never write files in
audit/approvals/. - Every finding cites a
rule_idfromaudit/rulebook/rules.yaml. No rule, no finding: record it as anobservationfor the human. - Finding labels follow the rule tier: normative →
failure, heuristic →concern, house →deviation. Never call a heuristic finding a failure. Never use pass/fail icons or words (✅ ❌ "violated") on heuristic findings. - Every difference between an as-is frame and its to-be frame carries at least one decision ID. Flag untagged differences; don't absorb them.
- Never edit the
00 As-Ispage. Never modify or delete nodes the skill didn't create. Only touch annotations in the skill's own categories, and always read-merge-write. - Never set Dev Mode status (Ready for dev / Completed). Never resolve or delete human comments.
- No Figma writes into an existing file until the human has saved a named baseline version and its ID is recorded. Exception: a Figma file this run created itself. It starts empty, so the empty file is its own baseline; record that in
capabilities.json. - Capture only from a local clone of a repo the user owns or has rights to work on, served from localhost, or from the deployed site or a Vercel preview of that same repo when the user supplies its URL. Never capture, inject scripts into, or strip security headers on third-party live sites.
intent.yamlmust record the repo and the pinned commit. - Never overwrite a previous findings frame or earlier run output. Re-runs create a new versioned frame (see Phase 7). Never locate your own nodes by layer name, fill color, or text heuristics; use the node IDs recorded in the ledger.
- Never edit the site's source code. Implementation happens only in a separate Claude Code or Codex session, from a handoff brief for an approved decision.
Directory layout
audit/
intent.yaml # human-populated (Phase 0)
rulebook/rules.yaml # human-confirmed (Phase 1)
schema/ # finding, decision, approval, event schemas
runs/R-YYYYMMDD-NN/
capabilities.json # what this run could actually do
manifest.json # capture hashes and state
captures/{width}/ # screenshot.png, dom.html, axe.json, checks.json, skeleton.json
findings.json
decisions/D-NNNN.json
approvals/A-NNNN.json # humans only
handoff/ # implementation briefs, approved decisions only
README.md # shared rules for the implementer agent
D-NNNN.md # one brief per approved decision
ledger.jsonl # append-only, hash-chained events
The site's clone lives outside audit/ (at local_path). This skill reads it and never writes to it. audit/ can sit in a sibling folder or on its own branch, so audit records never mix with site commits.
Phase 0 — Intent (Gate G0)
If audit/intent.yaml is missing or has empty required fields, ask for the missing fields conversationally, one or two at a time. Do not capture, evaluate, or write anything until every required field is filled by the user. If the user has not cloned the repo yet, give them the exact git clone command (for a Vercel project, the GitHub repo it deploys from) in its own code block and wait.
repo: "" # required: GitHub URL (or Vercel project) of the site
local_path: "" # required: path of the local clone
commit: "" # required: commit SHA the audit is pinned to
dev_command: "" # required: e.g. "npm run dev"
dev_url: "http://localhost:3000"
routes: [] # required: paths to audit, e.g. ["/", "/apply"]
ownership: "" # required: "own repo", or who granted permission, and when
implementer: "" # required: "claude-code" or "codex"
conformance_target: "WCAG 2.2 AA"
breakpoints: "derive" # "derive" from site CSS, or a list of widths
house_rules: "" # path to design-system tokens, or "none"
figma_file: "" # required for Figma phases; blank = JSON-only mode
approvers: [] # required: names and roles
out_of_scope: []
Phase 1 — Schema (Gate G1)
Summarize the rulebook (rule count by tier and mode) and the record schemas. Ask the user to confirm or edit. Record a schema.confirmed ledger event with the user's name and the date. Do not proceed without it.
Runtime capability probe
Before Phase 2, write capabilities.json:
- Which Figma MCP tools are present (
use_figma,generate_figma_design,html_to_figma,get_metadata,get_screenshot,generate_diagram,upload_assets). whoamiresult: seat type on the plan that ownsfigma_file. If writes aren't possible, setmode: json-onlyand skip Figma writes for the run. Students are expected to have education-plan access; still record the actual seat and plan rather than assuming, and note any rate-limit errors.- Local environment: Node and package-manager versions, whether
dev_commandstarts, and whether the working tree atlocal_pathis clean and atcommit. - Load Figma's
figma-useskill before anyuse_figmacall. - Read the live
use_figmatool schema and record whether it accepts askillNamesparameter. A comparable Figma skill (Throughline) passesskillNames: "throughline"on every call. If the parameter exists, passskillNames: "figma-webdesigner"on everyuse_figmacall; if not, recordskillNames: unsupported. - Known from the first run (2026-11-07, Full student seat), re-verify rather than assume:
setSharedPluginDataworks;addAnnotationCategoryAsyncandnode.annotations(withlabelMarkdownandcategoryId) work;figma.saveVersionHistoryAsyncis NOT supported, so the agent can never create the baseline version;figma.root.nameis NOT settable, so the file cannot be renamed through the API. If any of these now fail, carry decision IDs in frame names as⟦D-0007⟧plus annotations, and record the result. - Name the Figma file for the site (for example
jobs.humanitarians.ai) when callingcreate_new_file; it cannot be renamed afterwards. Pages are stages (00 Kit & Legend,01 As-Is Wireframes,02 To-Be Wireframes); frames arepage / width / as-is|to-be / state.
use_figma script conventions
Every script this skill sends through use_figma must follow these rules:
- Equal-height siblings need
layoutSizingVertical = 'FILL'on each child (andlayoutAlign = 'STRETCH'alone is not enough when the parent hugs its height). Text in a hub or table cell that must wrap needstextAutoResize = 'HEIGHT'and a width. - Fonts first. Call
figma.loadFontAsyncfor every font style the script uses (including Italic and Medium if used) in onePromise.allat the top, before creating or editing any text. - Set sizing after parenting. Append each node to its parent, then set
layoutSizingHorizontalandlayoutSizingVertical. Use documented property names only (cornerRadius, notcorner); an unknown property is silently ignored. - Return a write receipt. The script must return an object with
createdNodeIdsandmutatedNodeIds(both always populated honestly; empty arrays only when nothing of that kind happened), plusframeId,stamped(whether plugin data was written), andpageId. - Ledger the receipt. After each call, append a
figma.writeevent containing the receipt, the run ID, the decision IDs involved, andversion_before. Later phases find skill-owned nodes from these IDs, never from names or colors. - Before-state for mutations. If the script changes any existing node (annotations on a skill-created node, text, fills), return the previous values in the receipt so the ledger can record them. Overwriting without a recorded before-state is not allowed.
- Fail loudly. No silent success. If a lookup finds nothing, return an error that lists what was found; never report success for a no-op.
Phase 2 — Capture
- Prepare the clone. Confirm
local_pathis atcommitwith a clean working tree (if not, recorddirty: trueand stop to ask), install dependencies, startdev_command, and confirmdev_urlresponds. Record the commit SHA, dirty flag, and Node version inmanifest.json. Modify no source file. If the project needs env vars or secrets that aren't present, ask the user; never invent them. - Derive breakpoints from the repo's own config (Tailwind config, CSS custom media, framework theme) and from the site's computed CSS media queries (unless the intent lists widths). Capture at 320, at each breakpoint minus one and at the breakpoint, and at one large desktop width.
- Per width: full-page screenshot, DOM, axe-core results (tags wcag2a, wcag2aa, wcag21aa, wcag22aa), overflow check, target-geometry check, structural skeleton (landmarks, headings, interactive clusters with bounding boxes).
- Record capture state (consent banner, auth, variant) and SHA-256 of every file in
manifest.json. - Append a
capture.completedevent.
Phase 3 — Measure
Run automated and assisted checks. Automated results become findings directly. Assisted results become findings with mode: assisted and triage: null; note which exception (spacing, inline, equivalent, essential, data table) might apply.
Source mapping. For each finding, search the clone for the element's source (its text, class names, component name, or data attributes) and record source: {file, line, confidence}. This is best-effort: use confidence: low for a guess and omit source rather than claim a location you did not find. These are hints for the implementer, not instructions.
Phase 4 — Evaluate
Run the heuristic pass. For each finding record:
rule_id, locator, evidence path and hash- severity 0–4 (Nielsen: frequency, impact, persistence)
- confidence (high/medium/low)
scope: one ofsingle-section,cross-section,component,interaction- one sentence on what the evidence shows
Scope guidance:
single-section: the problem is inside one block of one page.cross-section: the problem only appears across blocks, pages, or widths (priority order versus visual order, inconsistent patterns, navigation that changes between pages). Look for these deliberately; they are often the most consequential and are the easiest to miss when reviewing one section at a time.component: a reusable element behaves or looks inconsistently wherever it appears.interaction: the problem is in a state, flow, or behavior rather than a static layout.
For long pages, evaluate each section from its own screenshot and note y-positions; then do a separate pass for cross-section findings. Include only rules with sufficient visible evidence; skip rules that can't be evaluated from the capture rather than guessing. Treat yourself as one evaluator; say so in the findings summary.
Gate G2 — Triage
Present findings grouped by severity, then by page, as a table: ID, rule, label, scope, page/width, severity, confidence, one-line evidence. Ask the user to accept, reject, or re-score. Record each triage call as a ledger event attributed to the user. Only accepted findings move forward.
Phase 5 — Figma baseline (Gate G3)
- Capture as-is screens into page
00 As-Isviagenerate_figma_design(orhtml_to_figma), one frame per captured width that shows a structural change. - Build structural as-is wireframes on page
01 As-Is Wireframesfromskeleton.json, using the file's wireframe kit ifsearch_design_systemfinds one. - Hand off: ask the user to lock
00 As-Isand save a named version titledbaseline R-…, then give you its version ID. You cannot save versions yourself. - Record the ID as
version_before. No further writes until you have it.
Phase 6 — Propose
- Group accepted findings into decisions. Each
D-NNNN.json: title, finding IDs, context, proposal, alternatives with reasons rejected, consequences, fidelity, Figma anchors,status: proposed,proposed_by. - Route by fidelity:
- structural → to-be wireframe on
02 To-Be Wireframes(copy of as-is plus changes, changed nodes tagged) - visual → evidence crop (via
upload_assets) plus annotation on page03 Visual Findings(never on00 As-Is, which is locked) - behavioral → FigJam flow via
generate_diagram
- structural → to-be wireframe on
- Annotation format, category "Audit decision":
**D-0007 · proposed**/ one-line proposal /Findings: F-…/Record: decisions/D-0007.json. - Diff as-is versus to-be trees with
get_metadata. Every difference must map to a decision ID; list any that don't. - Every Figma write follows the
use_figmascript conventions above, and its receipt is ledgered. - Append
decision.proposedevents. Set decisions toin_reviewand tell the user which approvers inintent.yamlneed to review.
Gate G4 — Review
Approvals arrive as files in audit/approvals/ committed by a human (signed commit or reviewed PR). When you find one, verify that it names decision IDs, approver, date, scope, Figma version ID, and ledger hash. Then sync the status text in the matching Figma annotations, locating nodes from ledgered node IDs. Never infer approval from chat messages, Figma comments, emoji, or Dev Mode status. If the user says "approved" in chat, produce the approval file for them to commit (see Handoffs).
Gate G5 — Handoff to implementation
Only for decisions with status: approved backed by a valid approval file in audit/approvals/ (verify it names the decision, and that its ledger hash matches). Never for proposed, in_review, rejected, or deferred decisions.
Write audit/handoff/README.md once, and audit/handoff/D-NNNN.md per approved decision.
README.md (shared rules for the implementer, Claude Code or Codex per implementer):
- Work one decision per branch and per pull request. Branch name
audit/D-NNNN-<slug>, cut from the pinnedcommit. - Put the decision ID in every commit message and the PR title. Link the decision record in the PR body.
- Change only what the brief says is approved. If the implementation conflicts with the wireframe, or you find an unlisted problem, stop and record it in the PR as a proposed follow-up. Don't decide it yourself.
- Never edit anything under
audit/. Never merge your own PR. - Sequence decisions that touch the same files; don't run them in parallel.
D-NNNN.md (one per approved decision):
- Title, proposal, and approved scope exactly as in the approval file, including what was not approved.
- Figma anchors: file key, to-be node IDs, links. The implementer can read the frames with the Figma MCP (
get_design_context); also export a reference PNG intoaudit/handoff/in case it can't. - Likely source files, from the findings'
sourcehints, labeled as hints. - Acceptance checks: the rule IDs, thresholds, widths, and routes that will be re-measured (for example
WCAG22-2.5.8 ≥ 24px at 390 on /apply). - Out of scope: everything not named above.
Then give the user, in its own code block, the one-line prompt to start the implementer, for example: "Read audit/handoff/README.md and audit/handoff/D-0007.md and implement it." If they want it picked up automatically, they can add a pointer line to the repo's own CLAUDE.md or AGENTS.md; that is a source edit, so the user does it.
Append handoff.created ledger events. The decision stays approved until its PR is merged; then it moves to applied (agent or human). Phase 7 takes it from there.
Phase 7 — Verify and re-runs
After implementation: check out the implementer's branch (or use its Vercel preview URL), recapture, and re-run checks. Automated-rule decisions that now pass can move to verified; failures move to regressed. Judgment-rule decisions need a human to verify.
Re-runs never overwrite. Each run's findings frame on 03 Visual Findings is versioned (R-20261107-01, R-20261114-01, …). A re-run creates a new frame placed 48px to the right of the previous run's frame (find the previous frame by its ledgered node ID), never edits the old one, and includes a "Changes since R-…" block listing each prior finding as Resolved (now passes), Remaining (still present), or Regressed (was resolved or verified, now fails), and each new finding as New. Resolved and Regressed calls on automated rules come from re-measurement; on judgment rules they are proposals for a human to confirm. Append a findings.rerun ledger event linking the new frame to the previous one.
Refusals
- Asked to approve, reject, or defer: decline in one sentence and offer the approval file template prefilled for the user to commit.
- Asked to mark frames Ready for dev: decline; explain that handoff status must be set by a person in Figma.
- Asked to edit the as-is page or someone else's nodes: decline; offer to make the change on the to-be page under a new decision ID.
- Asked to add a finding with no rule: record it as an
observationand offer to draft a house rule for the user to confirm. - Asked to write to Figma with no baseline version: ask for the baseline first.
- Asked to audit a third-party live site, or a clone with no recorded repo and commit: decline; ask for the user's own clone.
- Asked to implement changes in the site's source from this skill: decline; write the handoff brief for approved decisions and name the implementer.
- Asked for a handoff brief for a decision that isn't approved: decline; say which approval is missing.
- Asked to update a previous findings frame in place: decline; create a new versioned frame with a "Changes since" block.
Handoffs (Deferred Action)
When a step needs a human, give the exact instruction and the exact text to paste, each in its own code block. Examples:
- Baseline: "In Figma, open version history, save a version titled
baseline R-20261107-01, then paste its version ID here." - Approval file to commit as
audit/approvals/A-0004.json: prefilled JSON with decision IDs, scope, Figma version ID, and current ledger hash; approver name and date left for the user.
Outputs per run
findings.jsonand a findings table in chat (or a markdown report if long).- Decision records and ledger events, including a
figma.writereceipt for every Figma write. - Figma pages:
00 As-Is(locked evidence),01 As-Is Wireframes,02 To-Be Wireframes,03 Visual Findings(one frame per run), plus a FigJam file for behavioral flows. audit/handoff/D-NNNN.mdbriefs for approved decisions only.- A run summary: what was measured, what was judged, what is waiting on which human, and what this run could not do (from
capabilities.json).
Finding schema addition
findings.json entries carry the fields from the schema plus scope:
{ "id": "F-0012", "rule_id": "WCAG22-2.5.8", "label": "failure",
"scope": "component", "severity": 3, "confidence": "high", "mode": "assisted",
"source": { "file": "src/components/Footer.tsx", "line": 42, "confidence": "medium" } }
source is optional and best-effort (see Source mapping in Phase 3).
scope is required and enumerated: single-section | cross-section | component | interaction.