Audit Brand Compliance
Rule-based brand-semantics audit. Detects (a) tokens used in forbidden contexts per their $extensions.harness.brand.forbidden_contexts metadata (BRAND-T001) and (b) UI copy containing phrases listed in DESIGN.md ## Brand Rules → voice.forbidden_phrases (BRAND-V001). The 4th composed verifier in harness check-design, alongside audit-component-anatomy, design-craft critique, and detect-design-drift.
When to Use
- After authoring or editing
DESIGN.md ## Brand Rules — verify the new constraints are enforceable on the existing codebase
- After adding
$extensions.harness.brand metadata to a token — discover existing call sites in forbidden contexts
- As part of
harness validate (fast-mode hook, gated by design.audit.brandCompliance.enabled)
- As the 4th composed verifier in
harness check-design (the unified design check)
- Before a PR with UI copy or token-usage changes lands
- NOT for tone-by-context rules (deferred to v1.x — requires component-state inference)
- NOT for reading-level or sentence-length rules (deferred to v1.x — ship with tone-context)
- NOT for asset-usage rules (deferred to v1.x — requires image-tag scanning)
- NOT for semantic-token-alias enforcement (overlaps with detect-design-drift T001 — design after both have shipped)
- NOT for brand-rule authoring (use
harness-design skill to draft DESIGN.md sections)
Capability Roles
- Defines (Service Definition): the shared
Verifier<F, Cat, Meta> interface (packages/cli/src/shared/verifier.ts); this was the 4th verifier whose addition triggered extraction of the interface.
- Provides (Provider): this skill — emits
AuditBrandOutput = Verifier<BrandFinding> (packages/cli/src/brand/index.ts).
- Consumes (Consumer):
harness-design-pipeline / harness check-design, which compose it generically via VerifierRegistry.
Process
Phase 1: LOAD — Parse the two input sources
Read project configuration. Check harness.config.json for:
design.strictness — strict / standard / permissive (default standard)
design.audit.brandCompliance.enabled — gate (default true)
design.audit.brandCompliance.rules.{tokenMisuse,voice} — per-rule toggles
Load design-system/DESIGN.md ## Brand Rules. The parser extracts:
voice.forbiddenPhrases: string[] — used by BRAND-V001 in v1
voice.constant, voice.readingLevel, voice.maxSentenceWords — parsed but unused in v1 (forward-compat)
toneByContext, assets, semanticTokenAliases — parsed but unused (v1.x)
- Returns
null when DESIGN.md absent or ## Brand Rules section missing → BRAND-V001 silently skips.
Load design-system/tokens.json $extensions.harness.brand. Walks the DTCG token tree capturing per-token role, approved_contexts, forbidden_contexts. Returns null when no token carries the extension → BRAND-T* silently skips.
Phase 2: SCAN — Apply the two rule families
BRAND-T001 — token misuse (regex-based). For each token whose forbidden_contexts is non-empty:
- Find every reference to the token's dotted path in source (recognizes three forms):
tokens.X.Y.Z (JS accessor)
var(--X-Y-Z) (CSS var, kebab-cased)
'X.Y.Z' / "X.Y.Z" (string literal)
- Inspect surrounding context (same line + nearest non-blank previous and next line) for the v1 context-vocabulary keywords:
cta, selection, focus, data-visualization, decorative, background, text, border, error, success, warning.
- If a forbidden context matches: emit BRAND-T001.
BRAND-V001 — forbidden phrases (TS Compiler API). For each .tsx/.jsx file:
- Walk the JSX tree.
- For each
JsxText node: case-insensitive substring scan for any forbiddenPhrase.
- For each
JsxAttribute whose initializer is a string literal: same scan.
- Deduplicate per
(file, line, phrase).
Phase 3: REPORT — Aggregate and surface
Severity from design.strictness (uses severityFor):
strict — all findings error
standard — BRAND-T001 error (declared violation), BRAND-V001 warn (copy nuance)
permissive — all findings info
Aggregate bySeverity and byCode into the standard Verifier shape: { findings, summary, catalog, meta }.
Persist findings to the graph (when composed by check-design). check-design routes brand findings through DesignConstraintAdapter.recordFindings() alongside anatomy / craft / drift. v1 uses the shared VIOLATES_design edge; v1.x may add a brand-specific edge.
Harness Integration
harness validate — Fast-mode hook gated by design.audit.brandCompliance.enabled. Degrades gracefully on failure (single warning; other checks continue).
harness check-design — Composes brand as the 4th verifier alongside audit-anatomy, design-craft critique, and detect-design-drift. This is the canonical invocation path.
mcp__harness__audit_brand — MCP tool. Input: { path, mode, files?, designStrictness?, rules? }. Output: { findings, summary, catalog, meta }. Consumed by check-design and the (future) design-pipeline orchestrator.
DesignConstraintAdapter.recordFindings() — Generic graph persistence entry point. Brand findings reuse the adapter (no graph schema changes in v1).
harness-design skill — Authors DESIGN.md ## Brand Rules. audit-brand-compliance is the matching enforcer.
Verifier<F> interface — Extracted in this PR at the 4th-verifier threshold. Lives at packages/cli/src/shared/verifier.ts. Adding a 5th verifier requires only a type-alias declaration of conformance.
Success Criteria
See docs/changes/design-pipeline/audit-brand-compliance/proposal.md for the full 34 success criteria. Highlights:
- DESIGN.md parser returns
null when section absent (silent-skip pattern)
- Token-extensions walker returns
null when no token carries $extensions.harness.brand
- BRAND-T001 fires on
tokens.X, var(--x), and 'X' reference forms
- BRAND-T001 honors approved_contexts (no finding when context is allowed)
- BRAND-V001 fires on JSX text + string-typed JSX attributes (case-insensitive)
- BRAND-V001 deduplicates per
(file, line, phrase)
- Verifier interface extraction: anatomy / drift / brand all declare structural conformance
harness check-design test extended for 4-verifier composition (zero regressions)
- MCP tool count bumps 72 → 73
Rationalizations to Reject
These are common rationalizations that sound reasonable but lead to incorrect results. When you catch yourself thinking any of these, stop and follow the documented process instead.
| Rationalization |
Why It Is Wrong |
"This copy says 'world-class' which sounds off-brand, so I'll flag it even though it isn't in forbidden_phrases." |
BRAND-V001 fires ONLY on phrases declared in DESIGN.md ## Brand Rules → voice.forbidden_phrases. Inventing violations beyond the declared list is editorializing, not auditing. If the phrase should be banned, that is a DESIGN.md authoring change (via harness-design), not an audit finding. |
"This token is clearly used decoratively, so I'll flag it even though decorative isn't in its forbidden_contexts." |
BRAND-T001 fires only when a matched context keyword is in that token's declared forbidden_contexts, and honors approved_contexts. The policy lives in the token metadata — do not substitute your own judgment for the declared contract. |
"This .ts file has a forbidden phrase in a string, so I'll flag it." |
BRAND-V001 scans only .jsx/.tsx — user-visible JSX text and string-typed JSX attributes. .ts/.js and .md copy are a different audience and explicitly out of scope. |
"## Brand Rules is missing from DESIGN.md, but I can infer the brand voice, so I'll audit anyway." |
The DESIGN.md parser returns null when the section is absent, and BRAND-V001 silently skips. Likewise BRAND-T* skips when no token carries $extensions.harness.brand. No findings without parsed inputs — a null resolver is not a verifier failure. |
| "This copy looks like an error state, so I'll infer the tone-by-context and flag a mismatch." |
Tone-by-context inference is deferred to v1.x. v1 matches only the explicit context-vocabulary keywords against adjacent source text. Do not simulate component-state inference the audit does not yet perform. |
Examples
Example: Token used in forbidden context
Input:
design-system/tokens.json:
{
"color": {
"brand": {
"500": {
"$type": "color",
"$value": "#3b82f6",
"$extensions": {
"harness": {
"brand": {
"role": "primary",
"approved_contexts": ["cta", "selection", "focus"],
"forbidden_contexts": ["data-visualization", "decorative"]
}
}
}
}
}
}
}
src/Chart.tsx:
// data-visualization color palette
const palette = [tokens.color.brand.500, ...];
Output:
BRAND-T001 [error] src/Chart.tsx:2 — Token "color.brand.500" is used in forbidden context "data-visualization"
Fix: Token "color.brand.500" is not approved for the "data-visualization" context.
Use an approved token (allowed contexts: cta, selection, focus), or update
tokens.json $extensions.harness.brand if the policy is wrong.
Example: Forbidden phrase in UI copy
Input:
DESIGN.md:
## Brand Rules
### Voice
forbidden_phrases:
- "click here"
- "best-in-class"
src/Cta.tsx:
export const Cta = () => <a href="/x">Click here</a>;
Output:
BRAND-V001 [warn] src/Cta.tsx:1 — UI copy contains forbidden phrase "click here" — declared at DESIGN.md ## Brand Rules → Voice → forbidden_phrases
Fix: Rewrite to avoid "click here". If the phrase is unavoidable for this context,
remove it from voice.forbidden_phrases (or scope the audit) — but the default
policy is that brand voice trumps convenience.
Gates
- No findings without parsed inputs. DESIGN.md absent → BRAND-V001 skips silently. tokens.json
$extensions.harness.brand absent on every token → BRAND-T001 skips silently. Either resolver returning null is NOT a verifier failure.
- No
.ts/.js file scans for BRAND-V001. Only .jsx/.tsx (user-visible JSX). Doc copy in .md is a different audience.
- No tone-by-context inference. v1 only matches the explicit context-vocabulary keywords against surrounding source text. v1.x adds component-state inference.
- No autofix. audit-only. The matching
align-brand-compliance fix-side skill is deferred until detect signals demand.
- No graph schema changes. v1 reuses
VIOLATES_design via recordFindings(). v1.x may add VIOLATES_brand edge for queryability.
- Strictness from config, not assumed. Read
design.strictness from harness.config.json; default standard if absent.
Escalation
- When BRAND-T001 false-positives on a far-context reference: the v1 context inference is intentionally narrow (same line + adjacent non-blank). For a token used in a "background" context where the keyword appears 10 lines away, v1 misses it. v1.x adds richer context inference; for now, either widen the surrounding comment or accept the miss.
- When BRAND-V001 false-positives on a substring (e.g., "as is" in "as issued"): v1 uses substring match. Add word-boundary regex in v1.x. For now, rephrase the copy or remove the phrase from voice.forbidden_phrases.
- When a project ships tokens with a different
$extensions shape: v1 reads only harness.brand. Document the actual shape your project uses and add it to the schema sketch in ADR 0028 — DTCG $extensions namespaces are vendor-prefixed and additions are forward-compatible.
- When
harness validate runtime exceeds 3 seconds: Set design.audit.brandCompliance.fastMode.maxFiles to cap the scope. The MCP tool ignores the cap (fast/full equivalent in v1).
- When the graph persistence fails: Skip graph integration for that run; findings still appear in the report. The graph is a consumer, not a gate.
- When you want tone-by-context rules today: Manual audit until v1.x ships. Component-state inference (empty/error/success/loading) requires JSX-context analysis that's a separate brainstorm.
Status
v1 — in implementation. See:
- Spec:
docs/changes/design-pipeline/audit-brand-compliance/proposal.md
- ADR (input schema source):
docs/knowledge/decisions/0028-brand-guidelines-source-of-truth.md
- Roadmap entry: part of the
design-pipeline initiative in docs/roadmap.md
- Sibling rule-based audits:
audit-component-anatomy, detect-design-drift
- Cross-cutting: extracts
Verifier<F> interface at packages/cli/src/shared/verifier.ts (deferred until 4th data point — this is it)
1---2name: audit-brand-compliance3description: Audit Brand Compliance4---5# Audit Brand Compliance67> Rule-based brand-semantics audit. Detects (a) tokens used in forbidden contexts per their `$extensions.harness.brand.forbidden_contexts` metadata (BRAND-T001) and (b) UI copy containing phrases listed in `DESIGN.md ## Brand Rules → voice.forbidden_phrases` (BRAND-V001). The 4th composed verifier in `harness check-design`, alongside audit-component-anatomy, design-craft critique, and detect-design-drift.89## When to Use1011- After authoring or editing `DESIGN.md ## Brand Rules` — verify the new constraints are enforceable on the existing codebase12- After adding `$extensions.harness.brand` metadata to a token — discover existing call sites in forbidden contexts13- As part of `harness validate` (fast-mode hook, gated by `design.audit.brandCompliance.enabled`)14- As the 4th composed verifier in `harness check-design` (the unified design check)15- Before a PR with UI copy or token-usage changes lands16- NOT for tone-by-context rules (deferred to v1.x — requires component-state inference)17- NOT for reading-level or sentence-length rules (deferred to v1.x — ship with tone-context)18- NOT for asset-usage rules (deferred to v1.x — requires image-tag scanning)19- NOT for semantic-token-alias enforcement (overlaps with detect-design-drift T001 — design after both have shipped)20- NOT for brand-rule authoring (use `harness-design` skill to draft DESIGN.md sections)2122## Capability Roles2324<!-- Capability seam: this skill participates in a real extension point whose three roles are named and concrete. A seam with only one role filled is accidental single-implementation lock-in. See harness-skill-authoring Phase 1C. -->2526- **Defines (Service Definition):** the shared `Verifier<F, Cat, Meta>` interface (`packages/cli/src/shared/verifier.ts`); this was the 4th verifier whose addition triggered extraction of the interface.27- **Provides (Provider):** **this skill** — emits `AuditBrandOutput = Verifier<BrandFinding>` (`packages/cli/src/brand/index.ts`).28- **Consumes (Consumer):** `harness-design-pipeline` / `harness check-design`, which compose it generically via `VerifierRegistry`.2930## Process3132### Phase 1: LOAD — Parse the two input sources33341. **Read project configuration.** Check `harness.config.json` for:35 - `design.strictness` — `strict` / `standard` / `permissive` (default `standard`)36 - `design.audit.brandCompliance.enabled` — gate (default `true`)37 - `design.audit.brandCompliance.rules.{tokenMisuse,voice}` — per-rule toggles38392. **Load `design-system/DESIGN.md` `## Brand Rules`.** The parser extracts:40 - `voice.forbiddenPhrases: string[]` — used by BRAND-V001 in v141 - `voice.constant`, `voice.readingLevel`, `voice.maxSentenceWords` — parsed but unused in v1 (forward-compat)42 - `toneByContext`, `assets`, `semanticTokenAliases` — parsed but unused (v1.x)43 - Returns `null` when DESIGN.md absent or `## Brand Rules` section missing → BRAND-V001 silently skips.44453. **Load `design-system/tokens.json` `$extensions.harness.brand`.** Walks the DTCG token tree capturing per-token `role`, `approved_contexts`, `forbidden_contexts`. Returns `null` when no token carries the extension → BRAND-T\* silently skips.4647### Phase 2: SCAN — Apply the two rule families48491. **BRAND-T001 — token misuse (regex-based).** For each token whose `forbidden_contexts` is non-empty:50 - Find every reference to the token's dotted path in source (recognizes three forms):51 - `tokens.X.Y.Z` (JS accessor)52 - `var(--X-Y-Z)` (CSS var, kebab-cased)53 - `'X.Y.Z'` / `"X.Y.Z"` (string literal)54 - Inspect surrounding context (same line + nearest non-blank previous and next line) for the v1 context-vocabulary keywords: `cta`, `selection`, `focus`, `data-visualization`, `decorative`, `background`, `text`, `border`, `error`, `success`, `warning`.55 - If a forbidden context matches: emit BRAND-T001.56572. **BRAND-V001 — forbidden phrases (TS Compiler API).** For each `.tsx`/`.jsx` file:58 - Walk the JSX tree.59 - For each `JsxText` node: case-insensitive substring scan for any forbiddenPhrase.60 - For each `JsxAttribute` whose initializer is a string literal: same scan.61 - Deduplicate per `(file, line, phrase)`.6263### Phase 3: REPORT — Aggregate and surface64651. **Severity from `design.strictness`** (uses `severityFor`):66 - `strict` — all findings `error`67 - `standard` — BRAND-T001 `error` (declared violation), BRAND-V001 `warn` (copy nuance)68 - `permissive` — all findings `info`69702. **Aggregate `bySeverity` and `byCode`** into the standard Verifier shape: `{ findings, summary, catalog, meta }`.71723. **Persist findings to the graph (when composed by check-design).** check-design routes brand findings through `DesignConstraintAdapter.recordFindings()` alongside anatomy / craft / drift. v1 uses the shared `VIOLATES_design` edge; v1.x may add a brand-specific edge.7374## Harness Integration7576- **`harness validate`** — Fast-mode hook gated by `design.audit.brandCompliance.enabled`. Degrades gracefully on failure (single warning; other checks continue).77- **`harness check-design`** — Composes brand as the 4th verifier alongside audit-anatomy, design-craft critique, and detect-design-drift. This is the canonical invocation path.78- **`mcp__harness__audit_brand`** — MCP tool. Input: `{ path, mode, files?, designStrictness?, rules? }`. Output: `{ findings, summary, catalog, meta }`. Consumed by check-design and the (future) design-pipeline orchestrator.79- **`DesignConstraintAdapter.recordFindings()`** — Generic graph persistence entry point. Brand findings reuse the adapter (no graph schema changes in v1).80- **`harness-design` skill** — Authors `DESIGN.md ## Brand Rules`. audit-brand-compliance is the matching enforcer.81- **`Verifier<F>` interface** — Extracted in this PR at the 4th-verifier threshold. Lives at `packages/cli/src/shared/verifier.ts`. Adding a 5th verifier requires only a type-alias declaration of conformance.8283## Success Criteria8485See `docs/changes/design-pipeline/audit-brand-compliance/proposal.md` for the full 34 success criteria. Highlights:8687- DESIGN.md parser returns `null` when section absent (silent-skip pattern)88- Token-extensions walker returns `null` when no token carries `$extensions.harness.brand`89- BRAND-T001 fires on `tokens.X`, `var(--x)`, and `'X'` reference forms90- BRAND-T001 honors approved_contexts (no finding when context is allowed)91- BRAND-V001 fires on JSX text + string-typed JSX attributes (case-insensitive)92- BRAND-V001 deduplicates per `(file, line, phrase)`93- Verifier interface extraction: anatomy / drift / brand all declare structural conformance94- `harness check-design` test extended for 4-verifier composition (zero regressions)95- MCP tool count bumps 72 → 739697## Rationalizations to Reject9899These are common rationalizations that sound reasonable but lead to incorrect results. When you catch yourself thinking any of these, stop and follow the documented process instead.100101| Rationalization | Why It Is Wrong |102| ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |103| "This copy says 'world-class' which sounds off-brand, so I'll flag it even though it isn't in `forbidden_phrases`." | BRAND-V001 fires ONLY on phrases declared in `DESIGN.md ## Brand Rules → voice.forbidden_phrases`. Inventing violations beyond the declared list is editorializing, not auditing. If the phrase should be banned, that is a DESIGN.md authoring change (via `harness-design`), not an audit finding. |104| "This token is clearly used decoratively, so I'll flag it even though `decorative` isn't in its `forbidden_contexts`." | BRAND-T001 fires only when a matched context keyword is in that token's declared `forbidden_contexts`, and honors `approved_contexts`. The policy lives in the token metadata — do not substitute your own judgment for the declared contract. |105| "This `.ts` file has a forbidden phrase in a string, so I'll flag it." | BRAND-V001 scans only `.jsx`/`.tsx` — user-visible JSX text and string-typed JSX attributes. `.ts`/`.js` and `.md` copy are a different audience and explicitly out of scope. |106| "`## Brand Rules` is missing from DESIGN.md, but I can infer the brand voice, so I'll audit anyway." | The DESIGN.md parser returns `null` when the section is absent, and BRAND-V001 silently skips. Likewise BRAND-T\* skips when no token carries `$extensions.harness.brand`. No findings without parsed inputs — a null resolver is not a verifier failure. |107| "This copy looks like an error state, so I'll infer the tone-by-context and flag a mismatch." | Tone-by-context inference is deferred to v1.x. v1 matches only the explicit context-vocabulary keywords against adjacent source text. Do not simulate component-state inference the audit does not yet perform. |108109## Examples110111### Example: Token used in forbidden context112113**Input:**114115`design-system/tokens.json`:116117```json118{119 "color": {120 "brand": {121 "500": {122 "$type": "color",123 "$value": "#3b82f6",124 "$extensions": {125 "harness": {126 "brand": {127 "role": "primary",128 "approved_contexts": ["cta", "selection", "focus"],129 "forbidden_contexts": ["data-visualization", "decorative"]130 }131 }132 }133 }134 }135 }136}137```138139`src/Chart.tsx`:140141```tsx142// data-visualization color palette143const palette = [tokens.color.brand.500, ...];144```145146**Output:**147148```149BRAND-T001 [error] src/Chart.tsx:2 — Token "color.brand.500" is used in forbidden context "data-visualization"150 Fix: Token "color.brand.500" is not approved for the "data-visualization" context.151 Use an approved token (allowed contexts: cta, selection, focus), or update152 tokens.json $extensions.harness.brand if the policy is wrong.153```154155### Example: Forbidden phrase in UI copy156157**Input:**158159`DESIGN.md`:160161```markdown162## Brand Rules163164### Voice165166forbidden_phrases:167168- "click here"169- "best-in-class"170```171172`src/Cta.tsx`:173174```tsx175export const Cta = () => <a href="/x">Click here</a>;176```177178**Output:**179180```181BRAND-V001 [warn] src/Cta.tsx:1 — UI copy contains forbidden phrase "click here" — declared at DESIGN.md ## Brand Rules → Voice → forbidden_phrases182 Fix: Rewrite to avoid "click here". If the phrase is unavoidable for this context,183 remove it from voice.forbidden_phrases (or scope the audit) — but the default184 policy is that brand voice trumps convenience.185```186187## Gates188189- **No findings without parsed inputs.** DESIGN.md absent → BRAND-V001 skips silently. tokens.json `$extensions.harness.brand` absent on every token → BRAND-T001 skips silently. Either resolver returning null is NOT a verifier failure.190- **No `.ts`/`.js` file scans for BRAND-V001.** Only `.jsx`/`.tsx` (user-visible JSX). Doc copy in `.md` is a different audience.191- **No tone-by-context inference.** v1 only matches the explicit context-vocabulary keywords against surrounding source text. v1.x adds component-state inference.192- **No autofix.** audit-only. The matching `align-brand-compliance` fix-side skill is deferred until detect signals demand.193- **No graph schema changes.** v1 reuses `VIOLATES_design` via `recordFindings()`. v1.x may add `VIOLATES_brand` edge for queryability.194- **Strictness from config, not assumed.** Read `design.strictness` from `harness.config.json`; default `standard` if absent.195196## Escalation197198- **When BRAND-T001 false-positives on a far-context reference:** the v1 context inference is intentionally narrow (same line + adjacent non-blank). For a token used in a "background" context where the keyword appears 10 lines away, v1 misses it. v1.x adds richer context inference; for now, either widen the surrounding comment or accept the miss.199- **When BRAND-V001 false-positives on a substring (e.g., "as is" in "as issued"):** v1 uses substring match. Add word-boundary regex in v1.x. For now, rephrase the copy or remove the phrase from voice.forbidden_phrases.200- **When a project ships tokens with a different `$extensions` shape:** v1 reads only `harness.brand`. Document the actual shape your project uses and add it to the schema sketch in ADR 0028 — DTCG `$extensions` namespaces are vendor-prefixed and additions are forward-compatible.201- **When `harness validate` runtime exceeds 3 seconds:** Set `design.audit.brandCompliance.fastMode.maxFiles` to cap the scope. The MCP tool ignores the cap (`fast`/`full` equivalent in v1).202- **When the graph persistence fails:** Skip graph integration for that run; findings still appear in the report. The graph is a consumer, not a gate.203- **When you want tone-by-context rules today:** Manual audit until v1.x ships. Component-state inference (empty/error/success/loading) requires JSX-context analysis that's a separate brainstorm.204205## Status206207**v1 — in implementation.** See:208209- Spec: `docs/changes/design-pipeline/audit-brand-compliance/proposal.md`210- ADR (input schema source): `docs/knowledge/decisions/0028-brand-guidelines-source-of-truth.md`211- Roadmap entry: part of the `design-pipeline` initiative in `docs/roadmap.md`212- Sibling rule-based audits: `audit-component-anatomy`, `detect-design-drift`213- Cross-cutting: extracts `Verifier<F>` interface at `packages/cli/src/shared/verifier.ts` (deferred until 4th data point — this is it)