- Rust 89.3%
- Python 6.1%
- HTML 3.4%
- Shell 1%
- CSS 0.1%
Code Gourmand 🍷
Keep the kitchen clean and everything in good taste.
A general-purpose AI slop detection tool for maintaining code quality. Designed to catch low-quality patterns commonly introduced by AI code generation and integrate seamlessly into agentic workflows.
Supports Python, Rust, JavaScript, and TypeScript codebases.
Of course, this won't guarantee good code, or prevent AI slop. For that, you need a "grown-up project" with proper code review, and (at least for the foreseeable future) expert humans deeply in the loop. But, it will catch some repeated frustrating and annoying AI slop patterns and therefore should make things at least somewhat better.
Based on one person's experience so far — it would greatly benefit from being tried on other people's vibe code. Feedback and contributions welcome!
What is "AI Slop"?
AI-generated code often contains patterns that technically work but reduce code quality:
- Hyperbolic comments ("a promotional adjective", "a marketing buzzword")
- Dead code markers ("deprecated", "no longer used")
- Deferred work markers ("TODO", "FIXME", "hack", "for now")
- Test excuses ("known issue", "will be fixed later")
- Lint suppressions without fixing underlying issues
- Copy-paste patterns instead of proper abstraction
- Generic variable names (
temp,data,x) - Hardcoded values that should be configurable
Code Gourmand detects these patterns so they can be caught as they're created, keeping your codebase clean.
Installation
cargo install --path crates/gourmand/
Note: System configuration files (config/ directory) are embedded into the binary at build time using rust-embed. The release binary is completely self-contained - it includes all system defaults and doesn't require external config files to run.
Quick Start
For a guided, end-to-end setup path — including pre-commit wiring and per-language linter
templates — load the gourmand-setup skill (gourmand init --skills installs it). The rest
of this section covers the core commands.
First-Time Setup
# Initialize gourmand on your existing codebase
gourmand init
# This will:
# • Scan your entire codebase
# • Create baseline exceptions for existing violations
# • Let you adopt gourmand without fixing everything first
# Optional: generate a gourmand.toml to customize which checks run
gourmand init --generate-config
# Optional: if you disable any checks in gourmand.toml, add required override entries
gourmand init --add-check-overrides
# Optional: install AI agent skills into .agents/skills/
gourmand init --skills
# Use --skills-dir to write to a different location (e.g. .claude/skills)
gourmand init --skills --skills-dir .claude/skills
After initialization, gourmand baselines all existing violations and adds .gourmand-cache/ to .gitignore to enable caching. For projects with 12 or fewer violations, exceptions go into gourmand-exceptions.toml. For projects with more than 12 violations, exceptions are written as individual files into .gourmand-exceptions.d/ instead. You can then:
- Review the exceptions - Check for false positives
- Commit the baseline -
git add gourmand-exceptions.toml .gitignore && git commit -m "chore: add gourmand baseline"(orgit add .gourmand-exceptions.d/ .gitignoreif the.d/format was used) - Fix incrementally - Remove exceptions as you fix violations
- New code is checked automatically - Staged changes are checked on commit
Daily Workflow
# Make changes and stage them
git add src/new_feature.rs
# Gourmand auto-detects staged changes
gourmand check # Checks only your staged changes
# Or force a full scan
gourmand check --full # Checks entire codebase
Advanced Usage
# Run all checks (current directory)
gourmand check
# Check specific directory
gourmand check /path/to/project
# Run specific check
gourmand check --filter hyperbolic_language
# One-line output (for test runners)
gourmand check --display one-line
# Fail fast on first error
gourmand check --fail-fast
# Force check even if directory doesn't look like a project
gourmand check /some/directory --force
# List all available checks (with scope labels)
gourmand list
# Print the README or CHANGELOG
gourmand readme
gourmand changelog
# Summary mode - show only violation counts
gourmand check --display summary
# Show worst files first
gourmand check --display worst-files
# Use custom config location
gourmand --config /path/to/gourmand.toml check
# Check specific diff range
gourmand check --diff-source branch:main..feature # PR review
gourmand check --diff-source file:changes.diff # From diff file
Check Scope Labels
Each check carries a scope label visible in gourmand list. The three values are:
general— applies meaningfully across all supported languages and project types; useful as a baseline for any codebase.ecosystem_specific— targets patterns specific to one language, framework, or runtime (Rust, Python, TypeScript/Next.js, etc.); enable selectively based on your stack.self_hosting_only— fires only on Gourmand's own internal APIs; not useful for external projects (reserved for future use).
Run gourmand list to see which scope each check belongs to; ecosystem-specific checks are tagged [ecosystem] in text output and carry a scope field in JSON output.
AI Slop Scoring
gourmand score computes a weighted 0–10 density score across 7 named categories and displays an emoji bar chart:
# Score the whole project
gourmand score
# Score only the files changed in a diff
gourmand score --diff-source staged
gourmand score --diff-source branch:main..HEAD
# Show how the current patch changes the score (before vs. after)
gourmand score --diff-source staged --delta
# Machine-readable output for CI
gourmand score --format json
# Score ignoring all project-local config (system defaults only)
gourmand score --raw-score
The 7 categories (Hollow Testing 🎭, Active Evasion 🙈, Error Concealment 🤫, AI Prose Artifacts 🦜, Unfinished Scaffolding 🏗️, Structural Shortcuts ✂️, Code Hygiene 🪱) are weighted by AI-specificity. Scores are violation densities per KLOC and will shift as checks are added or reweighted — treat them as directional indicators, not stable benchmarks.
Scores and Human Code
The patterns Gourmand looks for are not exclusive to AI-generated code — AI learned them from human code in the first place. Overly verbose comments, speculative generality, deferred work markers, and weak error handling all appear in human-written code too. What changes is the intensity: AI coding agents tend to lean heavily into these patterns, and some patterns are qualitatively worse when an agent does them (an agent that adds scaffolding "for future use" is likely to leave it there; a human might too, but usually has more context for whether it was ever needed).
A codebase that scores high on some Gourmand metrics is not necessarily AI-generated. A codebase that scores low is not necessarily clean. The score is a directional indicator of specific quality patterns, not a vibe-coding detector.
When by_design or gourmand_bug exceptions exclude violations from a category during scoring, that category's row is marked with 🔖 and a footnote explains that violations were suppressed. A 🔖 means "could be higher without these exceptions." Similarly, if any check in a category is entirely disabled in config, that row is marked with 🔕 — a disabled check contributes 0 violations regardless of what it would have found, so the score may be lower than reality.
Incremental Mode (Auto-detect)
Gourmand automatically detects when you have staged changes and checks only the changed lines, making it perfect for pre-commit hooks.
How it works:
- Git repository -
.gitdirectory exists - Initialized project -
gourmand-exceptions.tomlexists (if not, runs a full scan and hints to rungourmand init) - Checkpoint (optional) - If set, checks all changes since checkpoint
- Staged changes - Otherwise, checks only staged changes
- Auto-magic - Gourmand checks only changed lines
One-shot scans: Running
gourmand checkon an uninitialized repo (or any external repo) automatically falls back to a full scan with a hint. No--fullflag required.
Manual control:
# Force full scan (ignore staged changes)
gourmand check --full
# Explicit diff source
gourmand check --diff-source staged # Same as auto-detect (no checkpoint)
gourmand check --diff-source branch:main..pr # PR review mode
# Checkpoint workflow (for --no-verify commits)
gourmand checkpoint # Mark current HEAD as clean
gourmand checkpoint --clear # Return to staged-only mode
See the Checkpoint Workflow section for handling --no-verify commits.
Baseline Workflow
After running gourmand init, your baseline is established. Here's how to manage it over time.
Initial Setup
-
Review the generated exceptions
- Check for false positives (file issues if found)
- Understand what violations exist in your codebase
- Prioritize which violations to fix first
-
Commit the baseline
# Monolithic format (≤12 violations): git add gourmand-exceptions.toml .gitignore && git commit -m "chore: add gourmand baseline" # Split format (>12 violations): git add .gourmand-exceptions.d/ .gitignore && git commit -m "chore: add gourmand baseline" -
Fix violations incrementally
- As you modify files, fix violations in them
- Remove corresponding exceptions from
gourmand-exceptions.toml - Commit fixes with removed exceptions together
Updating the Baseline
When you fix violations:
# Fix code in src/foo.rs
vim src/foo.rs
# Remove the exception from gourmand-exceptions.toml
vim gourmand-exceptions.toml # Delete the [[exceptions]] entry
# Commit together
git add src/foo.rs gourmand-exceptions.toml
git commit -m "fix: resolve TODO markers in foo.rs"
Re-baselining
If you want to regenerate the baseline (e.g., after major refactoring):
# Monolithic format:
rm gourmand-exceptions.toml
# Split format:
rm -rf .gourmand-exceptions.d/
# Then re-initialize:
gourmand init
This will create a fresh baseline with current violations.
Best Practices
- Boy scout rule: Fix violations as you touch code
- Minimize exceptions: Don't add new exceptions without strong justification
- Code review: Review exceptions file changes in pull requests
- Trend toward zero: Work incrementally toward zero exceptions
- Periodic audit: Run
gourmand auditperiodically to audit your exceptions file and disabled-check overrides for stale, over-broad, or dishonest entries. By default, entries with a fresh ledger record (signed off at a commit where the file hasn't changed since) are skipped — use--allto include them anyway for a full sweep. For an incremental one-by-one workflow, rungourmand audit --show-nextin a loop: it shows one entry that needs review, and exits 1 when all entries are current. After reviewing an entry, rungourmand audit --show-next --sign-offto record a commit-anchored sign-off ingourmand-audit-ledger.toml. Add--remove-deadto automatically remove dead entries as they are encountered. By default,--show-nextshows onlyby_designexceptions (highest scrutiny value); use--classification allto audit every classification, or--classification fix_plannedetc. to focus on a specific category. Load thegourmand-auditskill for the full process
Project Config Files are Optional: Gourmand works with just embedded system defaults. Create project config files only to override defaults:
# Minimal setup (or use no config files at all)
touch gourmand.toml gourmand-patterns.toml
touch gourmand-exceptions.toml gourmand-lectures.toml
Configuration
Code Gourmand uses a two-tier configuration system:
System Defaults (config/ subdirectory)
Standard rules shared across projects:
config/gourmand.toml- Default settingsconfig/gourmand-patterns.toml- Standard patternsconfig/gourmand-exceptions.toml- Standard exceptionsconfig/gourmand-lectures.toml- Educational lectures for AI agents
Project Overrides (project root)
Per-project customization:
gourmand.toml- Override settings (minimal); also holds[[check_overrides]]for any disabled checksgourmand-patterns.toml- Additional patterns (optional)gourmand-exceptions.toml- Project exceptions (appends to system); or use.gourmand-exceptions.d/for split format.gourmand-exceptions.d/- Split exceptions directory: one.tomlfile per entry, loaded alongsidegourmand-exceptions.tomlgourmand-lectures.toml- Lecture overrides (rarely needed)
Merge behavior:
- Settings: Project overrides system
- Patterns: Uses system patterns
- Exceptions: Project appends to system
- Lectures: Uses system lectures
Disabling Checks Project-Wide
Setting a check to false in [checks] is a project-wide suppression — stronger than any per-path exception, because it hides violations everywhere. Gourmand requires the same accountability: every disabled check needs a [[check_overrides]] entry with a justification and classification.
[checks]
verbose_comments = false
[[check_overrides]]
check = "verbose_comments"
justification = "This project's domain requires explanatory prose — comment density reflects documentation requirements, not narrative filler."
classification = "by_design" # Required: by_design | gourmand_bug | accepted_bad_taste | fix_planned
gourmand check and gourmand score error if any disabled check lacks a classified override entry. To generate skeleton entries for all disabled checks:
gourmand init --add-check-overrides
gourmand audit --show-next # classify each one
gourmand audit also audits these overrides — it shows what violations would appear if each disabled check were re-enabled. Use gourmand audit --show-next --sign-off to sign off after reviewing each one.
Educational Lectures
The gourmand-lectures.toml file contains multi-line explanations designed for AI coding agents.
Why lectures? AI agents reflexively try to evade linter warnings (adding #[allow(...)], # noqa, etc.) instead of fixing issues. The lectures:
- Explain WHY the pattern matters
- Show HOW to fix it properly
- Call out common EVASION TACTICS explicitly
- Reference actual observed AI behavior (like what happened in this very development session!)
Example lecture excerpt:
OBSERVED BEHAVIOR IN THIS SESSION:
1. Clippy reports issues
2. AI immediately adds #[allow(clippy::...)] everywhere
3. Human calls it out as "code slop"
4. AI removes suppressions and ACTUALLY FIXES issues
THAT PATTERN IS EXACTLY WHAT WE'RE PREVENTING.
See CONFIGURATION.md for details.
Creating Exceptions
When you have legitimate reasons to exempt specific files from checks, create a gourmand-exceptions.toml file in your project root.
⚠️ Use exceptions sparingly. Most violations indicate real problems. Fix the code, don't exempt it.
Exception Format
[[exceptions]]
check = "check_name"
path = "glob/pattern/**/*.rs"
justification = "Why this exception is necessary"
classification = "by_design" # Required: by_design | gourmand_bug | accepted_bad_taste | fix_planned
The classification field is required. Running gourmand check without it exits with an error listing every unclassified entry. See Configuration for what each value means.
Examples
# Benchmark output written automatically by the criterion harness
[[exceptions]]
check = "summary_litter"
path = "benchmarks/**/data/*.txt"
justification = "Criterion benchmark harness writes these files as cargo-bench output; they are not authored by the project"
classification = "by_design"
# API response fixtures — field names come from the external schema
[[exceptions]]
check = "generic_names"
path = "tests/legacy/**/*.py"
justification = "Fixtures are captured API responses; 'result' and 'data' field names are defined by the external API contract and cannot be renamed without breaking deserialization"
classification = "by_design"
# Unsafe isolation wrapper — the single-use boundary is the point
[[exceptions]]
check = "single_use_helpers"
path = "src/crypto/primitives.rs"
justification = "unsafe_add() wraps a single unsafe pointer operation to isolate it for audit; the boundary exists to contain unsafe, not as an extraction accident"
classification = "by_design"
Path Patterns
- Use glob patterns:
**for any directories,*for any filename - Patterns are relative to project root
- Examples:
tests/**/*.rs,benchmarks/*/data/*.txt,src/legacy.py
When to Use Exceptions
Legitimate reasons:
- Test fixture data files
- Legacy code with planned refactor
- Unsafe code isolation helpers
- Domain-specific necessary patterns
NOT legitimate reasons:
- "Too hard to fix right now"
- "The linter is wrong" (it usually isn't)
- "Just this one file" (symptom of larger issue)
- Evading checks without fixing problems
Remember: Every exception is tech debt. Minimize them.
Integration
Pre-commit Hook
Add to your .pre-commit-config.yaml:
repos:- repo:localhooks:- id:gourmandname:Code Gourmand - AI Slop Detectionentry:gourmand checklanguage:systempass_filenames:falsestages:[pre-commit]Then install:
pip install pre-commit
pre-commit install
Now Gourmand runs automatically on every commit!
How it works: Gourmand auto-detects staged changes and checks only the lines you modified, making pre-commit hooks fast and focused on your changes.
Using Gourmand on Itself
Gourmand uses itself in pre-commit. During development, before gourmand is installed system-wide, use:
repos:- repo:localhooks:- id:gourmandname:Code Gourmand (self-check)entry:cargo run --release -- checklanguage:systempass_filenames:falsestages:[pre-commit]This builds and runs the local version on every commit.
Checkpoint Workflow (for --no-verify commits)
When you use git commit --no-verify to skip pre-commit hooks, Gourmand won't check those changes. The next commit will only check changes since the last commit, missing the skipped verification entirely.
Solution: Use checkpoints to manually track verification points.
Setting a Checkpoint
Mark the current commit as a clean verification point:
gourmand checkpoint
# ✓ Checkpoint saved at a1b2c3d
How Checkpoints Work
Once set, Gourmand auto-detect mode checks all changes since the checkpoint, not just staged changes:
# Normal workflow
git add .
gourmand check # Auto-detects staged changes only
git commit -m "fix"
# With checkpoint
gourmand checkpoint # Mark current HEAD as clean
git commit --no-verify # Skip hooks for this commit
git commit --no-verify # And this one too
gourmand check # Checks ALL changes since checkpoint
git commit -m "final" # Now verified
gourmand checkpoint # Update checkpoint to current HEAD
Clearing Checkpoints
Return to normal staged-only mode:
gourmand checkpoint --clear
# ✓ Checkpoint cleared
Use Cases
WIP commits: Skip hooks during experimentation, verify everything before pushing:
gourmand checkpoint
git commit --no-verify -m "wip: trying approach A"
git commit --no-verify -m "wip: trying approach B"
gourmand check # Checks all changes since checkpoint
Batch verification: Multiple small commits, one verification:
gourmand checkpoint
# ... make several commits with --no-verify ...
gourmand check # Verify entire batch
gourmand checkpoint # Update checkpoint
Manual control: Explicitly control what "incremental" means:
# Check everything since last release
git tag v1.0.0
gourmand checkpoint
# ... development work ...
gourmand check # Check all changes since v1.0.0 tag
Checkpoint Internals
- Stored in
.gourmand-cache/checkpoint(automatically gitignored) - Contains git commit hash of checkpoint
- Auto-detect checks checkpoint first, then falls back to staged changes
- No checkpoint = normal staged-only mode
GitHub Actions
# .github/workflows/quality.ymlname:Code Qualityon:[push, pull_request]jobs:gourmand:runs-on:ubuntu-lateststeps:- uses:actions/checkout@v3- uses:actions-rs/toolchain@v1with:toolchain:stable- run:cargo install --path crates/gourmand/- run:gourmand check --fullExample Detections
Code Gourmand catches patterns like these:
# ❌ Hyperbolic language
# This implementation demonstrates the warning pattern.
# ❌ Deferred work
# TODO: implement this properly later
# ❌ Generic names
temp = []
data = load_stuff()
# ❌ Test excuses
# Known issue: this test fails sometimes
# Will be fixed later
# ❌ Lint suppression
return result # noqa: disable check
// ❌ Lint suppression without justification
#[allow(dead_code)]fn unused_function(){}// ❌ Skipped test
#[test]#[ignore]fn test_feature(){}// ❌ Magic numbers
lettimeout=42;// What does 42 mean?
// ❌ Hyperbolic comments
// This amazing solution handles edge cases
// ❌ Deferred work
// TODO: refactor for clarity
// ❌ Generic names
let data = fetch();
let temp = process(data);
// ❌ Lint suppression
// eslint-disable-next-line
const result = compute();
Each violation includes:
- Location (file:line)
- Explanation (why it matters)
- Fix suggestions (how to resolve it)
Google Apps Script Support
JavaScript support includes Google Apps Script projects. GAS files are treated as regular JavaScript - no special mode needed. Use exceptions if needed for framework-required patterns (global entry points, Property Service calls, etc.).
Example exception for GAS projects:
[[exceptions]]
check = "single_use_helpers"
path = "src/*.js"
justification = "Google Apps Script lacks module system - single-use helpers isolate logic in global namespace"
Available Checks
Content Quality
- hyperbolic_language - Detects promotional superlatives and marketing hype words in code and comments
- dead_code_markers - Comments marking code as deprecated/unused
- deferred_work - TODO/FIXME/hack markers
- test_excuses - Excuse-making in tests (known issue, will fix later)
- verbose_comments - Overly verbose AI-generated comments
- speculative_generality - YAGNI violations (future use, placeholder, stub)
- agent_scaffolding - Agent workflow markers (Phase 1, Step 3, etc.)
- documentation_links - Broken relative links in Markdown documentation files
- hardcoded_years - Hardcoded year literals (2024–2029) that may be stale AI-generated timestamps or copyright headers
Dead Code Detection
- dead_local_variables - Wildcard bindings (
let _ =) and underscore variables that suppress warnings - dead_fields - Struct fields that are never read (write-only fields)
- dead_methods - Methods/functions with zero callers
- dead_structs - Struct/enum/class definitions that are never constructed in production code
- dead_variants - Rust enum variants (match-arm-only use is dead) and Python enum members (attribute-access-only) defined but never used in production code
- unused_trait_parameters - Trait parameters unused in ALL implementations
- vestigial_code - Functions with stub bodies (
todo!(),unimplemented!(),pass,..., empty block) that silently do nothing when called
Evasion Detection
- lint_suppression - Lint suppressions without fixes (
# noqa,#[allow]) - manifest_lint_suppression - Lint rules disabled at manifest level (Cargo.toml, pyproject.toml, ESLint config)
- test_skip_evasion - Skipped tests (
@pytest.mark.skip) - redundant_error_handling - Empty/bare error handlers that do nothing
- defensive_error_silencing - Silent error handling that hides bugs instead of failing fast
- silent_fallbacks - Converting errors to defaults (
.ok(),.unwrap_or_default(), catch-all match) - discarded_errors - Error-handling code that discards original error context:
.map_err(|_| ...), bareexceptwithout chaining,catchwithout using error - single_use_helpers - Functions called from only one place — review for reflexive extraction to dodge complexity lints
- empty_test_bodies - Test functions whose bodies cannot fail: empty, comment-only, let-only, trivially-true assertions, JS callbacks with no assertions
- always_passing_tests - Test functions with no assertion indicators — always pass regardless of code behavior
- hidden_env_config - Runtime env vars used as hidden behavioral toggles — undocumented switches invisible to the CLI/config
Code Hygiene
- absolute_paths - Hardcoded absolute filesystem paths that break portability across machines
- redundant_map_err - Redundant
.map_err(Type::from)?chains where?would do the sameFromconversion - generic_names - Poor variable names (temp, data, x)
- nonstandard_constructors - Non-standard constructor names (
build_with_*,new_with_*,create,make) - primitive_obsession - Using primitives where types belong (magic numbers, string/int pattern matching, bool proliferation)
- summary_litter - AI-generated status/summary/report files
- random_scripts - Scattered/unorganized shell scripts (
.sh,.bash,.zsh) or.pyscripts - over_abstraction - Thin wrapper functions that add no value
- outdated_setup - Outdated edition/version in setup files (Cargo.toml, pyproject.toml)
- copy_paste_detection - Duplicated code within/across files
- prefer_match - If/else chains that should use match/pattern matching
- implicit_state_machine - Detects implicit state machines that should use enums
- trivial_type_aliases - Type aliases that are trivial wrappers around standard library types
- unhelpful_expect - Unhelpful
.expect()messages that don't explain what went wrong - conditional_wrapper - Functions starting with conditional early-return that hide control flow from callers
- reexport_stubs - Rust files that contain only
pub usere-exports with no implementation (pass-through stubs) - duplicate_entry_points - Rust modules with conflicting entry points:
foo/mod.rsandfoo.rscoexisting, orlib.rs+main.rsboth independently declaring the same module names - direct_exit - Direct process termination (
process::exit,sys.exit,process.exit) called outside designated entry points — bypasses cleanup and prevents testing - public_test_modules - Test infrastructure leaked into the public API surface via
pub mod testsorpubhelper functions insidecfg(test) - pub_visibility -
pubitems in library crates with no cross-crate consumers — over-broad visibility that should bepub(crate)or private - borrow_checker_evasion - Excessive
.clone(),Arc<Mutex<T>>overuse, anddyn Traitoveruse — density-based detection of borrow-checker evasion patterns common in AI-generated Rust
Performance
- wasteful_roundtrip - Transform-then-inverse patterns that waste CPU and memory (
format!then slice,strthen int)
Security
- security_practices - Dangerous API scanner:
unsafeRust (with missing/vague// SAFETY:comment detection),eval/exec/picklein Python,innerHTML/eval/child_processin JavaScript
Infrastructure
- linter_configuration - Proper linter configuration (Clippy/Ruff) with complexity checking
- version_consistency - Version strings out of sync across config files (Cargo.toml workspace members, pyproject.toml/setup.cfg/
__version__, package.json monorepo)
AI Prose Artifacts
- typescript_any_abuse - TypeScript
anytype annotations, casts, and generic widening — the most common TypeScript AI slop pattern, indicating the generator gave up on type reasoning
Database
- json_column_abuse - Structured data serialized as JSON into a single database column instead of proper relational tables — a performance and maintainability disaster
- unsafe_sql_construction - SQL queries built with string formatting or concatenation (f-strings,
%,.format(),+) — SQL injection vector; use parameterized queries instead
Philosophy
Code Gourmand is inspired by SSI's standards-checker. The goal is not to be pedantic, but to catch genuine quality issues that accumulate when AI generates code without understanding context.
All checks are:
- Configurable - Patterns and thresholds in TOML
- Educational - Violations explain why they matter and how to fix them
- Justifiable - Exceptions require explicit reasoning
Complementary Tooling
Gourmand catches structural and behavioral slop patterns — dead scaffolding, evasion tactics, test theater, copy-paste, generic names. It is not a replacement for language-specific linters and type checkers. The two layers are complementary: strict linters enforce language-level correctness; Gourmand catches higher-level quality patterns that linters can't see.
The underlying thesis: AI code generation is stochastic optimization, and better constraints produce better outputs. Strict linters reduce the space of bad solutions the model can emit. Gourmand catches what leaks through anyway.
Recommended stack per language:
Rust
clippywith-D warnings -D clippy::all(all lints as errors, no silently-ignored warnings)rustfmtfor consistent formatting
Python
rufffor linting and formatting in one fast toolmypy --strictfor type checking
JavaScript / TypeScript
biome checkfor linting and formatting (fast Rust-based tool with type-aware rules in v2)tsc --noEmitfor full type checkingeslintwith@typescript-eslint/strict-type-checkedfor type-aware rules Biome hasn't yet implemented (any-propagation, strict boolean expressions, deprecation at call sites)
The recommended invocation order: biome check --write && tsc --noEmit && eslint. The JS/TS ecosystem evolves quickly — treat the specific tool choices here as current best practice, not permanent truth.
All of these should be enforced in pre-commit hooks (using pre-commit) and/or CI, not just run manually. Gourmand's own .pre-commit-config.yaml is an example: it runs rustfmt, clippy, and ruff on every commit alongside Gourmand itself.
Agent Skills
Gourmand provides AI agent skills - progressive, context-aware instructions that help AI coding agents work effectively with Gourmand.
Installing Skills
# Write skills to .agents/skills/ (default, works with Cursor and Claude Code)
gourmand init --skills
# Write to a custom directory
gourmand init --skills --skills-dir .claude/skills
# Overwrite existing skill files (shows a diff first if you don't use --force)
gourmand init --skills --force
Commit the resulting directory to version control so the skills are available to everyone working on the project.
Available Skills
gourmand-setup- End-to-end project setup: installing pre-commit, per-language linter templates, incremental mode, upgrade workflow, and CI integrationgourmand-usage- How to run Gourmand, interpret violations, and fix vs. evadegourmand-triage- Systematic workflow for working through violations: easy fixes first, fix_planned for the rest, when by_design is honest, and why accepted_bad_taste requires human approvalgourmand-config- Understanding the two-tier configuration systemgourmand-audit- Adversarial audit of the exceptions file usinggourmand audit
Using Skills in Your Agent
Skills are listed in AGENTS.md and loaded by name automatically by Cursor and Claude. Distribution skills (for gourmand users) live in skills/ and are plain markdown. Dev-only skills (for working on gourmand itself) live directly in .agents/skills/.
Why Skills?
Skills complement Gourmand's lectures:
- Skills are proactive - Loaded before work starts, teach workflow
- Lectures are reactive - Shown after violations, teach fixing
Together, they help AI agents maintain code quality without reflexive evasion.
License
Good Place Season 3 Episode 9 License (see LICENSE.md)
This code was generated by a large-language model AI.