1
0
Fork
You've already forked gourmand
0
forked from mattdm/gourmand
Reduce AI slop. Keep code tasteful. Your contributions welcome!
  • Rust 89.3%
  • Python 6.1%
  • HTML 3.4%
  • Shell 1%
  • CSS 0.1%
2026年05月18日 13:38:48 -04:00
.agents/skills docs: apply retro improvements from 030-072 marathon 2026年05月18日 13:32:53 -04:00
.claude docs: apply retro improvements from 030-072 marathon 2026年05月18日 13:32:53 -04:00
.cursor chore: add rule to ignore open files unless explicitly referenced 2026年05月07日 06:57:58 -04:00
.gourmand-exceptions.d fix: reclassify EV015 CH012 exception to by_design 2026年05月18日 11:46:48 -04:00
benchmarks benchmarks: record final 030-061 marathon baseline 2026年05月18日 10:34:17 -04:00
crates chore: bump minor version to 0.17.0 2026年05月18日 13:38:26 -04:00
docs fix: restore global_serverless_state in AI013 migration table 2026年05月18日 11:37:34 -04:00
scripts fix: plug 4 silent traversal gaps in Python visitor 2026年05月16日 23:13:10 -04:00
skills docs: document monolithic [[exceptions]] schema 2026年05月17日 06:00:14 -04:00
subagents/gourmand-audit-one chore: enforce --file in gourmand-audit-one spec 2026年05月01日 03:32:32 -04:00
todo fix: reclassify EV015 CH012 exception to by_design 2026年05月18日 11:46:48 -04:00
.gitignore feat: add phase_a_violations bridge to CheckContext 2026年05月11日 13:57:23 -04:00
.pre-commit-config.yaml chore: enforce no-ff merge via pre-push hook 2026年05月16日 00:55:48 -04:00
AGENTS.md docs: fix ctx.read_file receiver confusion in 3 locations 2026年05月15日 17:14:34 -04:00
Cargo.lock chore: bump minor version to 0.17.0 2026年05月18日 13:38:26 -04:00
Cargo.toml refactor: extract gourmand-patterns crate, replace bake-patterns with build.rs 2026年05月13日 14:02:57 -04:00
CHANGELOG.md chore: bump minor version to 0.17.0 2026年05月18日 13:38:26 -04:00
CLAUDE.md Revise AGENTS.md and add Claude Code symlinks 2026年04月25日 11:12:35 -04:00
clippy.toml Make trivial_type_aliases check strict, refactor to use meaningful names 2026年01月06日 13:28:43 -05:00
CONFIGURATION.md docs: document monolithic [[exceptions]] schema 2026年05月17日 06:00:14 -04:00
DEVELOPMENT.md docs: document two-binary model in DEVELOPMENT.md 2026年05月11日 12:49:20 -04:00
gourmand.toml refactor: rename threshold fields to use check ID prefix 2026年05月08日 21:55:07 -04:00
LICENSE.md Fix all Gourmand violations and remove JavaScript support 2025年12月15日 23:54:12 -05:00
pyproject.toml Fix version manager stale-branch regression 2026年04月13日 17:10:20 -04:00
README.md feat: add scope metadata field to all 93 checks 2026年05月17日 23:20:54 -04:00
rustfmt.toml Initial commit: Code Gourmand - AI slop detection tool 2025年12月15日 11:01:48 -05:00
version.toml chore: bump minor version to 0.17.0 2026年05月18日 13:38:26 -04:00

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:

  1. Review the exceptions - Check for false positives
  2. Commit the baseline - git add gourmand-exceptions.toml .gitignore && git commit -m "chore: add gourmand baseline" (or git add .gourmand-exceptions.d/ .gitignore if the .d/ format was used)
  3. Fix incrementally - Remove exceptions as you fix violations
  4. 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:

  1. Git repository - .git directory exists
  2. Initialized project - gourmand-exceptions.toml exists (if not, runs a full scan and hints to run gourmand init)
  3. Checkpoint (optional) - If set, checks all changes since checkpoint
  4. Staged changes - Otherwise, checks only staged changes
  5. Auto-magic - Gourmand checks only changed lines

One-shot scans: Running gourmand check on an uninitialized repo (or any external repo) automatically falls back to a full scan with a hint. No --full flag 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

  1. 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
  2. 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"
    
  3. 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 audit periodically 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 --all to include them anyway for a full sweep. For an incremental one-by-one workflow, run gourmand audit --show-next in a loop: it shows one entry that needs review, and exits 1 when all entries are current. After reviewing an entry, run gourmand audit --show-next --sign-off to record a commit-anchored sign-off in gourmand-audit-ledger.toml. Add --remove-dead to automatically remove dead entries as they are encountered. By default, --show-next shows only by_design exceptions (highest scrutiny value); use --classification all to audit every classification, or --classification fix_planned etc. to focus on a specific category. Load the gourmand-audit skill 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 settings
  • config/gourmand-patterns.toml - Standard patterns
  • config/gourmand-exceptions.toml - Standard exceptions
  • config/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 checks
  • gourmand-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 .toml file per entry, loaded alongside gourmand-exceptions.toml
  • gourmand-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 --full

Example 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(|_| ...), bare except without chaining, catch without 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 same From conversion
  • 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 .py scripts
  • 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 use re-exports with no implementation (pass-through stubs)
  • duplicate_entry_points - Rust modules with conflicting entry points: foo/mod.rs and foo.rs coexisting, or lib.rs + main.rs both 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 tests or pub helper functions inside cfg(test)
  • pub_visibility - pub items in library crates with no cross-crate consumers — over-broad visibility that should be pub(crate) or private
  • borrow_checker_evasion - Excessive .clone(), Arc<Mutex<T>> overuse, and dyn Trait overuse — 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, str then int)

Security

  • security_practices - Dangerous API scanner: unsafe Rust (with missing/vague // SAFETY: comment detection), eval/exec/pickle in Python, innerHTML/eval/child_process in 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 any type 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

  • clippy with -D warnings -D clippy::all (all lints as errors, no silently-ignored warnings)
  • rustfmt for consistent formatting

Python

  • ruff for linting and formatting in one fast tool
  • mypy --strict for type checking

JavaScript / TypeScript

  • biome check for linting and formatting (fast Rust-based tool with type-aware rules in v2)
  • tsc --noEmit for full type checking
  • eslint with @typescript-eslint/strict-type-checked for 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 integration
  • gourmand-usage - How to run Gourmand, interpret violations, and fix vs. evade
  • gourmand-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 approval
  • gourmand-config - Understanding the two-tier configuration system
  • gourmand-audit - Adversarial audit of the exceptions file using gourmand 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.