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:lineand 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, orvite build. Userg/grep/globonly. - No network: never
curl,fetch, or start servers. - Fix-forward: when
fixmode 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
# 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. <Field> labels in the citizen app MUST use <Bilingual>, never t().
# P0: Field labels using t() instead of <Bilingual>
rg 'label=\{t\(' <changed-citizen-tsx-files>
Expected: zero matches. Every match is a P0 finding.
Auto-fix (when fix mode):
label={t("section.key")} → label={<Bilingual tKey="section.key" />}
1.2: Hardcoded English Strings (P0)
User-visible text in citizen app JSX must go through i18n.
# P0: Hardcoded English in headings
rg '<h[1-6][^>]*>[A-Z][a-z]' <changed-citizen-tsx-files>
# P0: Hardcoded English in paragraphs and spans used as labels
rg '<(p|span)\s+className="[^"]*label[^"]*">[A-Z]' <changed-citizen-tsx-files>
# P1: Hardcoded English in button text (not using t())
rg '<button[^>]*>[A-Z][a-z]{2,}' <changed-citizen-tsx-files>
rg '<Button[^>]*>[A-Z][a-z]{2,}' <changed-citizen-tsx-files>
Exceptions (not findings):
- Code comments and JSDoc
classNamestring valuesconsole.log/console.errormessagesaria-labelvalues 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.
# Extract keys used in changed citizen TSX files
rg 'tKey="([^"]+)"' <changed-citizen-tsx-files> -o --no-filename | sort -u
rg "t\(['\"]([^'\"]+)['\"]" <changed-citizen-tsx-files> -o --no-filename | sort -u
# For each key, verify presence in all three locale files
rg '<key>' apps/citizen/src/locales/en.ts
rg '<key>' apps/citizen/src/locales/hi.ts
rg '<key>' 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.
# P3: camelCase keys (should be snake_case)
rg 'tKey="[a-z]+\.[a-z]+[A-Z]' <changed-citizen-tsx-files>
rg "t\(['\"][a-z]+\.[a-z]+[A-Z]" <changed-citizen-tsx-files>
Phase 2: CSS & Responsive Compliance
Applies to: all changed CSS files + inline styles in TSX files
2.1: Viewport Height (P0)
# P0: 100vh (must use dvh)
rg '100vh' <changed-css-files> <changed-tsx-files>
Auto-fix: 100vh → 100dvh
2.2: Pixel Breakpoints (P0)
# P0: px values in @media queries (must use rem)
rg '@media[^{]*\d+px' <changed-css-files>
Verify matches are actually breakpoint values (width/height), not properties inside the media block.
2.3: Ad-hoc Breakpoints (P1)
# 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' <changed-css-files>
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)
# P1: Inline styles with hardcoded spacing (should use CSS tokens)
rg 'style=\{\{[^}]*(padding|margin|gap):\s*"?\d+(px|rem)' <changed-tsx-files>
Exception: style={{ gap: "var(--space-4)" }} (using CSS variable in inline style) is acceptable.
2.5: Fixed Widths (P1)
# P1: Fixed pixel widths that will overflow on mobile
rg 'width:\s*\d{3,}px' <changed-css-files> <changed-tsx-files>
Widths >= 100px hardcoded in pixels are suspect. Should use min(), max-width, or percentage/rem values.
2.6: Hover Without Active (P1)
# P1: :hover styles without corresponding :active
rg ':hover' <changed-css-files> -l
For each file with :hover, verify a corresponding :active rule exists for the same selector.
2.7: Hardcoded Colors (P2)
# P2: Hardcoded hex/rgb colors instead of CSS custom properties
rg '(color|background|border).*#[0-9a-fA-F]{3,8}' <changed-css-files>
rg '(color|background|border).*rgb\(' <changed-css-files>
Exceptions: Inside CSS custom property definitions (:root { --color-x: #abc; }) and SVG fill/stroke attributes.
2.8: Standalone vw Units (P2)
# P2: vw not inside clamp() — causes horizontal scroll
rg '\d+vw' <changed-css-files> <changed-tsx-files>
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)
# P1: Small interactive elements without min-height
rg '<button\b' <changed-tsx-files> -l
rg 'onClick=\{' <changed-tsx-files> -l
For each file, verify that clickable elements have CSS rules with min-height: 2.75rem or use the shared <Button> component.
3.2: Missing aria-label on Icon Buttons (P1)
# P1: Buttons with only SVG/icon content and no aria-label
rg '<button[^>]*>\s*<svg' <changed-tsx-files>
rg '<Button[^>]*>\s*<svg' <changed-tsx-files>
3.3: div-as-button Anti-Pattern (P1)
# P1: div/span with onClick but no role="button" or tabIndex
rg '<(div|span)[^>]*onClick=' <changed-tsx-files>
For each match, verify role="button" and tabIndex={0} are present. Better fix: convert to <button>.
3.4: Missing aria-expanded on Toggles (P2)
# P2: Toggle patterns without aria-expanded
rg '(setIsOpen|setExpanded|setShow|toggle)' <changed-tsx-files> -l
3.5: Images Without alt (P2)
# P2: img tags without alt attribute
rg '<img\b(?![^>]*\balt=)' <changed-tsx-files>
3.6: Safe-Area Insets on Fixed/Sticky Elements (P1)
# P1: position fixed/sticky without safe-area-inset
rg 'position:\s*(fixed|sticky)' <changed-css-files> -l
For each fixed/sticky rule, verify env(safe-area-inset-*) or max(..., env(...)) is present.
Phase 4: Security Quick Scan
Applies to: all changed TS/TSX files
4.1: SQL Injection (P0)
# P0: String interpolation in SQL queries
rg 'query\s*\(\s*`' <changed-ts-files>
rg "query\s*\(\s*['\"].*\\\$\{" <changed-ts-files>
rg 'query\s*\(.*\+\s*(req|request|body|params|query)' <changed-ts-files>
Must use parameterized queries ($1, $2).
4.2: Hardcoded Secrets (P0)
# P0: Secrets/keys/passwords in source code
rg '(password|secret|api_key|apiKey|token)\s*[:=]\s*["\x27][^"\x27]{8,}' <changed-ts-files> --glob '!*.test.*' --glob '!*.spec.*' --glob '!locales/*'
False positive filters:
- Type definitions (
password: string) - Variable names without values (
const secret = process.env.SECRET) - Test fixtures in
*.test.*files - Locale string values containing "password" as a label
4.3: Console.log in Production Code (P2)
# P2: console.log left in production code
rg 'console\.(log|debug|info)' <changed-ts-files> --glob '!*.test.*' --glob '!*.spec.*'
4.4: Secrets in Config Files (P0)
# P0: Secrets in committed config files
rg '(password|secret|key|token)\s*[:=]\s*["\x27][A-Za-z0-9+/=]{16,}' <changed-config-files> --glob '!package-lock.json' --glob '!*.example'
4.5: Raw reply.send for Errors (P2)
# P2: Raw reply.send with error objects instead of sendError()
rg 'reply\.(code|status)\(\d+\)\.send\(\{' <changed-api-files>
rg 'reply\.send\(\{\s*error' <changed-api-files>
Should use sendError(), send400(), send401(), etc. from the errors module.
Phase 5: TypeScript Hygiene
Applies to: all changed TS/TSX files
5.1: New any Types (P2)
# P2: any type annotations in new/changed code
git diff HEAD -- <file> | rg '^\+.*:\s*any\b'
git diff HEAD -- <file> | rg '^\+.*as\s+any\b'
5.2: Missing Type on Button Elements (P1)
# P1: Raw <button> without explicit type attribute
rg '<button\b(?![^>]*\btype=)' <changed-tsx-files>
5.3: Import Path Conventions (P3)
# P3: Direct relative imports from packages instead of workspace aliases
rg "from ['\"]\.\.\/\.\.\/packages\/" <changed-ts-files>
# P3: Importing from @puda/shared root instead of sub-path
rg "from ['\"]@puda/shared['\"]" <changed-ts-files> -A 1
Phase 6: API Pattern Compliance
Applies to: changed API and frontend files
6.1: Missing Credentials Include (P0)
# P0: fetch() calls without credentials: "include" (breaks cookie auth)
rg 'fetch\(' <changed-citizen-tsx-files> <changed-officer-tsx-files> -A 5
For each fetch() call, verify credentials: "include" is present either in the options object or via a shared authHeaders() call.
Exception: Fetches to external URLs may intentionally omit credentials.
6.2: Missing Auth Headers (P1)
# P1: fetch without authHeaders() in authenticated components
rg 'fetch\(' <changed-citizen-tsx-files> -B 5 -A 10
6.3: Missing Offline Guard on Mutations (P1)
# P1: Form submit or mutation button without isOffline check
rg '(onSubmit|handleSubmit|handleDelete|handleUpdate|handleSave)' <changed-tsx-files> -l
For each mutation handler, verify:
- The component receives
isOfflineprop or uses an offline hook - The submit/mutation button has
disabled={isOffline}or equivalent guard - An offline banner is shown when offline
Phase 7: Component Pattern Compliance
Applies to: changed TSX files
7.1: Missing useCallback on Fetch Functions (P2)
# P2: Functions used in useEffect deps without useCallback
rg 'useEffect\(\s*\(\)\s*=>\s*\{[^}]*\b(fetch|load|get)[A-Z]' <changed-tsx-files>
7.2: Missing Error State UI (P2)
# P2: Components with fetch but no error state rendering
rg 'catch\s*\(' <changed-tsx-files> -l
7.3: Skeleton vs Spinner Loading (P3)
# P3: Spinner loading instead of skeleton
rg '(Loading\.\.\.|spinner|Spinner|CircularProgress)' <changed-tsx-files>
Project convention is skeleton blocks for loading states, not spinners.
7.4: Missing Cache Write After Fetch (P3)
# P3: fetch without corresponding writeCached
rg 'fetch\(' <changed-citizen-tsx-files> -l
For citizen app components that fetch API data, verify writeCached() is called with the response data.
Phase 8: Report
8.1: Finding Summary
VIBE-CODING GUARDRAILS REPORT
==============================
Scope: <files scanned / git diff range>
Files scanned: <count by bucket>
Scan time: <seconds>
FINDINGS:
P0 (Blocker): <count>
P1 (High): <count>
P2 (Medium): <count>
P3 (Low): <count>
Fixed (auto): <count>
VERDICT: <CLEAN | WARN | BLOCKED>
8.2: Findings Detail Table
| # | Sev | Phase | File:Line | Finding | Status |
|---|-----|-------|-----------|---------|--------|
| 1 | P0 | 1.1 | App.tsx:42 | `label={t("login.id")}` — must use `<Bilingual>` | OPEN / FIXED |
8.3: Auto-Fix Summary (if fix mode)
| # | File:Line | Before | After |
|---|-----------|--------|-------|
| 1 | App.tsx:42 | `label={t("login.id")}` | `label={<Bilingual tKey="login.id" />}` |
8.4: Next Steps
Based on verdict:
- CLEAN: "No guardrail violations. Ready for commit."
- WARN: "P1 findings should be addressed before merge. Use
/vibe-coding-guardrails fixto auto-fix where possible." - BLOCKED: "P0 violations must be resolved. These will break compliance or cause runtime failures."
Common Fix Patterns
| Finding | Before | After | Auto-fixable? |
|---------|--------|-------|---------------|
| Field label anti-pattern | label={t("k")} | label={<Bilingual tKey="k" />} | Yes |
| 100vh | height: 100vh | height: 100dvh | Yes |
| px in breakpoint | @media (max-width: 768px) | @media (max-width: 48rem) | Yes (if standard value) |
| Missing button type | <button onClick={fn}> | <button type="button" onClick={fn}> | Yes |
| console.log | console.log("debug") | (remove line) | Yes |
| Inline px spacing | style={{ padding: "16px" }} | style={{ padding: "var(--space-4)" }} | No (ambiguous token) |
| div onClick | <div onClick={fn}> | <button type="button" onClick={fn}> | No (needs class migration) |
| Missing credentials | fetch(url) | fetch(url, { credentials: "include" }) | No (need to verify context) |
| Hardcoded English | <h2>My Apps</h2> | <h2><Bilingual tKey="nav.my_apps" /></h2> | No (need to create keys) |
| Missing locale key | Key in en.ts only | Add to hi.ts + pa.ts | No (need translation) |
Expert Next.js App Router
Developpement
Un skill qui transforme Claude en expert Next.js App Router.
Générateur de README
Developpement
Crée des README.md professionnels et complets pour vos projets.
Rédacteur de Documentation API
Developpement
Génère de la documentation API complète au format OpenAPI/Swagger.