css-token-sweep — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited css-token-sweep (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Find every var(--name) in a project where --name is not defined in any :root { ... } block. Surface tokens defined in default :root but quietly inherited (not overridden) by theme variants. Optionally flag hardcoded colors that duplicate an existing token's value.
This is the missing lint pass that most design-system codebases never add. Without it, a typo like var(--ink-2) instead of var(--ink-soft) renders text in the unset color (often invisible against a white button) and ships to production unnoticed.
The skill bundles scripts/sweep.py — a zero-dependency Python scanner. Most of the work is mechanical, so the script does it deterministically; the skill body just orchestrates running it, interpreting the output, and proposing fixes.
============================================================ === PRE-FLIGHT === ============================================================
Before running, verify:
obvious project root in the current working directory.
python3 is on PATH (python3 --version)..css, .html, .htm, .jsx,.tsx, .vue, or .svelte file. If it doesn't, the scan has nothing to do — say so and exit.
Recovery:
============================================================ === PHASE 1: RUN THE SCANNER === ============================================================
Invoke the bundled scanner with the user's path (or .):
python3 ~/.claude/skills/css-token-sweep/scripts/sweep.py <PATH>Useful flags:
| Flag | When to use |
|---|---|
--strict | Wire into CI — exit 1 if any errors. |
--skip-warnings | First-pass triage; surface only the invisible-text errors. |
--ignore=--dx,--dy,--r | Suppress CSS custom properties that are set at runtime via JS (element.style.setProperty('--dx', '12px')). The static scanner cannot see runtime sets, so animation-param vars are false positives unless added here. |
--json | Machine-readable output for chaining into other tools or for the optional runtime-contrast follow-up below. |
The scanner reports three findings:
canonical invisible-text bug. A var(--ink-2) reference where --ink-2 is defined nowhere falls back to the unset initial value; for color that is the unset paint color, which depending on context may render the text the same color as the background.
:root but not overridden in a[data-theme="..."] variant.** Heuristic: only color-like tokens (anything whose name contains ink, surface, paper, bg, border, accent, text, etc.) are surfaced, because shape and shadow tokens often legitimately stay shared across themes. The warning is a prompt to confirm the default value still reads correctly under each variant, not an automatic fix.
These are stylistic; the code works fine, but switching themes won't move them. Suggest replacing with var(--matching-token).
VALIDATION: scanner exits cleanly and prints either "Clean." or a report with concrete file:line locations. FALLBACK: if the scanner errors on a specific file (permissions, weird encoding), note the file in the report and continue with the rest.
============================================================ === PHASE 2: INTERPRET AND FIX === ============================================================
For each error, the report includes a "Did you mean?" suggestion based on edit distance to the closest defined token. Take that suggestion seriously — in nine cases out of ten it is exactly the right fix, because the bug pattern is almost always a typo / wrong tier name (--ink-2 vs --ink-soft, --bg-muted vs --surface-muted, etc.).
Workflow:
var(--…) with the suggested token, or — if thesuggestion is wrong — define the new token in :root (and in each theme variant).
For theme-variant warnings, do not blindly add overrides. Read each warning, look at the component that uses the token, and decide whether the default value actually reads correctly in that variant. Many shadow / radius / motion tokens legitimately stay the same across themes — only color tokens need variant-specific overrides.
For hardcoded-color warnings, replace in place. This usually takes one sed pass per color.
VALIDATION: a second scanner run reports 0 errors. FALLBACK: if you cannot resolve an error (e.g., the token genuinely does not exist anywhere in any related file), surface it to the user explicitly with a recommendation — either define the token or remove the dead reference.
============================================================ === PHASE 3 (OPTIONAL): RUNTIME CONTRAST SWEEP === ============================================================
Static token-existence checks do not catch a token that exists but resolves to an unreadable color under a given theme. If the project has multiple data-theme variants and the user wants belt-and-suspenders coverage, do a runtime sweep with Playwright:
// Pseudocode — adapt to the project's entry point.
for (const theme of ["light", "dark", "morning", "dusk"]) {
await page.evaluate((t) => {
document.documentElement.dataset.theme = t;
}, theme);
const lowContrast = await page.evaluate(() => {
const out = [];
const interactive = document.querySelectorAll(
'button, a, input, [role="button"]',
);
for (const el of interactive) {
const cs = getComputedStyle(el);
const ratio = contrastRatio(cs.color, cs.backgroundColor);
if (ratio < 3.0)
out.push({ tag: el.tagName, text: el.textContent.slice(0, 40), ratio });
}
return out;
});
console.log(theme, lowContrast);
}Only do this if (a) Playwright is already available in the environment and (b) the project actually has multiple themes. Otherwise it is more overhead than the static scan.
VALIDATION: each theme returns 0 low-contrast interactive elements, or the user has reviewed and accepted any remaining hits. FALLBACK: if Playwright is not available, document the gap in the final report and stop. Do not try to install Playwright unprompted — the static check already catches the highest-impact bugs.
============================================================ === SELF-REVIEW === ============================================================
Score the output (1–5):
explicitly noted as needs-human-judgment)?
--ignore? Did you skip false-positive warnings rather than apply noisy fixes?
without reading the underlying CSS line by line?
If any score < 4:
the case to the user with the two or three candidate tokens rather than silently picking one.
============================================================ === LEARNINGS CAPTURE === ============================================================
After each run, append one entry to ~/.claude/skills/css-token-sweep/LEARNINGS.md:
## <YYYY-MM-DD> — <project name + scan scope>
- **What worked:** <specific suggestion that was correct, or a flag
combo that produced an actionable report>
- **What was awkward:** <false positive that the scanner could have
avoided, a missed bug, or a slow step>
- **Suggested patch:** <one concrete improvement — "auto-detect
inline-style runtime vars and skip them", "add `--include` glob
to scan only one subdirectory", etc.>
- **Verdict:** [Smooth / Minor friction / Major friction]var(--ink-2) is bad, fixthe reference or genuinely add the token — do not silently leave the reference and pretend the bug went away.
var(--name, fallback) everywhere as a workaround. Fallbackshide real bugs and create theme inconsistency. Use them only for intentionally optional vars (e.g., user-customizable accents) and document the intent.
--ignore instead offixing the underlying issue. --ignore is for runtime-set vars, not for silencing bugs.
are intentional. Read each one and decide.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.