Skip to content
Back to skills

Penpot Anti Slop

ASecurity

Opt-in layer that keeps Penpot designs from looking generically AI-generated: a direction step before building, a scored 0–100 slop check of a screen or deck (tree probes + visual review), and reviewed fixes. Triggers: 'avoid the AI look', 'does this look AI-generated', 'anti-slop check', 'make it feel less generic', 'evitar estética IA', '¿parece hecho por IA?'.

  • 8 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
developmentrustgoapi

Works with

  • claude code
  • api
  • mcp

Security analysis

A100/100

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

Scanned November 7, 2026

npx -y skills add huytran1120/vietnam-agentic-skills --skill penpot-anti-slop --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Penpot Anti Slop?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Penpot Anti Slop
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/huytran1120-penpot-anti-slop/badge)](https://www.skillsdirectory.com/skills/huytran1120-penpot-anti-slop)

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

Download with Pro
SKILL.md
---
name: penpot-anti-slop
description: "Opt-in layer that keeps Penpot designs from looking generically AI-generated: a direction step before building, a scored 0–100 slop check of a screen or deck (tree probes + visual review), and reviewed fixes. Triggers: 'avoid the AI look', 'does this look AI-generated', 'anti-slop check', 'make it feel less generic', 'evitar estética IA', '¿parece hecho por IA?'."
disable-model-invocation: false
version: 0.1.0
audiences: [product-designer, design-system]
mode-default: suggest
requires:
  - shared/penpot-mcp-tool-reference.md
  - shared/plugin-api-gotchas.md
  - shared/anti-slop.md
  - shared/anti-slop/lists.json
  - shared/design-quality.md
  - shared/visual-self-review.md
  - shared/visual-effects.md
  - shared/naming-conventions.md
  - shared/state-management.md
  - shared/modes-and-policies.md
  - shared/report-schemas/slop-report.schema.json
---

# penpot-anti-slop — design past the generic-AI look

## 1. Title + How it works
`penpot-anti-slop` is the **opt-in strict layer** over `shared/design-quality.md`: it knows the
patterns that make output read as AI-generated (`shared/anti-slop.md` §1), steers a build away from
them before it starts, and scores a finished screen or deck from 0 (genuine) to 100 (AI default).
Every read goes through `execute_code`; validate visually with `export_shape`; read structure with
`penpotUtils.shapeStructure` (full tool surface: `shared/penpot-mcp-tool-reference.md`). It runs in
three modes:

| Mode | Invoked by | Does |
|---|---|---|
| **direction** | Phase 0 of `penpot-build-screen` / `penpot-build-deck` when `prefs.antiSlop === "on"` | names the defaults for this brief, replaces each with a brief-driven reason, proposes a typeface, lists the tells to refuse |
| **diagnose** | the `slop` step of `brief-to-screen` / `brief-to-deck`, or a user asking "does this look AI-made?" | deterministic tree probes → describe-then-judge visual review → slop score + report |
| **treat** | the user, after a failing diagnosis | proposes the top fixes as concrete edits and applies them **with review**, one per call |

## 2. The One Rule That Matters Most
**Measure before judging, and never trade one default for another.** Anything measurable on the
shape tree is probed (p = 1) and outranks the visual opinion; the visual pass describes before it
judges. And a "fix" that lands on the fashionable anti-AI look (cream + italic serif accent + sage,
or one house style reused everywhere) is still a default — every replacement needs a reason from
the brief.

## 3. Penpot MCP Tool Reference
Full surface: `shared/penpot-mcp-tool-reference.md`. Domain calls: `penpotUtils.findShapes` /
`analyzeDescendants` for the probe walk; `penpot.library.local.tokens.sets` to tell inherited from
introduced fonts; `penpot.fonts.all` for typeface candidates; `penpot.currentFile.get/setSharedPluginData`
for the opt-in preference; `export_shape(<boardId>)` for the review pass.

## 4. Plugin API Essentials
Gotcha numbers refer to `shared/plugin-api-gotchas.md`.
- Text `fontFamily`, `fontStyle`, `align` return `"mixed"` when ranges differ — that is the
  serif-accent signal; `fontSize` is a **string** (`parseFloat` it).
- Effects: `shape.blur` / `board.backgroundBlur` are `{ value, hidden }`; shadows carry `color.color`;
  gradients live in `fills[].fillColorGradient.stops[]` (`{ color, opacity, offset }`).
- Strokes are uniform per shape, so a "one-edge accent" is always a thin child rect.
- **#1 immutable style arrays** — treat-mode edits replace whole `fills`/`shadows` arrays.
- **#2 async token application** — after a treat edit that binds a token, verify in the next call.
- **#11 default white board fill** — a treat edit that removes a gradient lands on a *token-bound*
  surface or `[]`, never on a literal white.
- **#13b exact font match** — `penpot.fonts.all.find(f => f.name === "…")`.
- Verify unfamiliar signatures with `penpot_api_info` first.

## 5. Token-Aware Brief Contract
- **Context** — the product, audience and brief behind the design; whether strict mode was opted in
  (`prefs.antiSlop`).
- **Objective** — single: "steer the build away from the generic-AI look" (direction), "score how
  generic board X reads" (diagnose), or "apply fix N to board X" (treat).
- **Inputs** — the board / deck page id, the brief, active tokens + type tokens, the ledger's prior
  `antiSlop.direction` picks.
- **Constraints** — read-only in direction and diagnose; system tokens and explicit user requests
  outrank the taxonomy (`shared/anti-slop.md` §5); never invent replacement evidence; fixes are
  Apply-with-review.
- **Acceptance Criteria** — every counted finding cites a probe measurement or a Pass A description
  line; score computed by `scripts/slopScore.js`; **pass = score ≤ 35** (target ≤ 15); report JSON
  validates against `shared/report-schemas/slop-report.schema.json`.

Act as a **senior art director who can tell a decision from a default** — and says which is which.

## 6. Mandatory Workflow

> **Visual self-review (mandatory):** treat-mode edits follow `shared/visual-self-review.md` —
> export, look, fix (max 2 iterations), then present.

**Phase 0 — Opt-in + discovery (read-only).** `high_level_overview` (once per session); run
`scripts/antiSlopPrefs.js` (`ACTION="read"`). When invoked from a build skill and the result is
`unset`, ask the §0 question of `shared/anti-slop.md` in the user's language, then store the answer
(`ACTION="set"`). A user who directly asks for a diagnosis has opted in for that diagnosis — do not
ask. `penpotUtils.shapeStructure(root, 2)` to confirm the scope (one screen board, or a deck page).

**Direction mode (from a build skill's Phase 0).** Follow `references/02-direction-playbook.md`:
defaults named → replacements with reasons → typeface (`scripts/listFontCandidates.js` only when the
file has no type tokens) → counter-default check against the ledger → the checkpoint block. Hand
the "will refuse" keys back to the build skill as Constraints. ✋ The build skill's Phase 0
checkpoint shows this block; no separate checkpoint.

**Diagnose Phase 1 — Probes.** One `execute_code` with `scripts/scanSlopProbes.js` (`ROOT_ID`,
`MODE`). Read `references/01-penpot-mapping.md` for what each key measures. Mark findings the user
explicitly asked for as `accepted`; brief-requested dark as `accepted` for `dark-neon-default`.

**Diagnose Phase 2 — Visual review.** `export_shape` the board (decks: batches of ≤ 4 slides) and
run `references/03-exam-protocol.md`: Pass A (describe, written out) → Pass B (judge review keys) →
template judgement → authenticity signals. Decks: read `references/04-deck-tells.md`.

**Diagnose Phase 3 — Score + report.** `scripts/slopScore.js` over `storage.slop`. Emit the Markdown
report (score, band, counted tells with evidence, unsure tells, inherited/accepted, signals, top 3
fixes) and the JSON per `shared/report-schemas/slop-report.schema.json`; mirror to the ledger.
✋ Checkpoint: present the export + report; offer treat for the top fixes.

**Treat (opt-in per fix).** One fix per `execute_code`, each a concrete edit naming the token or
pattern it moves to (e.g. "replace the hero gradient with `color.bg.default`; left-align headline +
subheading; remove `pill-badge`"). Export → look → ✋ approve each. After the last approved fix,
re-run Phase 1–3 and report the before/after **score and points** — the score saturates at 15 points, so on a
very generic design the first fixes move the points long before they move the score (live run: 29.7 → 24.7
points, 100 → 94). When the file has no color tokens, a flattening fix lands on a literal recorded as a ledger
exception plus a token proposal.

## 7. Critical Rules
1. Never run strict mode without the user's opt-in (stored `prefs.antiSlop === "on"`, or a direct
   diagnose request).
2. Ask the opt-in question at most once per file; read the stored preference first.
3. Probes before review; a probe result for a key replaces any review answer for it.
4. Pass A (describe) is written before Pass B (judge); Pass B cites Pass A.
5. Only `present` findings count; `unsure`, `inherited` and `accepted` are reported, never counted.
6. System tokens and explicit user requests beat the taxonomy — report `inherited` / `accepted`.
7. Never replace removed proof (stats, logos, quotes) with invented proof.
8. Every fix is Apply-with-review, one per call — none is in the safe set.
9. Record the direction picks; consecutive runs differ on ≥ 1 of layout, accent role, typeface.
10. Report the lists version used; flag lists older than 6 months as a governance note.

## 8. Domain Architecture
```
storage.slop = {
  listsVersion, scopeId,
  findings: [{ key, weight, p, source: "probe"|"review", status?, region, shapeIds, evidence, fix? }],
  template: { value: 0..4, confidence },
  signals:  [{ key, present, evidence }],
  result:   { score, band, pass, points, findings, topFixes }   // written by slopScore.js
}
ledger.antiSlop = { pref: "on"|"off", direction: { defaults, picks, typefaceProposal, refused }, reports: [ ... ] }
```
Score formula, bands and the gate: `shared/anti-slop.md` §3. Branch field read by workflows:
`slop.score` (and `slop.pass`).

## 9. Modes & Policies
Default **suggest**. Direction and diagnose are read-only. Treat is **Apply-with-review** for every
edit (geometry, color, content, fonts) — none is in the safe set (`shared/modes-and-policies.md`,
`policies/modes.json`). New type tokens are proposed, never created here (hand off to
`penpot-foundations`).

## 10. State Management
Ledger keys under `RUN_ID` (`shared/state-management.md`): `antiSlop.pref`, `antiSlop.direction`,
`antiSlop.reports[]` (score, band, findings ids, listsVersion, scopeId). The preference itself lives
file-wide in plugin data `penpot-ai` → `prefs.antiSlop`, independent of any run. Resume: re-read the
ledger, re-run the probe (cheap, idempotent) rather than trusting cached findings.

## 11. User Checkpoints
| After phase | Artifacts shown | What we ask |
|-------------|-----------------|-------------|
| 0 (from a build skill, pref unset) | the opt-in question | Avoid the AI look? Yes / No / Explain |
| direction | defaults → picks → typeface proposal → refused keys (inside the build skill's Phase 0) | Approve direction? |
| diagnose 3 | export + slop report (score, band, evidence, top fixes) | Treat the top fixes? Which ones? |
| each treat fix | export of the edited board | Approve this fix? |
| after treat | before/after score | Done, or another round? |

## 12. Naming Conventions
`shared/naming-conventions.md`. Region detection relies on semantic board names (`hero`, `features`,
`pricing`, `footer`…); suggest `penpot-rename-layers` when most regions come back as `content`.
Finding ids: `slop-<key>-NN`, stable across iterations.

## 13. Anti-Rationalization Table
| Excuse | Why it's wrong | Countermeasure (halt) |
|--------|----------------|------------------------|
| "The user would obviously want this on." | Strict mode is a style decision that belongs to the user. | Read `prefs.antiSlop`; if unset, ask the §0 question. |
| "My export looks fine, I'll skip the probe." | Self-review is lenient; the tree doesn't lie. | Run `scanSlopProbes.js` first, every diagnosis. |
| "I'll judge straight from the image." | Judging without describing invites motivated answers. | Write Pass A, then judge each key against its criterion. |
| "0.6 is close enough to count." | Unsure answers are noise in the score. | Only p ≥ 0.65 counts; report the rest as unsure. |
| "Swap Inter for something nicer, it's in the taxonomy." | The system outranks the mode. | Report `inherited`; propose a type token via `penpot-foundations`. |
| "Delete the fake stats and put a real-sounding number." | Fabricated evidence. | Labeled placeholder or a pattern that needs no number. |
| "Cream + italic serif is the antidote." | It is the newest default. | `shared/anti-slop.md` §4.4 — name it, replace it with a brief-driven pick. |
| "Apply all three fixes in one call." | One-shot edits are unreviewable. | One fix per call, export, approve. |

## 14. Helper Code Snippets
```js
// Phase 0 — read the stored opt-in (full helper: scripts/antiSlopPrefs.js)
const v = penpot.currentFile.getSharedPluginData("penpot-ai", "prefs.antiSlop");
return { antiSlop: v === "on" || v === "off" ? v : "unset" };
```
```js
// Phase 2 — append a review answer, then run scripts/slopScore.js in the same or the next call
storage.slop.findings.push({ key: "vague-headline", weight: 2, p: 0.8, source: "review", region: "hero:hero",
  evidence: "Pass A: headline reads 'Build the future of work' — no product, no audience" });
storage.slop.template = { value: 3, confidence: 0.7 };
storage.slop.signals.push({ key: "product-visible", present: false, evidence: "hero shows a 3D sphere, no UI" });
return { findings: storage.slop.findings.length };
```
```js
// Treat — one fix: flatten a gradient hero onto a bound surface token (gotchas #1, #11)
const hero = penpotUtils.findShapeById("HERO_ID_HERE");
hero.fills = [];
const bg = penpotUtils.findTokenByName("color.bg.default");
if (bg) hero.applyToken(bg, ["fill"]);
return { heroId: hero.id, bound: !!bg };   // verify the binding + export in the next call
```

## 15. Reference Resources
- `penpot_api_info('Text', 'fontFamily' | 'fontStyle' | 'align')`, `penpot_api_info('Blur')`,
  `penpot_api_info('Shadow')`, `penpot_api_info('Gradient')`, `penpot_api_info('FontsContext')`,
  `penpot_api_info('File', 'setSharedPluginData')`.

## 16. Supporting Files
**references/**: `01-penpot-mapping.md` (diagnose Phase 1), `02-direction-playbook.md` (direction),
`03-exam-protocol.md` (diagnose Phase 2), `04-deck-tells.md` (decks, both modes).
**scripts/**: `antiSlopPrefs.js` (opt-in read/store), `scanSlopProbes.js` (deterministic probes),
`slopScore.js` (pure scoring, unit-tested by `scripts/dev/test-anti-slop.mjs`),
`listFontCandidates.js` (typeface shortlist outside both lists).
**shared/**: `anti-slop.md` (taxonomy, signals, score, direction, precedence), `anti-slop/lists.json`
(fonts, buzzwords EN/ES, placeholders, patterns — versioned).

**Doctrine paths.** `shared/…` and `policies/…` resolve inside this bundle in native installs (vendored by the installer); in a Claude Code plugin install they live at the plugin root — `${CLAUDE_PLUGIN_ROOT}/shared/…`, two directories up from this file.

Files in this skill

  • SKILL.md14.2 KB
  • references/01-penpot-mapping.md5.9 KB
  • references/02-direction-playbook.md4.5 KB
  • references/03-exam-protocol.md3.1 KB
  • references/04-deck-tells.md2.4 KB
  • scripts/antiSlopPrefs.js1.8 KB
  • scripts/listFontCandidates.js2.4 KB
  • scripts/scanSlopProbes.js20 KB
  • scripts/slopScore.js6.7 KB

Attribution

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

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.