Baseline: enforce team decisions AI keeps ignoring
AI coding tools are fast. They're also confidently wrong about your team's conventions.
You migrate off moment.js. Cursor adds it back. You build a repository layer. Copilot writes db.select() directly in page.tsx. You ship a design system with a <Button> component. Claude writes a custom <button> with inline Tailwind. You ban axios. An AI assistant adds it to package.json.
They generate from training data, not from your architecture. And the legacy patterns in your codebase are the ones they see most — so they reinforce them.
I kept seeing this across teams and decided to build something to fix it.
Baseline
Baseline is a Rust-based CLI that enforces team decisions in CI. You define rules in a single TOML file, and it catches violations before they land.
npx code-baseline scanIt covers a class of rules that ESLint can't express.
Banning patterns where they don't belong
AI loves bypassing your abstractions. Scope bans to specific paths:
[[rule]]
id = "no-db-in-pages"
type = "banned-pattern"
pattern = "db."
glob = "app/**/page.tsx"
severity = "error"
message = "Use the repository layer, not direct DB access in pages"
suggest = "Import from @/lib/repositories instead"Banning imports and dependencies
Deprecated packages are one autocomplete away from returning:
[[rule]]
id = "no-moment"
type = "banned-import"
severity = "error"
packages = ["moment", "moment-timezone"]
message = "moment.js is deprecated — use date-fns or Temporal API"
[[rule]]
id = "no-request"
type = "banned-dependency"
severity = "error"
packages = ["request", "request-promise", "axios"]
message = "Use native fetch or undici"The banned-dependency rule parses package.json directly — it catches packages added as dependencies even if no source file imports them yet.
Ratcheting legacy code to zero
This is the feature I'm most proud of. Say you have 200 calls to legacyFetch() and want to migrate to apiFetch():
[[rule]]
id = "ratchet-legacy-fetch"
type = "ratchet"
severity = "error"
pattern = "legacyFetch("
max_count = 200
glob = "src/**/*.ts"
message = "Migrate to apiFetch"Set the ceiling at 200. Next sprint, migrate some, lower it to 180. The number only goes down. Any PR that adds new legacy calls fails CI.
ESLint is pass/fail. It can't count occurrences across your codebase and enforce a decreasing ceiling over time.
Tailwind + shadcn enforcement
If you use Tailwind with shadcn/ui, AI will write bg-white and text-gray-900 everywhere. Your design system says bg-background and text-foreground. Dark mode breaks silently.
Baseline ships two rules for this:
Dark mode enforcement flags color classes missing a dark: variant:
7:21 error Missing dark: variant for color class: 'bg-white'
│ <div className="bg-white border border-gray-200 rounded-lg">
→ Use 'bg-background' instead — it adapts to light/dark automatically
Semantic token enforcement bans raw color classes entirely and suggests your tokens. Ships with 130+ default mappings.
Both rules understand className, class, cn(), clsx(), cva(), and twMerge().
Proximity rules
Enforce that related patterns appear near each other:
[[rule]]
id = "org-scoped-deletes"
type = "window-pattern"
severity = "error"
pattern = "DELETE FROM"
condition_pattern = "organizationId"
max_count = 80
glob = "src/**/*.ts"
message = "DELETE queries must include organizationId within 80 lines"Presets
Get started in one line instead of writing rules from scratch:
[baseline]
extends = ["ai-codegen", "security", "nextjs"]| Preset | What it catches |
|---|---|
ai-codegen | as any, empty catch, console.log, TODO, placeholder text, var, require in TS, and more (12 rules) |
security | Hardcoded secrets, eval, dangerouslySetInnerHTML, .env files, http:// URLs (10 rules) |
nextjs | Use next/image, next/link, next/font; no next/head or next/router in App Router (8 rules) |
shadcn-strict | Dark mode enforcement, theme tokens, no inline styles, no CSS-in-JS (5 rules) |
What the output looks like
src/utils/helpers.ts
1:0 error moment.js is deprecated — use date-fns no-moment
│ import moment from 'moment';
→ import { format } from 'date-fns'
src/components/BadCard.tsx
7:21 error Missing dark: variant for 'bg-white' enforce-dark-mode
│ <div className="bg-white border border-gray-200 rounded-lg">
→ Use 'bg-background' instead
✗ 9 violations (7 error, 2 warning)
CI integration
Ships a GitHub Action that annotates violations inline on PR diffs:
- uses: stewartjarod/baseline@main
with:
paths: 'src'On PRs it automatically scans only changed files. Outputs GitHub annotations, SARIF for Code Scanning, and markdown summaries.
The technical bits
- Written in Rust. Single binary, no Node runtime.
- Tree-sitter for AST-aware rules (component size, nested components, useState/useEffect analysis)
- Parallel scanning via rayon
- Respects .gitignore automatically
- Also runs as an MCP server so AI tools can query your rules directly
- Available on crates.io and npm
Install
# Run instantly
npx code-baseline scan
# Or install
npm install -g code-baseline
cargo install code-baselineLinters catch syntax. Formatters fix whitespace. Baseline enforces the decisions your team has already made — especially the ones AI keeps ignoring.
MIT licensed. Feedback and contributions welcome.
GitHub: stewartjarod/baseline