feat(rules): add 4 design-system PreToolUse rules (DOJ-3924)#14
feat(rules): add 4 design-system PreToolUse rules (DOJ-3924)#14
Conversation
Adds AI-write-time enforcement for Design System rules — the 4th enforcement layer complementing dojo-os's: - useDsRules React hook (passive, runtime-only) - ds-lint pre-commit hook (post-author, post-write) - validate-storage-upload Edge Function (Storage bucket uploads) This 4th layer fires PreToolUse on Edit/Write/MultiEdit, blocking the write BEFORE the file is touched if the LLM proposes a violation. Tightest feedback loop + educates the LLM in real time. 4 new rules: - ds-arbitrary-breakpoint (block min-[Xpx] in TSX, suggest sm:/md:/lg:) - ds-deep-ui-import (block @/components/ui/<file> deep imports, force barrel) - ds-arbitrary-fixed-width-in-ds-component (block w-[Xpx] inside src/components/ui/) - ds-raw-hex-color-in-source (warn raw hex in TSX/TS, exempt theme/test/storybook) Each rule ships with 5 tests (20 total): blocks/warns the violation, allows preset alternatives, allows exempt paths, allows bypass marker. Bumps the rules manifest only — no new bash scripts, no schema changes. The existing v1.5.0 manifest-driven framework dispatches via pre-edit.sh on Edit/Write/MultiEdit and parse-input.sh extracts file_path + content. Closes DOJ-3924 (Phase G of DOJ-3439 design-system-linter). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR adds four PreToolUse rules that enforce the Dojo Design System at AI write-time — before a file is touched — completing the 4th enforcement layer alongside the existing runtime hook, pre-commit linter, and Storage upload validator. All three previously flagged issues have been resolved: the
Confidence Score: 5/5Manifest-only change adding 4 new enforcement rules; no runtime code paths are modified and no existing rules are altered. All three previously flagged blocking issues have been addressed: the absolute-path anchor is fixed with (^|/), the action/description mismatch on the hex rule is corrected, and the two missing exemption tests have been added. The remaining observations are minor coverage gaps that do not affect correctness of the existing rules. No files require special attention; the two open gaps (missing .stories.* test and uncovered max-[Xpx] breakpoints) are both in hooks/rules/rules.yaml.
|
| Filename | Overview |
|---|---|
| hooks/rules/rules.yaml | Adds 4 DS PreToolUse rules; all previously-flagged P1 issues fixed (`(^ |
| hooks/rules/rules.json | Generated JSON mirror of rules.yaml; all 4 new rules faithfully represented including the 6-test suite for ds-arbitrary-fixed-width-in-ds-component and the 8-test suite for ds-raw-hex-color-in-source. |
| hooks/rules/README.md | Adds a Rule families section documenting the new DS family; accurate, well-linked to the Linear issue. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["Claude proposes Edit / Write / MultiEdit"] --> B["pre-edit.sh dispatches"]
B --> C["parse-input.sh: extract file_path + content"]
C --> D["eval-rule.sh: evaluate DS rules"]
D --> E{"ds-arbitrary-breakpoint"}
D --> F{"ds-deep-ui-import"}
D --> G{"ds-arbitrary-fixed-width"}
D --> H{"ds-raw-hex-color"}
E -->|"match + no bypass"| I["EXIT 2 — BLOCKED"]
F -->|"match + no bypass"| I
G -->|"match + no bypass"| I
H -->|"match + no bypass"| J["EXIT 0 + stderr WARNING"]
E -->|"no match / bypass"| K["EXIT 0 — allowed"]
F -->|"no match / bypass"| K
G -->|"no match / bypass"| K
H -->|"no match / bypass"| K
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
hooks/rules/rules.yaml:607-608
**`.stories.*` exemption lacks a lock-in test**
The `not_pattern` lists five exemptions; this PR added lock-in tests for `design-system/` and `design-tokens` (addressing the previous review comment) but `.stories.*` still has no corresponding test case. A future regex change that accidentally narrows `not_pattern` could silently start warning on story files without any test failure — the same scenario the two new tests were explicitly added to prevent.
### Issue 2 of 2
hooks/rules/rules.yaml:428-432
**`max-[Xpx]` arbitrary breakpoints not covered**
Tailwind supports both `min-[Xpx]:` and `max-[Xpx]:` arbitrary breakpoints. The content pattern `min-\[[0-9]+px\]` only catches the `min-` variant; a write like `className="max-[1000px]:hidden"` will silently pass through. If `max-[Xpx]` breakpoints are intentionally out of scope, a brief comment noting that would help future maintainers avoid surprise gaps.
Reviews (2): Last reviewed commit: "fix(rules): make ds-fixed-width rule fir..." | Re-trigger Greptile
| pattern: 'from[[:space:]]+["'']@/components/ui/[a-zA-Z][a-zA-Z0-9_-]+["'']' | ||
| action: block | ||
| bypass_marker: ds-deep-ui-import |
There was a problem hiding this comment.
Deep imports with sub-paths are not caught
The character class [a-zA-Z][a-zA-Z0-9_-]+ contains no /, so an import like from "@/components/ui/button/index" or from "@/components/ui/date-picker/utils" won't fire the rule. In practice these multi-segment paths are rare, but a comment noting the limitation would help future maintainers.
Prompt To Fix With AI
This is a comment left during a code review.
Path: hooks/rules/rules.yaml
Line: 489-491
Comment:
**Deep imports with sub-paths are not caught**
The character class `[a-zA-Z][a-zA-Z0-9_-]+` contains no `/`, so an import like `from "@/components/ui/button/index"` or `from "@/components/ui/date-picker/utils"` won't fire the rule. In practice these multi-segment paths are rare, but a comment noting the limitation would help future maintainers.
How can I resolve this? If you propose a fix, please make it concise.… hex warn Greptile review on PR #14 caught three real issues: 1. ds-arbitrary-fixed-width-in-ds-component used a ^-anchored file_path pattern that never matched at runtime — Claude Code passes absolute paths like /home/user/repo/src/components/ui/foo.tsx, but ^src/ only anchors to start-of-string. All 56 tests passed because fixtures used relative paths; the rule was silently inert in production. Switch to (^|/)src/components/ui/ so both relative and absolute paths trigger it. Add a regression test covering an absolute fixture path. 2. ds-raw-hex-color-in-source description started with "Block" while action is warn — confused both maintainers reading the manifest and end-users seeing the runtime stderr. Description now says "Warn". 3. The design-system/ and design-tokens not_pattern exemptions had no test coverage. Added two fixtures so a future regex narrow-down that broke them would fail CI. Tests: 59/59 passing (was 56/56; added 3 regression cases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Addressed all 3 substantive Greptile findings in 26a92e2: 1. 2. Description/action mismatch 3. Test coverage for Tests: 56/56 → 59/59 passing. Issue 4 (deep-import multi-segment limitation) was a comment-only suggestion; given the rule only blocks the most common single-segment case and we have the bypass marker for genuine exceptions, leaving it as-is per its current scope. @greptile review |
GitHub Actions account is locked due to a billing issue, blocking every ubuntu-latest job from starting. Same pattern dojo-os already adopted: swap to blacksmith-2vcpu-ubuntu-2204 (Blacksmith managed runners) so the test workflow can actually run. Drop-in replacement — Blacksmith runners are API-compatible with the GitHub-hosted Ubuntu image, and the workflow steps (npm ci, build-rules, test-hooks.sh) don't depend on anything GitHub-specific. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Adds the 4th enforcement layer for the Dojo Design System: AI write-time PreToolUse rules that fire on
Edit | Write | MultiEditand block (or warn) before the file is touched if the LLM proposes a DS violation. Complements dojo-os's existinguseDsRulesruntime hook,ds-lintpre-commit hook, andvalidate-storage-uploadEdge Function.4 new rules added to
hooks/rules/rules.yaml:ds-arbitrary-breakpoint— blockmin-[Xpx]arbitrary breakpoints in TSX/JSX/TS, forcesm:/md:/lg:/xl:/2xl:presetsds-deep-ui-import— block deep imports from@/components/ui/<file>, force barrel importsds-arbitrary-fixed-width-in-ds-component— blockw-[Xpx]insidesrc/components/ui/(DS components must be flexible —w-full + max-w-[Xpx]); app-level usage exemptds-raw-hex-color-in-source— warn on raw hex#RRGGBBin TSX/TS source; exempttailwind.config.*,theme/,design-system/,design-tokens,.test.*,.stories.*Each rule has 5 tests (20 new total). All rules ship with bypass markers (
// hook-bypass: <id>) for the rare cases where a violation is the right call.Why
The existing 3 layers all fire post-write. This 4th layer fires pre-write — tightest feedback loop, educates the LLM in real time, prevents the violation from ever landing on disk.
Implementation
pre-edit.shdispatches Edit/Write/MultiEdit;parse-input.shextractsfile_path+content;eval-rule.shevaluates the rule.(^|[^a-zA-Z-])w-\\[[0-9]+px\\]so legitimate composites likemax-w-[200px]andmin-w-[120px]don't false-positive.npm run test-hooks(56/56 pass — 36 existing + 20 new) and via manual smoke tests piping fixtures intoeval-rule.shdirectly.Out of scope (deferred)
h-7/h-8on interactives) — needs context-aware AST parsing, not regexTest plan
npm run build-rulespasses (14 rules generated, no schema errors)npm run test-hookspasses (56/56)git diff main..HEADtouches onlyhooks/rules/rules.yaml,hooks/rules/rules.json,hooks/rules/README.mdCloses DOJ-3924 (Phase G of DOJ-3439 design-system-linter).
@greptile review
Created by Claude Code on behalf of @andres-dojocoding
🤖 Generated with Claude Code