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

CommandDescription
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 doctorDiagnose the current environment, config, and installation
clean-slop initCreate a starter clean-slop.config.js

scan flags

FlagDescriptionDefault
-c, --config <path>Path to configuration fileauto-discovered
--reporter <name>text, json, html, markdown, sariftext
-o, --output <file>Write report to a file instead of stdoutstdout
--fail-threshold <score>Minimum score before exiting with code 170
--max-critical <n>Maximum allowed critical issues (0 = none)0
--max-high <n>Maximum allowed high issuesunset
--no-ai-slop / --no-security / --no-reliability / --no-maintainability / --no-production-readinessDisable a rule categoryall enabled
--include-fixturesAlso scan conventional test-fixture directories (excluded by default)excluded
--verbosePrint full issue details including snippets and fixesoff
--quietSuppress banners/progress; still prints the full report (composes with --verbose)off
--ciDisable colors and animated progress, emit line-by-line outputauto-detected from TTY
--no-colorDisable colored output regardless of TTY detectionauto

--reporter is the only supported name for choosing an output format — there is no separate --format alias.

Exit Codes

CodeMeaning
0Scan completed and met --fail-threshold / issue-limit gates (or the command has no pass/fail gate)
1Scan completed but failed the configured gate
2clean-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

ReporterNotes
textDefault. Colorized in a TTY, plain in CI/piped output.
jsonStable, versioned shape — every payload includes version and timestamp. See the published JSON Schema.
htmlSelf-contained report with severity filters, WCAG AA contrast, a print stylesheet, and mobile layout.
markdownRenders cleanly on GitHub — suitable for posting as a PR comment.
sarifSARIF 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 →