clean-slop documentation
clean-slop is a static-analysis CLI that scans JavaScript/TypeScript codebases for
AI-generated code smells, security issues, and production-readiness problems, and turns
them into a 0–100 score you can gate CI on.
Installation
# One-off run, no install
npx clean-slop scan
# Add as a dev dependency
npm install --save-dev clean-slop
npx clean-slop scan
# Global install
npm install -g clean-slop
clean-slop scan
Requires Node.js >= 20.19.
CLI Reference
| Command | Description |
clean-slop scan [dir] | Scan a directory for issues (default command) |
clean-slop check [dir] | Quick check: exit 0 if production ready, 1 if not |
clean-slop watch [dir] | Watch for file changes and re-scan automatically |
clean-slop report [dir] | Generate a report from the last scan, or run a fresh scan |
clean-slop doctor | Diagnose the current environment, config, and installation |
clean-slop init | Create a starter clean-slop.config.js |
scan flags
| Flag | Description | Default |
-c, --config <path> | Path to configuration file | auto-discovered |
--reporter <name> | text, json, html, markdown, sarif | text |
-o, --output <file> | Write report to a file instead of stdout | stdout |
--fail-threshold <score> | Minimum score before exiting with code 1 | 70 |
--max-critical <n> | Maximum allowed critical issues (0 = none) | 0 |
--max-high <n> | Maximum allowed high issues | unset |
--no-ai-slop / --no-security / --no-reliability / --no-maintainability / --no-production-readiness | Disable a rule category | all enabled |
--include-fixtures | Also scan conventional test-fixture directories (excluded by default) | excluded |
--verbose | Print full issue details including snippets and fixes | off |
--quiet | Suppress banners/progress; still prints the full report (composes with --verbose) | off |
--ci | Disable colors and animated progress, emit line-by-line output | auto-detected from TTY |
--no-color | Disable colored output regardless of TTY detection | auto |
--reporter is the only supported name for choosing an output format — there is no separate --format alias.
Exit Codes
| Code | Meaning |
0 | Scan completed and met --fail-threshold / issue-limit gates (or the command has no pass/fail gate) |
1 | Scan completed but failed the configured gate |
2 | clean-slop could not run at all (invalid config, bad directory, internal crash) |
Color output: colors are disabled automatically when NO_COLOR is set, when output
is not a TTY (piped to a file or captured by CI), or when --ci is passed. Set
FORCE_COLOR=1 to force colors on anyway.
Configuration
clean-slop looks for (in order): clean-slop.config.{js,ts,mjs,cjs}, .clean-slop.{js,json,yaml,yml},
or a "clean-slop" field in package.json. Run clean-slop init to scaffold one with
every option commented. CLI flags always override the config file for the same setting.
/** @type {import('clean-slop').UserConfig} */
export default {
include: ['**/*.ts', '**/*.tsx'],
exclude: ['**/node_modules/**', '**/dist/**'],
excludeFixtures: true, // auto-exclude __tests__/fixtures, etc.
categories: { 'ai-slop': true, security: true, reliability: true, maintainability: true, 'production-readiness': true },
failThreshold: 70,
maxIssues: { critical: 0, high: 5 },
reporter: 'text',
};
Every config key has a CLI flag equivalent where it makes sense to override per-run (e.g. failThreshold ↔
--fail-threshold, reporter ↔ --reporter). A malformed config file (unknown key,
wrong type, invalid reporter value) fails with the exact key that's wrong rather than a generic error.
Scoring Model
Each category score (and the overall score, their average) is computed from a density-based curve:
weighted deductions (critical=20, high=10, medium=4, low=1) are divided by the number of files actually scanned,
then mapped through 100 / (1 + density / 10). This means the same absolute number of issues counts
for less in a larger codebase — a handful of findings in a 3,000-file repo won't cliff the score to 0 the
way a flat per-issue deduction would.
The visible score and the pass/fail gate (productionReady, driven by
--fail-threshold / --max-critical / --max-high) are tracked separately and can
never silently contradict each other — a repo can be "72/100, needs work" and "FAILED because of 3
criticals" at the same time, and both facts are shown.
When some files fail to parse, they are excluded from scoring and a partial coverage warning is
shown prominently at the top of every reporter (not buried at the end), grouped by likely cause (Flow syntax,
decorator syntax, JSX in a .js file, etc.) with actionable next steps.
Reporters & Schemas
| Reporter | Notes |
text | Default. Colorized in a TTY, plain in CI/piped output. |
json | Stable, versioned shape — every payload includes version and timestamp. See the published JSON Schema. |
html | Self-contained report with severity filters, WCAG AA contrast, a print stylesheet, and mobile layout. |
markdown | Renders cleanly on GitHub — suitable for posting as a PR comment. |
sarif | SARIF 2.1.0, consumable by GitHub Code Scanning and other SARIF-aware tools. |
The number shown in the HTML report, the text report, and the JSON score.overall field are guaranteed
identical for the same scan: all reporters render from the single ScanResult object the scanner produces,
rather than recomputing anything themselves.
Rule Reference
All 23 built-in rules, generated directly from the rule metadata in the codebase so this page cannot drift from the actual behavior.
AI Slop (3 rules)
Patterns genuinely more diagnostic of LLM-generated output specifically — not just general code smells any author could produce.
ai-slop/empty-catch
HIGH
confidence: certain
auto-fixable: No (manual fix required)
Detects catch blocks that silently swallow errors.
Why this matters: Empty catch blocks are a hallmark of AI-generated code that handles the error path syntactically but not semantically. They hide failures, making bugs invisible in production.
View rule source on GitHub →
ai-slop/fake-validation
HIGH
confidence: medium
auto-fixable: No (manual fix required)
Detects validation functions that trivially return true without meaningful checks.
Why this matters: AI code generators often produce validation functions with names that imply safety but bodies that unconditionally approve all input. This creates a false sense of security.
View rule source on GitHub →
ai-slop/stub-implementation
HIGH
confidence: high
auto-fixable: No (manual fix required)
Detects functions that throw a "Not implemented" placeholder error instead of a real implementation.
Why this matters: Unlike a plain TODO comment (a general maintainability marker any author might leave), a function body that is entirely `throw new Error("Not implemented")` is a stronger, more specific signal: it is a skeleton that looks complete (correct signature, valid syntax) but breaks immediately at runtime. This pattern shows up disproportionately in AI-generated code that stubs out functions it was not asked to fully implement.
View rule source on GitHub →
Security (8 rules)
Vulnerability patterns: injection, unsafe deserialization, weak cryptography, unsafe defaults.
security/command-injection
CRITICAL
confidence: high
auto-fixable: No (manual fix required)
Detects child_process calls that build shell commands from variables, enabling command injection.
Why this matters: Passing user-controlled data to exec(), spawn(), or similar APIs allows an attacker to inject shell metacharacters that execute arbitrary system commands.
View rule source on GitHub →
security/dangerous-cors
HIGH
confidence: high
auto-fixable: No (manual fix required)
Detects wildcard CORS origins, insecure cookie settings, and overly permissive configurations.
Why this matters: CORS misconfiguration and insecure cookie attributes are among the most common security mistakes in Node.js backends. They enable CSRF, session hijacking, and cross-origin data leakage.
View rule source on GitHub →
security/hardcoded-secrets
CRITICAL
confidence: high
auto-fixable: No (manual fix required)
API Key
Why this matters: Hardcoded credentials are one of the most common causes of security breaches. When code is committed to version control, credentials are permanently exposed in history even if removed later.
View rule source on GitHub →
security/path-traversal
CRITICAL
confidence: medium
auto-fixable: No (manual fix required)
Detects file system operations with dynamic paths that may allow path traversal attacks.
Why this matters: Path traversal occurs when an attacker uses "../" sequences in user-controlled input to escape the intended directory and access arbitrary files.
View rule source on GitHub →
security/prototype-pollution
HIGH
confidence: medium
auto-fixable: No (manual fix required)
Detects patterns that may allow attackers to pollute Object.prototype.
Why this matters: Prototype pollution allows an attacker to inject properties into Object.prototype, affecting all objects in the application. This can bypass security checks, cause denial of service, or enable remote code execution. NOTE: this is a lightweight, name-based heuristic, not real taint analysis or a data-flow graph. It only looks at where a computed key locally appears to come from (a literal, a for-in loop over a local object, a request/input-shaped identifier, or something unclear) and adjusts severity accordingly. It cannot trace a key through several variables or function calls, so both false positives and false negatives remain possible — treat findings as a starting point for review, not a verdict.
View rule source on GitHub →
security/sql-injection
CRITICAL
confidence: high
auto-fixable: No (manual fix required)
Detects SQL queries built with string concatenation or template literals containing variables.
Why this matters: SQL injection remains the most prevalent web application vulnerability. Embedding JavaScript variables directly into SQL strings gives attackers control over query structure.
View rule source on GitHub →
security/unsafe-eval
CRITICAL
confidence: high
auto-fixable: No (manual fix required)
Detects use of eval() and the Function constructor, which execute arbitrary code.
Why this matters: eval() and new Function() execute strings as JavaScript code. When any part of the string comes from user input or external data, this is a code injection vulnerability.
View rule source on GitHub →
security/weak-crypto
HIGH
confidence: high
auto-fixable: No (manual fix required)
Detects use of weak or broken cryptographic algorithms (MD5, SHA1, ECB mode, Math.random).
Why this matters: MD5 and SHA1 are cryptographically broken. Using them for password hashing or data integrity provides no real security. Math.random() is predictable and must not be used for tokens, keys, or any security-sensitive value.
View rule source on GitHub →
Reliability (3 rules)
Patterns likely to cause runtime failures: unhandled promises, missing awaits, infinite loops.
reliability/infinite-loop
HIGH
confidence: medium
auto-fixable: No (manual fix required)
Detects while(true) and for(;;) loops without reachable exit conditions.
Why this matters: Infinite loops without an exit condition hang server processes, exhaust CPU, and cause denial of service. They are commonly introduced by AI-generated code that models polling or retry logic incorrectly.
View rule source on GitHub →
reliability/missing-await
HIGH
confidence: medium
auto-fixable: Yes
Detects async functions that return un-awaited Promises or call async APIs without await.
Why this matters: Forgetting await is one of the most common async bugs in JavaScript. The function appears to return a value but actually returns a Promise, causing unexpected behavior in callers that expect a resolved value.
View rule source on GitHub →
reliability/unhandled-promise
HIGH
confidence: medium
auto-fixable: No (manual fix required)
Detects Promise-returning function calls that are not awaited, returned, or error-handled.
Why this matters: Unhandled Promise rejections crash Node.js processes in versions >= 15 and cause silent failures in older versions. Fire-and-forget async calls hide errors that should be surfaced to callers. This rule is not type-aware: without TypeScript type information it cannot always tell whether a given method truly returns a Promise, so ambiguous method names are reported as low-confidence, advisory findings rather than certain ones.
View rule source on GitHub →
Maintainability (7 rules)
General code-quality smells (size, complexity, nesting, dead code, TODOs) that any author — human or AI — can introduce. Being in this category does not imply AI authorship.
maintainability/circular-imports
MEDIUM
confidence: low
auto-fixable: No (manual fix required)
Identifies import patterns that commonly cause circular dependency cycles.
Why this matters: Circular imports cause initialization order issues, undefined module exports at runtime, and confusing bugs that differ between bundlers. They are a sign of poor module boundary design.
View rule source on GitHub →
maintainability/dead-code
MEDIUM
confidence: certain
auto-fixable: Yes
Detects unreachable code that follows a return, throw, break, or continue statement.
Why this matters: Unreachable code after an early exit is a general control-flow bug any author can introduce (often left behind after a refactor), not a marker specific to AI-generated code. It indicates logic that was added without tracking control flow and will never execute.
View rule source on GitHub →
maintainability/excessive-nesting
MEDIUM
confidence: certain
auto-fixable: No (manual fix required)
Detects functions with control-flow nesting deeper than ${MAX_DEPTH} levels.
Why this matters: Deeply nested code is a general maintainability smell that predates AI code generation -- it happens whenever conditionals accumulate without refactoring. Early returns, guard clauses, and extracted helper functions eliminate deep nesting regardless of who wrote it.
View rule source on GitHub →
maintainability/giant-file
MEDIUM
confidence: certain
auto-fixable: No (manual fix required)
Detects source files exceeding ${MAX_LINES} lines.
Why this matters: Large files concentrate unrelated logic, creating merge conflicts, slow code review, and unclear ownership. They are a common artifact of AI code generation that did not decompose responsibilities into modules.
View rule source on GitHub →
maintainability/giant-function
MEDIUM
confidence: certain
auto-fixable: No (manual fix required)
Detects functions exceeding ${MAX_LINES} lines, which tend to mix multiple responsibilities.
Why this matters: Long functions are a general code-smell, not something specific to AI-generated code -- humans write monolithic functions just as often. Functions longer than 80 lines are difficult to understand, test, and maintain regardless of who or what wrote them, which is why this lives under maintainability rather than ai-slop.
View rule source on GitHub →
maintainability/high-complexity
MEDIUM
confidence: certain
auto-fixable: No (manual fix required)
Detects functions with cyclomatic complexity exceeding ${COMPLEXITY_THRESHOLD}.
Why this matters: High cyclomatic complexity is a general, decades-old maintainability signal -- not specific to AI-generated code -- indicating a function juggles multiple responsibilities or branches. Complex functions are difficult to test and hard to reason about correctly regardless of origin.
View rule source on GitHub →
maintainability/todo-comment
LOW
confidence: high
auto-fixable: No (manual fix required)
Detects TODO, FIXME, HACK, and similar comments marking unfinished or provisional work.
Why this matters: TODO-style comments are a legitimate, decades-old maintainability signal used by human and AI authors alike -- this predates LLM-generated code entirely and is not evidence of AI authorship. It is tracked here because unresolved TODOs tend to accumulate silently and mark places the author knew were incomplete, not because it says anything about who or what wrote the code.
View rule source on GitHub →
Production Readiness (2 rules)
Development-time shortcuts that should not reach production: debug statements, hardcoded localhost URLs, mock implementations.
production-readiness/no-console-log
LOW
confidence: certain
auto-fixable: Yes
Detects console.log and other debug console methods left in production code.
Why this matters: Debug console statements leak internal application data, degrade performance, and indicate code that was not reviewed before shipping. Not flagged in test files, fixtures, build scripts, CLI tools, or dev-tooling config, where console output is the intended behavior rather than a leftover debug statement.
View rule source on GitHub →
production-readiness/no-localhost-urls
MEDIUM
confidence: high
auto-fixable: No (manual fix required)
Detects hardcoded localhost URLs, debug flags set to true, and mock/stub implementations.
Why this matters: Localhost URLs, active debug flags, and mock implementations in source code are telltale signs that development shortcuts were committed to production. Not flagged in test files, fixtures, or dev-server config (e.g. playwright.config.js), where a hardcoded localhost URL or a mock*/fake*/stub* function is expected and correct.
View rule source on GitHub →