--- name: vibe-coding-guardrails description: Fast post-coding scan that catches common anti-patterns introduced during rapid AI-assisted coding sessions. Runs pattern-matching checks against project conventions before code leaves the working tree. argument-hint: "[scope] [options]" user_invocable: true --- # Vibe-Coding Guardrails Fast, automated convention-compliance scan for changes in the working tree. Catches the mistakes that AI coding assistants commonly introduce — wrong i18n patterns, CSS violations, accessibility gaps, security anti-patterns, and project-specific convention breaks — before they reach review or deployment. **Design goal**: Complete in under 60 seconds. No builds, no tests, no servers — pure static pattern matching on changed files. ## Scoping Parse the user's arguments: - **No arguments**: scan all uncommitted changes (`git diff --name-only HEAD` + untracked files). - **App target**: `/vibe-coding-guardrails apps/citizen` — restrict scan to files under that directory. - **File list**: `/vibe-coding-guardrails src/Dashboard.tsx src/Login.tsx` — scan specific files only. - **`--staged`**: scan only staged files (`git diff --cached --name-only`). - **`--all`**: scan entire codebase, not just changed files (slower but thorough). Options: - `no-fix` — report findings only, do not apply fixes. - `fix` — auto-fix trivially fixable violations (default: report only). - `strict` — treat P2 findings as blockers (default: only P0/P1 block). ## Operating Rules - **Speed over depth**: this is a fast guardrail, not a full review. Prefer false negatives over slow execution. - **Evidence-first**: every finding must cite `file:line` and the matched text. - **Changed-files focus**: by default, only scan files that have uncommitted changes. This keeps the scan fast and relevant. - **No builds**: never run `npm run build`, `tsc`, or `vite build`. Use `rg`/`grep`/`glob` only. - **No network**: never `curl`, `fetch`, or start servers. - **Fix-forward**: when `fix` mode is active, apply the simplest correct fix. If the fix is ambiguous, report instead of guessing. - **One pass**: do not re-scan after fixes. Report what was found and what was fixed in a single pass. - **Deduplication**: if the same pattern appears N times in one file, report once with count, not N separate findings. ## Severity Definitions | Level | Meaning | Examples | |-------|---------|---------| | **P0** | Breaks compliance or causes runtime failure | `label={t()}` in citizen app, SQL string interpolation, hardcoded secret | | **P1** | Functional/UX problem on target devices | `100vh`, missing `credentials: "include"`, hover-only interaction | | **P2** | Convention violation, code quality issue | Hardcoded spacing, `any` type on new code, missing `aria-expanded` | | **P3** | Style/consistency nit | Key naming convention, import path preference | ## Verdict | Verdict | Condition | |---------|-----------| | **CLEAN** | Zero P0/P1 findings | | **WARN** | Zero P0, some P1 findings | | **BLOCKED** | Any P0 finding remains unfixed | --- ## Phase 0: Preflight — Identify Changed Files ```bash # Determine file set based on scope git diff --name-only HEAD git diff --cached --name-only git ls-files --others --exclude-standard ``` Classify changed files into buckets: | Bucket | Pattern | Checks Applied | |--------|---------|----------------| | **citizen-tsx** | `apps/citizen/src/*.tsx` | Phases 1-7 (all) | | **citizen-css** | `apps/citizen/src/*.css` | Phase 2 | | **citizen-ts** | `apps/citizen/src/*.ts` (non-tsx) | Phases 1, 4, 5 | | **officer-tsx** | `apps/officer/src/*.tsx` | Phases 2-7 (skip bilingual Field check) | | **officer-css** | `apps/officer/src/*.css` | Phase 2 | | **api-ts** | `apps/api/src/*.ts` | Phases 4, 5, 6 | | **package-ts** | `packages/*/src/*.ts` | Phases 4, 5 | | **locale** | `*/locales/*.ts` | Phase 1.3 | | **css** | `*.css` | Phase 2 | | **config** | `*.json`, `*.yaml`, `Dockerfile*` | Phase 4.4 | | **other** | Everything else | Skip | If no changed files match any bucket, report "No scannable changes found" and exit CLEAN. Record the file count per bucket for the report header. --- ## Phase 1: Internationalization Compliance **Applies to**: citizen-tsx, citizen-ts, locale files ### 1.1: Field Label Anti-Pattern (P0) The #1 bilingual compliance violation. `` labels in the citizen app MUST use ``, never `t()`. ```bash # P0: Field labels using t() instead of rg 'label=\{t\(' ``` **Expected**: zero matches. Every match is a P0 finding. **Auto-fix** (when `fix` mode): ``` label={t("section.key")} → label={} ``` ### 1.2: Hardcoded English Strings (P0) User-visible text in citizen app JSX must go through i18n. ```bash # P0: Hardcoded English in headings rg ']*>[A-Z][a-z]' # P0: Hardcoded English in paragraphs and spans used as labels rg '<(p|span)\s+className="[^"]*label[^"]*">[A-Z]' # P1: Hardcoded English in button text (not using t()) rg ']*>[A-Z][a-z]{2,}' rg ']*>[A-Z][a-z]{2,}' ``` **Exceptions** (not findings): - Code comments and JSDoc - `className` string values - `console.log` / `console.error` messages - `aria-label` values that are English-only by design (screen readers) - Test files (`*.test.tsx`, `*.spec.tsx`) ### 1.3: Missing Locale Keys (P0) Every i18n key used in citizen app must exist in all three locale files. ```bash # Extract keys used in changed citizen TSX files rg 'tKey="([^"]+)"' -o --no-filename | sort -u rg "t\(['\"]([^'\"]+)['\"]" -o --no-filename | sort -u # For each key, verify presence in all three locale files rg '' apps/citizen/src/locales/en.ts rg '' apps/citizen/src/locales/hi.ts rg '' apps/citizen/src/locales/pa.ts ``` If a key exists in `en.ts` but is missing from `hi.ts` or `pa.ts`, that is a P0 finding. If locale files themselves were changed, cross-check that all three files have the same set of top-level keys. ### 1.4: Key Naming Convention (P3) Keys should follow `section.descriptor` pattern with snake_case descriptors. ```bash # P3: camelCase keys (should be snake_case) rg 'tKey="[a-z]+\.[a-z]+[A-Z]' rg "t\(['\"][a-z]+\.[a-z]+[A-Z]" ``` --- ## Phase 2: CSS & Responsive Compliance **Applies to**: all changed CSS files + inline styles in TSX files ### 2.1: Viewport Height (P0) ```bash # P0: 100vh (must use dvh) rg '100vh' ``` **Auto-fix**: `100vh` → `100dvh` ### 2.2: Pixel Breakpoints (P0) ```bash # P0: px values in @media queries (must use rem) rg '@media[^{]*\d+px' ``` Verify matches are actually breakpoint values (width/height), not properties inside the media block. ### 2.3: Ad-hoc Breakpoints (P1) ```bash # P1: Breakpoints not matching the three defined tokens # Valid values: 22.5rem (360px), 48rem (768px), 80rem (1280px) rg '@media[^{]*(min-width|max-width):\s*\d' ``` For each match, verify the value is one of `22.5rem`, `48rem`, or `80rem`. Any other value is a P1 finding. ### 2.4: Hardcoded Spacing in Inline Styles (P1) ```bash # P1: Inline styles with hardcoded spacing (should use CSS tokens) rg 'style=\{\{[^}]*(padding|margin|gap):\s*"?\d+(px|rem)' ``` **Exception**: `style={{ gap: "var(--space-4)" }}` (using CSS variable in inline style) is acceptable. ### 2.5: Fixed Widths (P1) ```bash # P1: Fixed pixel widths that will overflow on mobile rg 'width:\s*\d{3,}px' ``` Widths >= 100px hardcoded in pixels are suspect. Should use `min()`, `max-width`, or percentage/rem values. ### 2.6: Hover Without Active (P1) ```bash # P1: :hover styles without corresponding :active rg ':hover' -l ``` For each file with `:hover`, verify a corresponding `:active` rule exists for the same selector. ### 2.7: Hardcoded Colors (P2) ```bash # P2: Hardcoded hex/rgb colors instead of CSS custom properties rg '(color|background|border).*#[0-9a-fA-F]{3,8}' rg '(color|background|border).*rgb\(' ``` **Exceptions**: Inside CSS custom property definitions (`:root { --color-x: #abc; }`) and SVG `fill`/`stroke` attributes. ### 2.8: Standalone vw Units (P2) ```bash # P2: vw not inside clamp() — causes horizontal scroll rg '\d+vw' ``` `vw` is acceptable inside `clamp()`. Outside `clamp()`, it is a P2 finding. --- ## Phase 3: Accessibility Compliance **Applies to**: changed TSX files (citizen + officer) ### 3.1: Touch Targets (P1) ```bash # P1: Small interactive elements without min-height rg ' -l rg 'onClick=\{' -l ``` For each file, verify that clickable elements have CSS rules with `min-height: 2.75rem` or use the shared `