|
| 1 | +--- |
| 2 | +name: token-validation |
| 3 | +description: Validate OUDS design token usage in SCSS component files - checks that every component token used is present in all three brands (orange, sosh, orange-compact) and detects anti-patterns like hardcoded values, raw tokens, and forbidden CSS patterns |
| 4 | +compatibility: opencode |
| 5 | +metadata: |
| 6 | + audience: developers |
| 7 | + workflow: token-audit |
| 8 | +--- |
| 9 | + |
| 10 | +## What I do |
| 11 | + |
| 12 | +I analyze SCSS component files in the OUDS Web project to: |
| 13 | + |
| 14 | +1. **Cross-brand token presence check**: For every `$ouds-<component>-*` token referenced in a component SCSS file, I verify it is defined in ALL three brand `_component.scss` files: |
| 15 | + - `packages/orange/scss/tokens/_component.scss` |
| 16 | + - `packages/sosh/scss/tokens/_component.scss` |
| 17 | + - `packages/orange-compact/scss/tokens/_component.scss` |
| 18 | + |
| 19 | +2. **Anti-pattern detection**: I flag forbidden patterns in component SCSS files: |
| 20 | + - Hardcoded numeric values (e.g. `8px`, `1rem`, `#ff7900`, `rgb(...)`) |
| 21 | + - Raw tokens used directly in components (`$core-ouds-*`, `$core-orange-*`, `$core-sosh-*`) |
| 22 | + - Forbidden Sass functions (`lighten(`, `darken(`) |
| 23 | + - `border: none` (must be `border: 0`) |
| 24 | + - Direct `transition:` or `border-radius:` properties without `@include` |
| 25 | + |
| 26 | +## When to use me |
| 27 | + |
| 28 | +Use me when: |
| 29 | + |
| 30 | +- You have added or modified SCSS component files and want to verify token usage is correct across all brands |
| 31 | +- You are reviewing a PR that touches `scss/_*.scss` or `scss/forms/_*.scss` files |
| 32 | +- You want to audit the full codebase for token-related issues |
| 33 | + |
| 34 | +## Scope |
| 35 | + |
| 36 | +### Files to analyze |
| 37 | + |
| 38 | +Only analyze files in these two directories: |
| 39 | + |
| 40 | +- `scss/_*.scss` (root component files) |
| 41 | +- `scss/forms/_*.scss` (form component files) |
| 42 | + |
| 43 | +### Files to EXCLUDE from analysis |
| 44 | + |
| 45 | +These are not component implementations — skip them entirely: |
| 46 | + |
| 47 | +- `scss/_variables.scss` |
| 48 | +- `scss/_variables-dark.scss` |
| 49 | +- `scss/_config.scss` |
| 50 | +- `scss/_functions.scss` |
| 51 | +- `scss/_mixins.scss` |
| 52 | +- `scss/_maps.scss` |
| 53 | +- `scss/_root.scss` |
| 54 | +- `scss/_reboot.scss` |
| 55 | +- `scss/_utilities.scss` |
| 56 | +- `scss/_helpers.scss` |
| 57 | +- `scss/_grid.scss` |
| 58 | +- `scss/_containers.scss` |
| 59 | +- `scss/_images.scss` |
| 60 | +- `scss/_type.scss` |
| 61 | +- `scss/_transitions.scss` |
| 62 | +- `scss/_forms.scss` |
| 63 | + |
| 64 | +### Token files to read (all three brands, mandatory) |
| 65 | + |
| 66 | +- `packages/orange/scss/tokens/_component.scss` |
| 67 | +- `packages/sosh/scss/tokens/_component.scss` |
| 68 | +- `packages/orange-compact/scss/tokens/_component.scss` |
| 69 | + |
| 70 | +## Step-by-step instructions |
| 71 | + |
| 72 | +### Phase 1: Load all component tokens per brand |
| 73 | + |
| 74 | +Read all three brand `_component.scss` files. For each file, extract every SCSS variable definition that matches the pattern: |
| 75 | + |
| 76 | +``` |
| 77 | +^\$(ouds-[a-z][a-z0-9-]*):\s |
| 78 | +``` |
| 79 | + |
| 80 | +This gives you three sets: `tokens_orange`, `tokens_sosh`, `tokens_orange_compact`. |
| 81 | + |
| 82 | +The union of all three sets is the complete list of known component tokens. |
| 83 | + |
| 84 | +### Phase 2: Identify component SCSS files to analyze |
| 85 | + |
| 86 | +Use Glob to list: |
| 87 | + |
| 88 | +- `scss/_*.scss` |
| 89 | +- `scss/forms/_*.scss` |
| 90 | + |
| 91 | +Then exclude the files listed in the **Files to EXCLUDE** section above. |
| 92 | + |
| 93 | +If the user specifies a particular component (e.g. "validate button"), only analyze `scss/_buttons.scss`. |
| 94 | + |
| 95 | +### Phase 3: For each component SCSS file |
| 96 | + |
| 97 | +#### 3a. Extract all `$ouds-*` token references |
| 98 | + |
| 99 | +Grep the file for all occurrences of `\$ouds-[a-z][a-z0-9-]*` (excluding the `: ` assignment syntax to avoid re-reading token definitions if the file defines local overrides). Collect unique token names used. |
| 100 | + |
| 101 | +**Important**: A token is "used" if it appears as a value reference (e.g. `$ouds-button-size-min-width`, not as a definition). In practice, component SCSS files do not define `$ouds-*` tokens — they only use them. So any `$ouds-*` occurrence is a usage. |
| 102 | + |
| 103 | +#### 3b. Cross-brand presence check |
| 104 | + |
| 105 | +For each token found in step 3a: |
| 106 | + |
| 107 | +- Check if it exists in `tokens_orange` |
| 108 | +- Check if it exists in `tokens_sosh` |
| 109 | +- Check if it exists in `tokens_orange_compact` |
| 110 | + |
| 111 | +A token **passes** if it exists in all three. |
| 112 | +A token **fails** if it is missing in one or more brands. |
| 113 | + |
| 114 | +#### 3c. Anti-pattern detection |
| 115 | + |
| 116 | +Scan the file content for: |
| 117 | + |
| 118 | +| Anti-pattern | Regex / Pattern | Severity | |
| 119 | +| ----------------------------- | --------------------------------------------------------------------------- | -------- | |
| 120 | +| Hardcoded pixel/rem/em values | `:\s*[\d.]+(?:px\|rem\|em)` | WARNING | |
| 121 | +| Hardcoded hex color | `#[0-9a-fA-F]{3,8}(?!\s*[;,{])` or as a value | WARNING | |
| 122 | +| Hardcoded rgb/rgba | `rgba?\(` | WARNING | |
| 123 | +| Raw OUDS core token | `\$core-ouds-` | ERROR | |
| 124 | +| Raw brand token | `\$core-orange-\|\$core-sosh-\|\$core-orange-compact-` | ERROR | |
| 125 | +| Forbidden Sass function | `lighten\(\|darken\(` | ERROR | |
| 126 | +| border: none | `border:\s*none` | ERROR | |
| 127 | +| Direct transition property | `^\s+transition:\s` (without `@include`) | ERROR | |
| 128 | +| Direct border-radius property | `^\s+border-radius:\s` (without `@include` and without `stylelint-disable`) | ERROR | |
| 129 | + |
| 130 | +**Exceptions for hardcoded values** (do NOT flag these): |
| 131 | + |
| 132 | +- Standalone `0` (e.g. `margin: 0`, `border: 0`, `padding: 0`) — `0` has no unit and is valid |
| 133 | +- Values inside `@keyframes` blocks (animation percentages are not design tokens) |
| 134 | +- Values inside comments `//` or `/* */` |
| 135 | +- `100%` used for width/height fill |
| 136 | +- `1` or `-1` used as multipliers in `calc()` expressions |
| 137 | +- Line numbers or indices in CSS counters |
| 138 | +- Values flagged with `// stylelint-disable` comments on the same line |
| 139 | + |
| 140 | +### Phase 4: Generate the report |
| 141 | + |
| 142 | +Output a clear, structured report: |
| 143 | + |
| 144 | +``` |
| 145 | +╔══════════════════════════════════════════════════════╗ |
| 146 | +║ OUDS TOKEN VALIDATION REPORT ║ |
| 147 | +║ Files analyzed: <N> ║ |
| 148 | +╚══════════════════════════════════════════════════════╝ |
| 149 | +
|
| 150 | +── CROSS-BRAND TOKEN PRESENCE ────────────────────────── |
| 151 | +
|
| 152 | +✅ PASS (<N> files — all tokens present in all brands) |
| 153 | + • _alert.scss ............... 13 tokens ✓ |
| 154 | + • _buttons.scss ............. 59 tokens ✓ |
| 155 | + • _chips.scss ............... 44 tokens ✓ |
| 156 | + [list all passing files] |
| 157 | +
|
| 158 | +❌ FAIL (<N> files) |
| 159 | + • _<component>.scss |
| 160 | + ❌ $ouds-<token-name> |
| 161 | + orange: ✓ |
| 162 | + sosh: ✗ MISSING ← packages/sosh/scss/tokens/_component.scss |
| 163 | + orange-compact: ✓ |
| 164 | +
|
| 165 | +── ANTI-PATTERNS ─────────────────────────────────────── |
| 166 | +
|
| 167 | +✅ No anti-patterns found |
| 168 | +
|
| 169 | + OR |
| 170 | +
|
| 171 | +⚠️ WARNING — Hardcoded values |
| 172 | + • scss/_chips.scss |
| 173 | + line 5: gap: 0 8px; → replace with a spacing token |
| 174 | + line 6: padding: 0 5px; → replace with a spacing token |
| 175 | +
|
| 176 | +❌ ERROR — Forbidden patterns |
| 177 | + • scss/_<component>.scss |
| 178 | + line 42: $core-ouds-dimension-200 → use semantic token instead |
| 179 | +
|
| 180 | +── SUMMARY ───────────────────────────────────────────── |
| 181 | + Cross-brand: <N_pass> pass, <N_fail> fail |
| 182 | + Anti-patterns: <N_warnings> warnings, <N_errors> errors |
| 183 | +``` |
| 184 | + |
| 185 | +## Important context |
| 186 | + |
| 187 | +### Token naming convention |
| 188 | + |
| 189 | +Component tokens follow this pattern: `$ouds-<component>-<category>-<variant>` |
| 190 | + |
| 191 | +Where `<component>` can be multi-word with hyphens: |
| 192 | + |
| 193 | +- `$ouds-alert-*` → component: alert |
| 194 | +- `$ouds-button-*` → component: button |
| 195 | +- `$ouds-bullet-list-*` → component: bullet-list |
| 196 | +- `$ouds-control-item-*` → component: control-item |
| 197 | +- `$ouds-input-tag-*` → component: input-tag |
| 198 | +- `$ouds-text-input-*` → component: text-input |
| 199 | +- `$ouds-text-area-*` → component: text-area |
| 200 | + |
| 201 | +### Brand token files structure |
| 202 | + |
| 203 | +Each brand's `_component.scss` groups tokens by component, delimited by markers: |
| 204 | + |
| 205 | +```scss |
| 206 | +// scss-docs-start ouds-component-<name> |
| 207 | +$ouds-<name>-...: ... !default; |
| 208 | +// scss-docs-end ouds-component-<name> |
| 209 | +``` |
| 210 | + |
| 211 | +The three brand files have the **same token names** but **different values**. All three must define the same set of tokens. |
| 212 | + |
| 213 | +### Known component token groups (from \_component.scss) |
| 214 | + |
| 215 | +The following component token groups are defined (same across all brands): |
| 216 | + |
| 217 | +- `ouds-component-alert` → `$ouds-alert-*` |
| 218 | +- `ouds-component-badge` → `$ouds-badge-*` |
| 219 | +- `ouds-component-breadcrumb` → `$ouds-breadcrumb-*` |
| 220 | +- `ouds-component-bullet` → `$ouds-bullet-list-*` |
| 221 | +- `ouds-component-button` → `$ouds-button-*` |
| 222 | +- `ouds-component-checkbox` → `$ouds-checkbox-*` |
| 223 | +- `ouds-component-chip` → `$ouds-chip-*` |
| 224 | +- `ouds-component-control` → `$ouds-control-item-*` |
| 225 | +- `ouds-component-divider` → `$ouds-divider-*` |
| 226 | +- `ouds-component-expand` → `$ouds-expand-*` |
| 227 | +- `ouds-component-input` → `$ouds-input-tag-*` |
| 228 | +- `ouds-component-link` → `$ouds-link-*` |
| 229 | +- `ouds-component-pin` → `$ouds-pin-code-input-*` |
| 230 | +- `ouds-component-quantity` → `$ouds-quantity-input-*` |
| 231 | +- `ouds-component-radio` → `$ouds-radio-button-*` |
| 232 | +- `ouds-component-select` → `$ouds-select-input-*` |
| 233 | +- `ouds-component-skeleton` → `$ouds-skeleton-*` |
| 234 | +- `ouds-component-switch` → `$ouds-switch-*` |
| 235 | +- `ouds-component-tag` → `$ouds-tag-*` |
| 236 | +- `ouds-component-text` → `$ouds-text-input-*`, `$ouds-text-area-*` |
| 237 | + |
| 238 | +### What counts as a component SCSS token vs a semantic token |
| 239 | + |
| 240 | +- Component tokens: `$ouds-<specific-component>-*` (e.g. `$ouds-button-size-min-width`) → MUST be checked across all brands |
| 241 | +- Semantic tokens: `$ouds-color-*`, `$ouds-space-*`, `$ouds-border-*`, `$ouds-dimension-*`, `$ouds-size-*`, `$ouds-opacity-*` → These are defined in `_semantic.scss`, not `_component.scss`. **Do NOT check these cross-brand** — they are always present. |
| 242 | + |
| 243 | +To distinguish: a component token is any `$ouds-*` variable whose name prefix matches one of the known component groups listed above. |
| 244 | + |
| 245 | +## Known real anti-patterns (validated) |
| 246 | + |
| 247 | +These are confirmed issues found in the codebase that the skill should flag: |
| 248 | + |
| 249 | +| File | Line | Pattern | Type | |
| 250 | +| ------------------------- | ---------------- | ------------------------------- | ------------------------ | |
| 251 | +| `scss/_button-group.scss` | 142, 146 | `$core-orange-color-orange-500` | ERROR: raw brand token | |
| 252 | +| `scss/_chips.scss` | 90, 91, 158, 159 | `width: 1em; height: 1em;` | WARNING: hardcoded value | |
| 253 | + |
| 254 | +## How to run |
| 255 | + |
| 256 | +When this skill is loaded, immediately: |
| 257 | + |
| 258 | +1. Read all three brand `_component.scss` files |
| 259 | +2. List all SCSS component files (excluding non-component files) |
| 260 | +3. For each file, run checks as described above |
| 261 | +4. Output the full report |
| 262 | + |
| 263 | +If the user says "validate [component name]", only analyze the SCSS file(s) corresponding to that component. |
| 264 | + |
| 265 | +Do not make any file modifications. This is a read-only audit. |
0 commit comments