ESLint plugin with AI agent configuration generator for JavaScript projects.
Validation: Dogfooded on own codebase (769-line CLI → 50 lines, 338 violations detected)
Method: Static analysis + project-specific configuration generation
Use Cases: JavaScript/Node.js projects using AI coding assistants (Claude, Cursor, Copilot)
This plugin combines two functions and built-in guardrails:
- ESLint rules - 8 rules targeting AI-generated code patterns
- Configuration generator - CLI tool that creates AI agent guides and ESLint configs
- Guardrails: No-New-Debt Ratchet — hooks + CI that block increases in analyzer categories. See No-New-Debt Ratchet (All projects)
Running init creates:
| File | Purpose | Size |
|---|---|---|
.ai-coding-guide.json |
Machine-readable configuration | ~100 lines |
AGENTS.md |
AI agent coding reference | ~200 lines |
.cursorrules |
Cursor editor integration | ~50 lines |
eslint.config.mjs |
ESLint configuration with architecture rules | ~150 lines |
Add scripts to your package.json (init will add these automatically):
{
"scripts": {
"lint": "eslint .",
"lint:ci": "eslint . || true",
"lint:json": "eslint . -f json -o lint-results.json",
"analyze:current": "eslint-plugin-ai-code-snifftest analyze --input=lint-results.json --format=json --output=analysis-current.json",
"ratchet": "eslint-plugin-ai-code-snifftest ratchet"
}
}GitHub Actions options:
A) Scaffold with the CLI
npx eslint-plugin-ai-code-snifftest init --ciB) Reusable workflow
name: ci-ratchet
on: [push, pull_request]
jobs:
ratchet-and-tests:
uses: mojoatomic/eslint-plugin-ai-code-snifftest/.github/workflows/ratchet-reusable.yml@main
with:
node-version: '20'Branch protection
- Require status check: ratchet-and-tests
- Optional: also require your primary test job (e.g., Test on Node.js 20.x)
- Node.js 18+
- ESLint 9+
See docs/MINIMUM_REQUIREMENTS.md for version compatibility.
npm i eslint --save-dev
npm install eslint-plugin-ai-code-snifftest --save-devSee full command and flag reference in docs/CLI.md.
npx eslint-plugin-ai-code-snifftest setup --yes# Step 1: Analyze your codebase
npx eslint-plugin-ai-code-snifftest learn --interactive
# Step 2: Generate configuration files
npx eslint-plugin-ai-code-snifftest init
# Step 3: Start linting!
npx eslint .# One command setup
npx eslint-plugin-ai-code-snifftest init \
--primary=web-app \
--yes
# Start linting!
npx eslint .After setup, you'll have:
- ✅ AGENTS.md - AI coding guidelines (read by Warp, Cursor, Claude)
- ✅ .ai-coding-guide.json - Project configuration
- ✅ eslint.config.mjs - ESLint rules with architecture guardrails
- Review
AGENTS.mdfor coding guidelines - Run
npx eslint .to check your code - Enable the No‑New‑Debt Ratchet to prevent regressions (see section below)
- Use AI assistants (they'll automatically read AGENTS.md)
npx eslint-plugin-ai-code-snifftest init
# Prompts for:
# - Primary domain (e.g., web-app, cli, data-science)
# - Additional domains (optional)
# - File generation preferencesnpx eslint-plugin-ai-code-snifftest init \
--primary=web-app \
--additional=react,api \
--cursor# Analyze existing codebase patterns
npx eslint-plugin-ai-code-snifftest learn --interactive
# Generate config based on detected patterns
npx eslint-plugin-ai-code-snifftest init --primary=autoWe tested this tool on its own codebase during a refactoring effort.
- Codebase: This plugin's source code
- Task: Refactor 769-line CLI file
- Method: Used generated AGENTS.md and architecture guardrails
- CLI file: 769 lines → 50 lines (93% reduction)
- Violations detected: 338 (10 errors, 328 warnings)
- Auto-fixable: 242 violations (72%)
- CLI complexity: 1 warning (down from multiple violations)
Learn command:
- Score: 44/100
- Naming detection: 97% accuracy (4,836 camelCase detected)
- Generic name detection: 8 terms flagged correctly
Init command:
- Generated AGENTS.md with correct domain configuration
- Created architecture section with file/function limits
- ESLint config properly integrated
Lint results:
- Plugin rules detected real issues (prefer-simpler-logic: 27, no-redundant-conditionals: 10)
- Architecture rules caught limit violations (complexity: 44, max-lines: 8)
- Quote style violations: 204 (auto-fixed)
See DOGFOOD_RESULTS.md for complete analysis.
This project uses itself to manage tech debt. See real results:
- analysis-report.md - 360 violations analyzed in 8 minutes
- FIXES-ROADMAP.md - 4-phase remediation plan
- analysis.json - Structured data
See docs/dogfood/issues-enhanced/ for 5 production-ready issues:
- Phase 1: Auto-fix - 215 violations, 10 min effort
- Phase 2: Domain Terms - 72 violations, 5.8 hours
- Phase 3: Complexity - 91 violations, 117 hours
- Phase 4: Architecture - 52 violations, 10.4 hours
Each issue includes:
- Problem statement (WHY it matters)
- AGENTS.md pattern references
- Specific fix strategies (result → ruleResult)
- Top 10 files with line numbers
- Code examples from violations
- Verification commands
- Before (tool output): docs/dogfood/issues/
- After (AI enhanced): docs/dogfood/issues-enhanced/
See the difference AI makes in adding domain intelligence!
See ULTIMATE-ISSUE-PROMPT.md for the AI prompt that transformed rich markdown into perfect, domain-aware GitHub issues.
This is what you get when you use this tool. ✨
├── docs/
│ ├── dogfood/
│ │ ├── analysis-report.md
│ │ ├── FIXES-ROADMAP.md
│ │ ├── analysis.json
│ │ ├── issues/ # Tool output
│ │ └── issues-enhanced/ # AI enhanced
│ └── ULTIMATE-ISSUE-PROMPT.md
See docs/MIGRATION.md for changes in defaults and new commands.
The learn command analyzes your codebase to detect patterns:
# Interactive mode with code sampling
npx eslint-plugin-ai-code-snifftest learn --interactive --sample=300Detection capabilities:
- Naming patterns (camelCase, snake_case, PascalCase detection)
- Generic variable names
- Boolean prefix patterns
- Async function patterns
Limitations:
- Sample-based analysis (may miss patterns in unsampled files)
- Requires minimum codebase size for pattern detection
- Generic name detection threshold: 0.6 confidence minimum
Output:
.ai-coding-guide.jsonupdated with detected patternslearn-report.jsonwith detailed findings- Optional:
.ai-constants/project-fingerprint.js
See docs/learn.md for methodology details.
🔧 Automatically fixable by the --fix CLI option.
💡 Manually fixable by editor suggestions.
| Name | Description | 🔧 | 💡 | |
|---|---|---|---|---|
| enforce-domain-terms | Encourage domain-specific naming using declared project terms | ![badge-permissive-start][] | 💡 | |
| enforce-naming-conventions | Enforce naming conventions from project config (style, boolean/async prefixes, plural collections) | 💡 | ||
| no-equivalent-branches | Detect if/else branches that do the same thing | ![badge-permissive-start][] | 🔧 | |
| no-generic-names | Flag generic names; enforce domain-specific naming | ![badge-permissive-start][] | ||
| no-redundant-calculations | Detect redundant calculations that should be computed at compile time | ![badge-permissive-start][] | 🔧 | 💡 |
| no-redundant-conditionals | Simplify redundant conditional expressions | ![badge-permissive-start][] | 🔧 | |
| no-unnecessary-abstraction | Suggest inlining trivial single-use wrapper functions that add no value | ![badge-permissive-start][] | 💡 | |
| prefer-simpler-logic | Simplify boolean expressions and remove redundant logic | ![badge-permissive-start][] | 🔧 |
Optional feature that enforces file and function complexity limits.
Enabled via init command or by adding to .ai-coding-guide.json:
{
"architecture": {
"maxFileLength": {
"cli": 100,
"command": 150,
"util": 200,
"default": 250
},
"functions": {
"maxLength": 50,
"maxComplexity": 10,
"maxDepth": 4,
"maxParams": 4,
"maxStatements": 30
}
}
}When enabled, adds these rules to eslint.config.mjs:
Global (warnings):
max-lines: 250 lines per filemax-lines-per-function: 50 linescomplexity: Cyclomatic complexity ≤10max-depth: Nesting depth ≤4max-params: Function parameters ≤4max-statements: Statements per function ≤30
Path-specific (errors):
- CLI files (
bin/*.js): 100 lines maximum - Command files: 150 lines maximum
- Test files: Complexity/statement limits disabled
- Limits are suggestions, not enforced by language/runtime
- May require adjustment for specific project needs
- Test files excluded from most limits
Generated files are consumed by AI coding assistants:
| Tool | Integration Method | File Read |
|---|---|---|
| Warp | Automatic | AGENTS.md |
| Cursor | Automatic | .cursorrules |
| Claude Desktop | Manual reference | AGENTS.md |
| GitHub Copilot | Manual reference | AGENTS.md |
| Other AI assistants | Manual reference | AGENTS.md |
From AGENTS.md:
- Project domain context
- File length limits by type
- Function complexity limits
- Naming conventions
- Code pattern examples (good vs. problematic)
- Architecture preferences
- AI agents may not always follow guidelines
- Effectiveness depends on AI model and integration method
- Manual reference tools require explicit prompting
- No enforcement mechanism for AI compliance
This plugin is compatible with Prettier. Auto-fixes focus on logical simplification (not formatting).
Recommended workflow:
eslint --fix . # Logical fixes
prettier --write . # FormattingOr use integrated setup:
// eslint.config.mjs
import prettier from 'eslint-plugin-prettier';
import aiSnifftest from 'eslint-plugin-ai-code-snifftest';
export default [
{
plugins: { 'ai-code-snifftest': aiSnifftest },
rules: {
'ai-code-snifftest/no-redundant-calculations': 'error',
'ai-code-snifftest/prefer-simpler-logic': 'error',
},
},
prettier.configs.recommended,
];Verified with Prettier 3.x compatibility suite (18 integration tests).
Module type of file:///...eslint.config.mjs is not specified
Solution: Add "type": "module" to package.json
error 'require' is not defined no-undef
error 'process' is not defined no-undef
Solution: Generated config includes Node.js globals. For custom configs:
import globals from 'globals';
export default [{
languageOptions: {
globals: { ...globals.node }
}
}];Fixed in v0.0.1+. Update to latest version:
npm update eslint-plugin-ai-code-snifftestFORCE_ESLINT_CONFIG=1 npx eslint-plugin-ai-code-snifftest init- Preferred:
npm run lint:json→ writeslint-results.json - Alternate:
npm run lint:js -- --format json -o lint-results.json - Note: the aggregator
npm run lintuses npm-run-all and does not forward CLI flags to sub-scripts. - Analyze JSON:
npx eslint-plugin-ai-code-snifftest analyze --input=lint-results.json
If eslint.config.mjs doesn’t include “Architecture guardrails” or test files aren’t exempted:
- Generate a debug snapshot
AI_DEBUG_INIT=1 npx eslint-plugin-ai-code-snifftest init --yes --eslint
# or
npx eslint-plugin-ai-code-snifftest init --yes --eslint --debug- Inspect
.ai-init-debug.jsonin your project root. It includes:
- args: parsed CLI flags (e.g.,
--no-arch,--arch=false,--yes,--eslint) - enableArch: whether architecture was enabled
- cfgHasArchitecture: whether architecture config was added
- files.eslintConfig: path to generated config
- files.eslintHasGuardrails: whether guardrail rules were written
- files.agentsHasArchitectureSection: whether
AGENTS.mdincludes the section
- Share
.ai-init-debug.jsonin your GitHub issue if you need help (it contains no secrets).
Tip: Add .ai-init-debug.json to .gitignore (this repo does).
This tool is designed for JavaScript/Node.js projects and is currently in active development.
Not suitable for:
- TypeScript projects (limited support)
- Projects using ESLint <9.0
- Languages other than JavaScript
- Projects requiring JSDoc-based type checking
- Projects with custom ESLint plugin conflicts
Known limitations:
- Learn command requires minimum codebase size for pattern detection
- Generic name detection may have false positives in domain-specific contexts
- Architecture limits are suggestions only, not runtime-enforced
- AI agent compliance not guaranteed
Keep quality trending in one direction only: better. The ratchet blocks any increase in analyzer categories (complexity, architecture, domain terms, magic numbers) while allowing gradual cleanup.
Why this matters (works for greenfield and brownfield)
- New templates often ship with violations; accept the baseline once, then prevent regressions
- Rapid prototyping: iterate fast without blocking, but never get worse than the last accepted state
- AI-generated and copied code: capture current count as baseline; fix over time
How it works in this repo
- Baseline file: analysis-baseline.json
- Local hook: pre-push runs
npm run lint:json && npm run analyze:current && npm run ratchet && npm test - CI: .github/workflows/ci-ratchet.yml runs the same and uploads artifacts
Usage
# Create/refresh baseline from current state (intend to accept current count)
npm run lint:json && npm run analyze:baseline
# Check current branch against baseline (runs in hooks/CI)
npm run lint:json && npm run analyze:current && npm run ratchet
# After reducing violations, refresh baseline intentionally
npm run lint:json && npm run analyze:baseline
# Commit the updated baseline (recommended message):
# 'ratchet: refresh baseline after reductions'Modes for different project types
- Zero-Tolerance (pure greenfield)
- Keep baseline at 0; optionally promote key rules to error in CI-only config
- Flexible Greenfield (prototype mode)
- Set baseline from current state (e.g., template/prototype); prevent increases; ratchet down as you fix
- Brownfield (existing code)
- Set baseline from current codebase; prevent increases; reduce over time; optionally use path-specific overrides for legacy areas
Adopting ratchet in other repos
- Manual setup today: copy scripts/ratchet.js, add the npm scripts shown above, wire a pre-push hook, and add a CI job similar to ci-ratchet
- Coming soon: a one-shot "guardrails setup" command to scaffold these pieces automatically (see issue #180)
For contributors to this repo (self-hosting the plugin): running our rule fixers on the plugin’s own rule sources can mutate rule implementations. We added guardrails and documented a safe workflow — see docs/DEVELOPING.md.
Built-in feature for discovering domain-specific constants from npm packages.
Disable (opt-out):
npx eslint-plugin-ai-code-snifftest init --no-externalOr in .ai-coding-guide.json:
{
"experimentalExternalConstants": true, // default
"externalConstantsAllowlist": ["^@ai-constants/"]
}Status: Enabled by default; use --no-external or set experimentalExternalConstants: false to opt out
Limitations:
- Requires specific npm package structure
- Discovery cache may need manual clearing
- Not all domain packages supported
[Your License]
[Contribution guidelines]
Rule tests should not depend on the repository's .ai-coding-guide.json. The plugin resolves configuration with this precedence:
- RuleTester settings (
context.settings['ai-code-snifftest']) - Environment variable
AI_SNIFFTEST_CONFIG_JSON(JSON string) - Disk
.ai-coding-guide.json
Inject settings per test or suite to make behavior deterministic:
// Example: dual suites for a rule
const RuleTester = require('eslint').RuleTester;
const rule = require('../lib/rules/your-rule');
const tester = new RuleTester({ languageOptions: { ecmaVersion: 2021, sourceType: 'module' } });
(function extConstOn(){
const inject = (tc) => ({ ...tc, settings: { 'ai-code-snifftest': { experimentalExternalConstants: true } } });
tester.run('your-rule [extConst=true]', rule, {
valid: [ inject({ code: '/* ... */' }) ],
invalid: [ inject({ code: '/* ... */', errors: [{ messageId: '...' }] }) ]
});
})();
(function extConstOff(){
const inject = (tc) => ({ ...tc, settings: { 'ai-code-snifftest': { experimentalExternalConstants: false } } });
tester.run('your-rule [extConst=false]', rule, {
valid: [ /* deterministic cases */ ],
invalid: [ inject({ code: '/* ... */', errors: [{ messageId: '...' }] }) ]
});
})();For integration tests, you can set an environment override instead of per-test settings:
export AI_SNIFFTEST_CONFIG_JSON='{"experimentalExternalConstants":false}'Note: This documentation describes capabilities validated through self-testing. Results may vary based on project structure, codebase patterns, and AI assistant behavior.