When to run an audit
Audit triggers a different mindset than authoring. The goal is not to write what should be there — it's to find what shouldn't be, prove it from the conventions, and propose minimal changes.
Three common audit modes:
- Pre-merge audit — code arrives via PR; check it against the system before approving.
- Migration audit — older code uses Malevich loosely; produce a list of upgrades to make it strict.
- Vendor audit — code arrives from an external source (designer, AI generation, snippet site); decide whether it's worth adopting.
The procedure is the same for all three; the disposition differs.
Read first
Before touching the code:
llm-wiki/glossary.md— the CSS-conventions section: BEM with short modifiers, thedata-{category}modifier attributes, and the retirement of both them-custom-element prefix (ADR-0004) and thet-tweak prefix (ADR-0007).llm-wiki/architecture.md— token tiers and composition direction.packages/lint/— the active lint rules, with passing and failing examples.
You should be able to recite the lint rules from memory:
malevich/no-raw-values— hex, rgb, hsl, named colors, px, em, and raw shadows outside the token source files are bugs. (0, percentages, and a single hairline1pxpaired with avar()color are allowed.)malevich/no-foundation-direct-reference— a component, application, or modifier that reads a tier-1 foundation directly (var(--palette-accent),var(--scale-spacing-4)) is a bug. Components consume tier-2 (semantic) or tier-3 (component-specific) only.malevich/bem-short-modifier— variant modifiers are leading-dash and must be paired with a block or element;.-accentstandalone is a bug,.button--accent(double-dash) is a bug, and a legacy.t-glow-hovertweak class is flagged (deprecated, migrate to adata-modifier).malevich/modifier-applicability-check— adata-{category}modifier whose category or value is not in the component's row ofpackages/components/modifiers/applicability.jsonis a bug (.button[data-effect="grain"],.tooltip[data-background="image"]).malevich/custom-element-prefix— any custom-element selector (a tag with a hyphen) must use them-prefix. In practice Malevich ships no custom elements (ADR-0004), so any custom element you find is itself a delivery-model violation; the rule catches accidental drift.
The audit pass — order matters
Run the passes in this order. Each builds on the previous; reversing them produces noisy reports.
Pass 1 — Tokens
Find every literal value: hex codes, rgb(), hsl(), named colors,
pixel values that aren't 0 or a hairline 1px, em/rem that aren't
1em, and raw durations or shadows. For each:
- Inside the foundations source (
foundations.json) ortokens.css? → allowed (that's where primitives live). - A direct tier-1 reference (
var(--palette-*),var(--scale-*)) from a component or app? → violation (no-foundation-direct-reference); propose the semantic token that should sit in front of it. - Anything else → violation (
no-raw-values); propose a semantic token replacement.
If a file uses color: #333 five times, that's one violation — the
absence of an appropriate semantic token at the call site, repeated five
times. Group them.
Pass 2 — BEM and class structure
Read every class selector. For each:
- Block name is kebab-case, no underscores in the block segment.
- Element separator is
__(.card__title). - Modifier is short-form, leading dash, paired with a block or
element:
.button.-accent,.tab__item.-active. - A standalone modifier (
.-accent), double-dash (.button--accent), or a legacy tweak class (.t-glow-hover) → violation. For a tweak class, the fix is the equivalentdata-{category}modifier.
Pass 3 — Component identity
For each visible UI control, identify the Malevich component it corresponds to. Check:
- Canonical class (
.button,.card) or a hand-rolled equivalent (.btn,.my-card)? If hand-rolled, is the styling actually different or just a renamed duplicate? The latter is a strong refactor candidate. - A custom element (
<m-button>,<app-card>) or a<div role="button">standing in for a real control? Both are violations — Malevich is standard HTML elements with classes. - The wrong layer? A
<div class="card">styled as a full-width region is a misclassification; promote it to a section.
This pass is the highest-impact and the slowest. Don't skip it.
Pass 4 — Composition direction
Verify no component sits inside a higher layer than it should:
- An element wrapping a block, a block wrapping a section, or an overlay nested inside another overlay → violation.
- Watch for structural inversion even when class names look fine — a section nested inside a card, for example.
Pass 5 — Accessibility
For each interactive element:
- Real
<button>,<a>,<input>,<dialog>used — not a<div>with a role? - Focus visible? Inspect for
outline: nonewith no replacement. - Keyboard reachable, natural tab order?
- ARIA correct?
aria-pressedon a button that never toggles is a bug. A hand-rolled modal withoutaria-modal+ focus trap is incomplete — prefer native<dialog>+showModal().
Accessibility violations are not "nice to have." Treat them with the same severity as token violations.
Pass 6 — Modifiers
If the code uses data-{category} modifiers (data-surface,
data-effect, data-behavior, …):
- Is the category available on the host component, per its row in
packages/components/modifiers/applicability.json? - Is the value in the allowed list for that category?
- Are two values of the same category stacked on one element (illegal —
a component has one
surface)? - Does a
behaviormodifier rely on the runtime that the page never initializes?
Any t--prefixed tweak class or data-tweak attribute is a v0.1.0
artifact — flag it and propose the data-{category} equivalent.
Reporting format
Produce one report, three sections.
Severity
- blocker — would fail lint, breaks the system contract, or introduces an accessibility regression.
- major — works at runtime but violates the conventions; will drift further without correction.
- minor — stylistic, doesn't affect correctness, optional.
Report shape
For each finding:
[severity] [pass] [file:line] short title
what: one sentence of what's wrong
why: one sentence of why it's wrong (cite the rule)
fix: the proposed minimal change, as code
Example:
[major] [tokens] index.css:42 raw color in card body
what: `color: #444` appears in `.card__body`.
why: raw values are forbidden outside the token sources
(malevich/no-raw-values).
fix: `color: var(--color-ink-subtle);`
Summary
End with three counts (N blockers, N major, N minor) and a one-line disposition: "Approve after blockers fixed" / "Request changes" / "Reject — consider rewriting."
What audit is NOT
- It's not a rewrite. Don't rewrite the file in your head and present
the result. Find what's wrong; propose minimal changes. (For the
rewrite, hand off to
malevich-refactor.) - It's not a style review of the visual design. If the layout is questionable but technically conformant, note it separately as "design feedback."
- It's not a personality match. The audit cites rules; it doesn't argue taste.
When the audit finds nothing
A real possibility. Say so plainly: "Audit complete. No violations found. Approve." Don't manufacture findings to fill a quota.