AI-SLOP Detector
Find AI-generated code that looks finished but isn't.
Catches what a normal linter passes over: empty functions with real-looking bodies, imports of things that don't exist, copy-pasted logic, unreachable code, and docs that oversell what the code actually does.
Runs fully offline · deterministic core scoring · no API key, no model download, nothing leaves your machine.
Proven on other people's code
A detector that flags plausible-but-empty code has to show its own evidence. Public open-source projects are its benchmark, its test bed and its calibration set: every number below comes from a recorded run on code its authors did not write.
Benchmark: v3.11.0 (the published wheel) on public projects
| Project | Domain | Python files analyzed | Cold scan | Rerun | |---|---|---:|---:|---| | biopython | bioinformatics | 270 | 31 s | byte-identical | | sentence-transformers | NLP embeddings | 317 | 17 s | byte-identical | | HealthChain | healthcare data (FHIR) | 125 | 7 s | byte-identical | | alphafold3 | protein structure prediction | 85 | 8 s | byte-identical | | jieba | Chinese text segmentation | 30 | 25 s | byte-identical | | cytools | Calabi-Yau geometry | 16 | 5 s | byte-identical | | financial-services | finance agents | 16 | 2 s | byte-identical | | langdetect | language detection | 9 | 2 s | byte-identical | | minGPT | language models | 5 | 1 s | byte-identical |
Each project was scanned twice with --read-only and an empty home
directory: 18 of 18 runs exited 0 with byte-identical JSON, nothing was
written to the home directory, and no repository changed. One Windows 11 desktop (8 logical cores),
Python 3.12; times are the first run, and depend on the machine.
Outside code shaped the analyzer
| Found on | What it exposed | What the analyzer does now |
|---|---|---|
| langdetect | an elif chain of eleven branches read as nesting depth 11 (real depth 2) | an elif is part of its if; across the 11 Python study repositories, 1,158 findings that were only elif depth are gone (v3.9.3) |
| the 12-repository study | @overload signatures reported as empty placeholders | ... under @overload is the typing construct, not a stub; 35 findings gone (v3.9.3) |
| HealthChain | modules with no executable code scored 97.5: 3 of its 7 hard-gate failures | a module without executable code is flagged no_executable_code, not scored as empty logic (v3.9.3) |
| jieba | 4 data modules (7.2 MB) held 99.2% of the line weight (project score 0.09) | static data modules are analyzed but carry no weight; 0.09 becomes 13.30 (v3.10.0) |
| hermes-agent | the JS regex fallback found 0 god functions where tree-sitter found 2,703 | every result says which analysis mode ran and which checks it could not run (v3.10.0) |
| OpenClaw-Medical-Skills, minGPT, cytools | imports of the project's own or declared modules reported as phantom imports | import evidence follows the scanned project's layout; 168 phantom_import findings reclassified (v3.10.0) |
| an 18-repository replay | function length and nesting counted layout, not logic | length counts logical lines with pylint's limit of 50; nesting follows SonarSource Cognitive Complexity; -608 god_function findings, none added (v3.10.1) |
How the results are checked
| | |
|---|---|
| Hand-reviewed findings | 146 sampled findings from 12 outside repositories were read in the source and classified as true positives or named error classes; the v3.9.3 and v3.10 fixes come from that review, each listed with its measured effect in the CHANGELOG. The tool is not independently validated; VALIDATION.md says what is and is not known. |
| Replayed before release | Scoring changes are replayed on an 18-repository corpus and the CHANGELOG states what moved, finding by finding. |
| Says when it measured nothing | Every project result carries measurement_state (COMPLETE, PARTIAL, NO_MEASUREMENT) and score_valid. When nothing was measured, human reports read NOT EVALUATED instead of a clean verdict, and a scan whose files all failed to analyze fails --fail-threshold. |
| Verified fast, and exactly | Each rule change runs its own tests, a mutation run, the recall corpus and a replay on 14 public repositories that runs only the changed patterns in parallel and is checked against the full pipeline (14/14 identical); every added finding is read in the source before the rule ships. Full suite in balanced parallel shards: 77-87 s instead of 270 s. See the verification workflow. |
| Its own tests are tested | More than 1,500 tests run in CI on Python 3.8 to 3.12. Behavior changes are checked against deliberately broken copies of the code (mutation testing): in v3.12.0, 21 of 21 breakages of the new rules were caught (v3.11.0: 25 of 25). |
How a rule change is verified
flowchart LR
A[Rule change] --> B[Owner tests<br/>fail first, then pass]
B --> C[Mutation run]
C --> D[Recall corpus]
D --> E[Fast replay<br/>14 public repos in parallel]
E --> F{Added findings<br/>read in source}
F -->|false alarm| A
F -->|real| G[Full suite<br/>parallel shards]
G --> H[CI exact commit]
E -. parity 14/14 .-> P[Full pipeline]
Linters check form; this checks substance. See why not just a linter.
Release track
- Stable tag:
v3.13.0 - Previous stable tag:
v3.12.0 v3.13.0: blind hold-out benchmarks on public repositories and the fixes they pointed to: null objects, base-class defaults and framework hooks are no longer reported as placeholders, and loggedNotImplementedErrorstubs, constant success reports and discarded results are. Hold-out v2 (development data): 3/12 -> 6/12 hollow found, 12 -> 4 of 828 genuine flagged. Hold-out v3 (independent draw, frozen detector, scored once): 9 of 374 and 10 of 741 genuine functions flagged; 3 of the 4 hollow functions found, too few to state a recall rate. See benchmarks/holdout_v3/FINDINGS.md.v3.12.0: catches code that only looks finished: stubs hidden behind logging (return_constant_stub), arguments returned unchanged (return_input_stub), catch-all handlers that return the success result (failure_as_success) and parameters a function never reads (ignored_parameter); measured on a recall corpus of lint-clean hollow code (4 of 29 found before, 18 of 29 now, 0 of 22 genuine flagged) and on 14 public repositories, with every added finding read in the source. See CHANGELOG.md.v3.11.0: one scan engine (ScanService) behind the CLI, MCP and REST, with progress events, cancellation and typed errors; MCP and REST scans write nothing by default (REST history only with"save_history": true), the REST server binds127.0.0.1, and agent project responses say whether the score measured anything (measurement_state,score_valid,reason). Findings and scores are unchanged. See CHANGELOG.md.v3.10.1: function length counts logical lines andtry/withare not nesting levels (far fewer falsegod_functionand nesting findings); UTF-8 files with a byte order mark and single JS/TS/Go files are analyzed instead of scored as parse errors; scans of git repositories under non-ASCII folder names and HTML reports of projects no longer crash; a scan that measured nothing showsNOT EVALUATEDand never passes--fail-threshold; an invalid or missing config stops the run before analysis. See CHANGELOG.md.v3.10.0: findings need evidence (declared intent, protocol methods, claim words, mutation evidence), every score discloses what it could not look at, static data modules no longer dilute the project score, and cold scans are 2.6-3x faster. See CHANGELOG.md.v3.9.3is a precision correction found by testing 3.9.2 on 12 outside repositories: anelifno longer counts as a level of nesting,@overloadsignatures are not placeholders, a module with no executable code is not scored as empty logic, a file that fails to analyze is listed and marks the scan incomplete (a hard gate then fails) instead of silently disappearing,--json --jsprints one JSON document, and the JS/TS analysis mode (tree-sitter or regex fallback) is disclosed. Scores mostly go down; see docs/RELEASE_NOTES.md.v3.9.2makes one result mean the same thing everywhere and removes double counting: one set of score bands for files, projects, JS and Go; docstrings count like comments (logic density, god_function); a nested function's nesting is charged once; exact duplicates compare function bodies only, with one size floor for the same-file and cross-file reports; import evidence follows the scanned project's root; output is byte-identical across cache, hash seed, Rust helper, and working directory. Self-calibration is now an advisory report that writes nothing, documented configuration keys that nothing read are removed with a warning, and--initwrites into. Scores move; see docs/RELEASE_NOTES.md.v3.9.1makes claim checks count only real claims and real evidence: jargon in import paths, identifiers, and license notices is not a claim; a claim is supported only by code structure (a library named in a comment no longer counts); evidence that one file cannot show, such as tests that live elsewhere, is reported as unmeasured rather than missing. It also flags names a real package does not define (phantom_member) and decides test files and exclusions relative to the project root, so a checkout under abuild/directory is no longer scanned as 0 files. Scores move; see docs/RELEASE_NOTES.md.v3.9.0closes a security hole (no model is read from the working directory, nothing is unpickled), makes import evidence follow CPython's resolution rules, and makesasync defstubs,--patterns-only,--disable, and the multi-file pre-commit hook work as documented. See docs/RELEASE_NOTES.md.
Navigation: What Is It? • Why Not a Linter? • When Not to Use • Quick Start • Verification • Boundaries • How It Works • Document Map • What It Detects • Scoring • Key Features • Calibration • Security • CI/CD • Config • VS Code • Roadmap • Changelog • Release Notes • Schema Validation
What Is AI-SLOP Detector?
AI-SLOP Detector is an evidence-based static analyzer that targets a defect class common in AI-assisted code: structurally plausible code that is functionally empty, disconnected, or misleading. It does not tell whether code was written by an AI; it reports static signals in the code itself.
General linters flag style and convention. This tool flags structural risk.
- 51 checks across Python, JS/TS and Go (the pattern registry) — empty stubs, imports that don't resolve, unreachable code, copy-paste clones, and buzzword-padded docs
- One 0–100 risk score per file — four measurements are combined so one bad dimension can't be hidden behind good ones (weighted geometric mean of logic density, jargon inflation, dependency use, and critical severity)
- Keeps local history — every scan is recorded locally with what measured it (detector version, configuration, project root);
--self-calibratereports what the comparable runs of your project suggest for the weights. The report is advisory: weights change only when you edit.slopconfig.yaml. - Tells real changes from noise — uses your commit history so a score drifting a point or two isn't mistaken for a real regression
- Knows your project type —
--initdetects the domain (web API, data/ML, numerical, CLI, library, bio, finance, or general;generalwhen the evidence is weak) and sets that domain's pattern thresholds, printing every change; weights stay the defaults; override with--domain - Python first, JS/TS and Go optional — install the
[js]or[go]extra to scan those files too - Drops into CI — soft / hard / quarantine gates, GitHub Actions ready
- VS Code extension — inline warnings as you type, score in the status bar
Why Not Just Use a Linter?
Ruff, pylint, ESLint, and SonarQube primarily check syntax, style, and general static-quality rules. They'll happily pass code that follows every rule and still does nothing — which is exactly what AI assistants tend to produce. This tool checks the other half: does the code contain structural evidence for what it and its docs claim? Structural evidence is not proof that a claim is true; the tool does not run your code.
| Can it catch... | ruff / pylint / SonarQube | AI-SLOP Detector | |---|---|---| | Style, formatting, syntax | Yes | No (not its job) | | An empty function with a real-looking body or fake return | No | Yes | | A module imported but never actually used downstream | Partial (unused-import) | Yes (usage ratio) | | A handler or pipeline that's defined but never wired in | No | Yes | | Docs or comments that oversell what the code does | No | Yes | | Copy-pasted duplicate functions across files | Partial | Partial (exact duplicates) | | Runs offline, no API key, deterministic core score | Yes | Yes |
Use a linter for correctness-of-form. Use this for "is this code real, or just plausible-looking." The two are complementary — run both.
When NOT to Use This
- You want style or formatting enforcement — use ruff / black / ESLint. This tool ignores style on purpose.
- You need a runtime correctness guarantee — a low score means cleaner structure, not that the code works. Keep your tests.
- Your code isn't Python, JS/TS, or Go — other languages aren't analyzed yet.
- You expect thresholds tuned to your repository — weights are the defaults or the values in your
.slopconfig.yaml; nothing tunes them automatically. Treat findings as leads, not verdicts.
60-Second First Run
No project-side config needed. Run it against any folder of Python:
pip install "ai-slop-detector>=3.13.0"
slop-detector --project . --json --output slop.json
python -c "import json; d=json.load(open('slop.json',encoding='utf-8')); print(d['overall_status'], d['weighted_deficit_score'])"
Expected output for a healthy project: clean 0.0 to clean 30.0. clean
describes the weighted deficit band only; inspect finding_summary,
scan_coverage, and ml_scoring before treating it as a complete project
assessment. Modules that hold only static data (literal_data_module) are
analyzed but carry no line weight in the weighted score;
scan_coverage.aggregation_exclusions lists them. The --output
form writes UTF-8 (no BOM) directly to disk, so it is safe under Windows
PowerShell — prefer it to > slop.json redirection.
Quick Start
pip install "ai-slop-detector>=3.13.0"
slop-detector scan . # canonical analysis entry
slop-detector review . --json # canonical changed-code review
slop-detector pulse . --json # canonical repo health view
slop-detector sweep dead-code . --json # canonical cleanup family
legacy / compatible surface
slop-detector --init # bootstrap baseline .slopconfig.yaml + .gitignore
slop-detector --init --adaptive-init --init-preview
slop-detector --init --adaptive-init --apply-init-suggestions
slop-detector mycode.py # single file
slop-detector --project ./src # entire project
slop-detector --project . --include-tests # include only default-excluded test files
slop-detector --project . --json --output slop.json # machine-readable output (Windows-safe)
slop-detector --project . --ci-mode hard --ci-report # CI gate
Optional extras
pip install "ai-slop-detector[js]" # JS/TS tree-sitter analysis
pip install "ai-slop-detector[go]" # Go tree-sitter analysis
npm wrapper (delegates to the Python CLI). With no Python backend installed
it runs the PyPI release of the same version through uvx or pipx.
npx ai-slop-detector --project .
npm install --save-dev ai-slop-detector
npx ai-slop-detector scan .
npx ai-slop-detector review . --format json
npx ai-slop-detector pulse . --format json
npx ai-slop-detector sweep dead-code . --format json
npx ai-slop-detector mcp
Typed output contract
import type { ReviewOutput, ScanOutput } from "ai-slop-detector/types"
Programmatic Node API
import { scanProject, reviewChanges, computeHealth, runCleanupFamily } from "ai-slop-detector"
Local impact story
slop-detector impact enable .
slop-detector impact .
Telemetry controls (default: off)
slop-detector telemetry status
slop-detector telemetry inspect --example
Local wrapper development
cd npm-wrapper
node ./bin/ai-slop-detector.js --version
No install required
uvx ai-slop-detector mycode.py
npx ai-slop-detector mycode.py # same, through the npm wrapper
The npm surface is intentionally thin:
- it does not reimplement analysis logic
- it delegates into the Python CLI/runtime
- it exists for Node-first teams that want
npx-style entry without changing
ai-slop-detector with the
same version as the PyPI release
- it discovers a backend in this order:
AI_SLOP_DETECTOR_EXECUTABLE->
VIRTUAL_ENV -> PATH executables -> python -m slop_detector.cli ->
uvx / pipx running the PyPI release of the wrapper's own version (uv
can also fetch Python), so npx works without a Python install when uv or
pipx is present
- it ships version-pinned TypeScript interfaces at
ai-slop-detector/types - it exports a small async Node API for
scanProject,reviewChanges,
computeHealth, and runCleanupFamily
Windows / PowerShell tip: PowerShell > redirection writes UTF-16 LE
or UTF-8 with BOM by default, which breaks json.load(..., encoding='utf-8').
Use --output instead — it writes UTF-8 bytes (no BOM) directly,
skipping the shell.
Verification Path
Use the same verification surface the repository exposes in CI:
pip install -e ".[dev]"
python -m pytest -q
ruff check src tests
python -m build
Governance verification is a separate enforcement gate:
slop-detector verify-governance ./.cr-ep
See docs/GOVERNANCE.md for the artifact contract and policy checks.
Operational review commands live on the same CLI entry point:
slop-detector review . --json
slop-detector pulse . --json
slop-detector sweep dead-code . --json
slop-detector sweep dupes . --json
slop-detector sweep unused-deps . --json
slop-detector sweep boundary-violations . --json
slop-detector watch . --follow
slop-detector explain dead-code
Legacy command forms such as audit, health, and direct cleanup-family names
remain supported for compatibility, but scan / review / pulse / sweep are the
preferred stable surface.
Cleanup-family semantics are now more operational than a raw candidate list:
dead-code,dupes,unused-deps,stale-suppressions, and
boundary-violations emit confidence, action_class, and evidence
unused-depsincludes project-manifest findings such as
manifest_unused_dependency and undeclared_import
boundary-violationsstays cycle-only by default and only enables layered
Agent tooling can use the same semantics over MCP stdio:
slop-detector mcp
or
slop-mcp
Adoption and observability surfaces are intentionally separate from scoring:
slop-detector impacttracks local, gitignored repository progress in
.slop-detector/impact.json
slop-detector telemetrystays default-off and only builds anonymized
AI_SLOP_DETECTOR_TELEMETRY=inspect prints a real payload without
queueing it
Scope And Boundaries
- This repository measures static code and documentation signals. It does not prove runtime correctness.
- The default scoring path is deterministic; it does not require an LLM or external API.
- JS/TS and Go support are optional extras with language-specific analyzers and fallbacks.
- Optional Rust acceleration is available for file discovery and glob matching when a compiled
rust/slop_scanhelper is present; otherwise the Python walker remains the fallback. - Narrow framework-aware masking is enabled for test boilerplate (pytest no-op hooks, test-harness console/no-op hooks in JS/TS) and never masks
criticalfindings. - A low deficit score is evidence of cleaner structure, not a guarantee that the code is complete or safe.
How It Works
flowchart LR
A[📄 Source File] --> R[FileRole\nClassifier]
R --> B[AST Parser]
B --> C[34 Pattern Checks]
B --> D[LDR · ICR · DDC\n+ Purity Metrics]
C --> E[GQG Scorer\nWeighted Geometric Mean]
D --> E
E --> F{deficit_score}
F -->|< 30| G[✅ CLEAN]
F -->|30–50| H[⚠️ SUSPICIOUS]
F -->|50–70| I[🔶 INFLATED_SIGNAL]
F -->|≥ 70| J[🚨 CRITICAL_DEFICIT]
E --> H2[history.db]
H2 --> K[Self-Calibrator\nadvisory report]
Every file goes through four independent measurement axes (LDR, ICR, DDC,
Purity) and 34 pattern checks. Results are combined via a **weighted
geometric mean** — a near-zero in any single dimension pulls the overall score
down regardless of other dimensions. Every scan is recorded to local history; --self-calibrate
turns that history into an advisory weight report and never writes weights.
Full specification: docs/HOW_IT_WORKS.md · docs/MATH_MODELS.md Agent usage: docs/AGENT_WORKFLOW.md
Document Map
Use the docs by task, not by chronology:
Core behavior
Verification and operations Calibration and history Interfaces- docs/CLI_USAGE.md
- docs/CONFIGURATION.md
- docs/CONFIG_EXAMPLES.md
- docs/CLAUDE_CODE_SKILL.md
- docs/API.md
What It Detects
**Registry and advanced pattern detectors across structural, placeholder, clone, dependency, and language-specific signals.** Full catalog: docs/PATTERNS.md
| Category | Patterns | Signal |
|---|---|---|
| Placeholder | empty_except, not_implemented, pass_placeholder, ellipsis_placeholder, return_none_placeholder, return_constant_stub, return_input_stub, todo_comment, fixme_comment, hack_comment, xxx_comment, interface_only_class | Unfinished / scaffolded code |
| Structural | bare_except, mutable_default_arg, star_import, global_statement, failure_as_success, ignored_parameter | Anti-patterns |
| Cross-Language | js_push, java_equals, ruby_each, go_println, csharp_length, php_strlen | Wrong-language syntax |
| Python Advanced | god_function, dead_code, deep_nesting, nested_complexity, lint_escape, exact_duplicate_pair, function_clone_cluster, placeholder_variable_naming | Structural complexity + evasion |
| Phantom | phantom_import, phantom_member | Hallucinated packages, and names an installed package does not define |
Four metric axes per file:
| Metric | What it measures |
|---|---|
| LDR (Logic Density Ratio) | logic_lines / total_lines — code vs. whitespace/comments |
| ICR (Inflation Check) | jargon_density × complexity_modifier — buzzword weight |
| DDC (Dependency Check) | used_imports / total_imports — import utilization |
| Purity | exp(-0.5 × n_critical_patterns) — AND-gate on critical pattern severity |
Scoring Model
purity = exp(-0.5 × n_critical_patterns)
quality (GQG) = exp( Σ wᵢ·ln(max(1e-4, dimᵢ)) / Σ wᵢ ) — weighted geometric mean
deficit_score = 100 × (1 − quality) + pattern_penalty
max(1e-4, ...) prevents log(0) = -inf from collapsing the entire score (v3.7.2 __post_init__ guards enforce upstream range invariants on metric results).
| Score | Status |
|---|---|
| ≥ 70 | CRITICAL_DEFICIT |
| ≥ 50 | INFLATED_SIGNAL |
| ≥ 30 | SUSPICIOUS |
| < 30 | CLEAN |
Default weights: ldr=0.40 · inflation=0.30 · ddc=0.20 · purity=0.10 — sum is 1.00; GQG divides by total_w so exact normalization is not required (all four appear in the advisory --self-calibrate report)
Project aggregation uses SR9 conservative weighting: 0.6 × min + 0.4 × mean
Full specification: docs/MATH_MODELS.md
Readable, actionable output
Scores are not a black box. Every project and file report renders each metric with its value, healthy direction, and a one-line what it means, plus a deficit-band legend:
Project Metrics
Metric Value Healthy What It Means
Average Deficit Score 6.1/100 Lower Mean file risk; 0 is clean, 100 is severe.
Logic Density Ratio (LDR) 95.36% Higher Share of code lines that contain real implementation.
Inflation-to-Code Ratio (ICR) 0.00x Lower Unjustified jargon vs. average cyclomatic complexity.
Dependency Usage Ratio (DDC) 86.00% Higher Imported libraries referenced by runtime code.
Deficit bands: CLEAN <30 | SUSPICIOUS 30-50 | INFLATED 50-70 | CRITICAL >=70
Each report ends with deterministic Next Steps — the top concern, the
recommended sweep command, and the highest-priority file to open — so analysis
turns directly into action. The same wording is shared by the rich, text, and
markdown renderers.
Per-file deficit_breakdown (v3.7.6)
Every per-file result in the JSON output also carries a deficit_breakdown
that attributes the score back to its source dimensions. This answers
"why is my clean-status file not 0.0?" without drilling into raw findings:
| Field | Meaning |
|---|---|
| ldr_penalty | Points of deficit attributable to low logic density |
| inflation_penalty | Points from unsupported claim words (docstring inflation is reported separately and is not scored) |
| ddc_penalty | Points from low import-usage ratio |
| purity_penalty | Points from critical-severity pattern hits via GQG |
| pattern_hits | Additive pattern penalty (post-cap) |
| total | Equals deficit_score (sum of the above when not capped at 100) |
GQG-dimension shares are computed via log-loss attribution — the sum of the
five penalty fields equals total within 0.01 when deficit_score < 100.
Structural Coherence Level
coherence_level in project-level JSON output reports how the
structural_coherence value was derived:
| Value | Meaning | When emitted |
|---|---|---|
| vr_structural | Vietoris-Rips H0 persistence over file DCFs (MST max edge) | At least two parsed Python files with non-empty DCFs |
| vr_structural_approx | Deterministic approximation of the same signal above the exact topology ceiling | At least two parsed Python files, with file count above advanced.exact_topology_ceiling |
| none | No coherence computed | Empty project, single file, or all files unparseable |
mst_persistence and not_applicable are not emitted — they were
proposed in the v3.7.5 audit but never wired in. Verify the actual value
from the JSON output rather than guessing.
Key Features
Bootstrap — domain-aware, one command to start
slop-detector --init # auto-detect domain, generate .slopconfig.yaml
slop-detector path/to/repo --init # same, for another directory (must exist)
slop-detector --init --domain web/api # explicit domain override
slop-detector --init --adaptive-init --init-preview
slop-detector --init --adaptive-init --apply-init-suggestions
--init detects your project domain from the imports of your own non-test
Python files (8 built-in profiles: general, scientific/ml,
scientific/numerical, web/api, library/sdk, cli/tool, bio,
finance). Common stacks (numpy, scipy, matplotlib, argparse) do not decide
the domain, and a tie is general. The profile sets pattern thresholds and
--init prints each threshold it changes; every profile keeps the default
weights. Also secures .slopconfig.yaml in .gitignore by default.
Adaptive init is now a separate safety layer:
--init --adaptive-init --init-preview
--init --adaptive-init --apply-init-suggestions
JS/TS Analysis — optional tree-sitter path
pip install "ai-slop-detector[js]"
slop-detector --project ./src # now includes .js/.jsx/.ts/.tsx files
Activates JSAnalyzer v2.8.0 with tree-sitter AST (regex fallback when not installed).
Results appear under js_file_results in ProjectAnalysis and JSON output.
The regex fallback cannot measure god functions, dead code or complexity;
scan_coverage.analysis_modes lists those checks as not_measured, and the
text, rich and markdown reports say so in one line.
Go Analysis — regex-based, optional tree-sitter-go path
pip install "ai-slop-detector[go]"
slop-detector --project ./src # now includes .go files
Activates GoAnalyzer v1.0.0. Detects: empty function stubs, panic() as error handling,
fmt.Println/Printf debug prints, _ = ignored errors, TODO/FIXME comments, god functions
(> 60 lines). Results appear under go_file_results in JSON output.
Self-Calibration — advisory weight report (Calibration v2)
slop-detector . --self-calibrate # advisory: what this project's history suggests
Learns ldr / inflation / ddc (purity is held) from the comparable runs of the
scanned project root only: same detector, engine and configuration. An
improvement needs a changed file whose metric-only score fell from the
SUSPICIOUS band; a flagged file left unchanged counts once. Pattern penalties
are never learned as weights. The report is advisory only: scans never
run it, and --apply-calibration is disabled (it writes nothing and exits 2).
History recorded before v3.10 has no measurement provenance and is reported as
legacy, not evidence (on one real database v1 learned 79% of its
"improvements" from files that had not changed).
docs/SELF_CALIBRATION.md → · Validation boundary →
History Tracking — longitudinal quality analysis
slop-detector mycode.py --show-history # per-file trend
slop-detector --history-trends # 7-day project aggregate
slop-detector --export-history data.jsonl
Every run auto-recorded to ~/.slop-detector/history.db. The history database is
the repository-local signal source for operational calibration and trend review.
docs/HISTORY_TRACKING.md →
Claude Code Integration
cp -r claude-skills/slop-detector ~/.claude/skills/
restart Claude Code, then use the canonical review surfaces below
Adds a persistent JSON-first review loop inside Claude Code:
review → inspect evidence → apply bounded fixes → pulse/sweep → human handoff.
| Command | What it does |
|---|---|
| slop-detector review . --format json | Changed-code review with verdict, attribution, targets, actions, and findings |
| slop-detector pulse . --format json | Health summary plus hotspot prioritization |
| slop-detector sweep | Cleanup planning for dead code, duplication, deps, stale suppressions, or boundaries |
| slop-detector scan . --format json | Full baseline analysis when the agent needs the complete core report |
| slop-detector verify-governance | Governance artifact verification kept separate from score output |
Recommended use:
- start with
reviewfor PR-like work - use
pulseto decide what to fix next - use
sweeponly for bounded cleanup families - re-run after each patch instead of assuming the first diagnosis still holds
- escalate to a human with evidence, not just a score
Local Calibration
AI-SLOP Detector can report how its weights would change from the history of one project root. This is a local operational aid: it is not a multi-repository data collection channel, a global profile injector, or evidence that the score is externally validated.
The report is bounded by comparable evidence (same measurement, same project
root), per-class minimums, a confidence gap, and the configuration boundary:
it never writes weights. Review a recommendation before changing
.slopconfig.yaml for a team workflow. See
docs/SELF_CALIBRATION.md for the supported flow and
docs/VALIDATION.md for the validation boundary.
Security Considerations
.slopconfig.yaml sensitivity
Your .slopconfig.yaml contains domain_overrides — a precise map of which functions
are exempt from complexity rules. This is effectively a codebase weakness surface:
it reveals which areas are too complex to refactor right now.
Best practice:
- Run
slop-detector --initto generate.slopconfig.yamland auto-add it to.gitignore - To share governance config with your team, explicitly remove
.slopconfig.yamlfrom.gitignore - Open-source repos committing it is fine (transparency over obscurity — see this project's own
.slopconfig.yaml)
history.db
History is stored at ~/.slop-detector/history.db (your home directory, outside all repos).
It is never committed and accumulates across all projects you scan.
Adversarial Validation (SPAR-Code) — ground-truth regression guard
fhval spar # 3-layer adversarial check
fhval spar --layer a # known-pattern anchors
fhval spar --layer c # existence probes
Verifies each metric is measuring what it claims. Catches calibration drift
before it reaches production.
Structural Coherence — project-level signal
project = detector.analyze_project("./src")
print(project.structural_coherence) # 0.0 – 1.0
Experimental. Use for longitudinal comparison within a project, not as an
absolute gate. Exact MST topology is used up to
advanced.exact_topology_ceiling (default 300 files); above that the engine
switches to a deterministic approximation and reports
coherence_level = "vr_structural_approx". docs/ARCHITECTURE.md →
Source held in memory
from slop_detector.core import SlopDetector
detector = SlopDetector()
result = detector.analyze_code_string(code) # no location
result = detector.analyze_code_string(code, "pkg/mod.py", project_root=".") # as that file
Without project_root the filename is only a label: no sibling module,
project root or dependency declaration is read, and an import the installed
environment cannot resolve (it may be local code) is listed in
unverified_imports instead of being reported. With project_root the
source is analyzed as analyze_file(project_root/filename, root=project_root)
would analyze it.
CI/CD Integration
pre-commit (runs on every commit):
# .pre-commit-config.yaml
repos:
- repo: https://github.com/flamehaven01/AI-SLOP-Detector
rev: v3.7.3
hooks:
- id: slop-detector # hard gate — fails on CRITICAL_DEFICIT >= 70
# - id: slop-detector-warn # soft mode — reports only, never blocks
# - id: slop-detector-patterns # fast per-file pattern scan
GitHub Actions (runs on every PR):
# .github/workflows/quality-gate.yml
- name: AI-SLOP Gate
run: |
pip install "ai-slop-detector>=3.7.3"
slop-detector --project . --ci-mode hard --ci-report
Enforcement modes:
--ci-mode soft # informational, never fails build
--ci-mode hard # fails: deficit_score >= 70, critical_patterns >= 3, inflation >= 1.5, ddc < 0.5
--ci-mode quarantine # escalates repeat offenders after 3 violations
Full CI/CD Integration Guide →
Configuration
# .slopconfig.yaml
weights:
ldr: 0.40
inflation: 0.30
ddc: 0.20
purity: 0.10
patterns:
god_function:
domain_overrides:
- function_pattern: "check_node" # AST walker — complex by design
complexity_threshold: 30
lines_threshold: 200
ignore:
- "tests/**"
- "**/__init__.py"
advanced:
exact_topology_ceiling: 300
topology_mode_above_ceiling: deterministic_approximate
analysis_cache_enabled: true
analysis_cache_db: ""
churn_commit_window: 200
coverage_data_file: ".coverage"
hotspot_limit: 10
hotspot_weights:
deficit: 0.50
churn: 0.30
coverage_gap: 0.20
architecture:
enabled: true
preset: layered
layers: []
Full Configuration Guide → · Config Examples →
Inline Suppression
Use inline suppression when a specific local exception is intentional and should remain visible in audit output:
# slop-disable-next-line bare_except
except:
pass
slop-disable all
def compatibility_layer():
...
slop-enable all
# slop-disable-next-line# slop-disable# slop-enable
Repeated-Run Cache
Repeated Python-file analysis can reuse prior results through the SQLite-backed metadata cache:
advanced:
analysis_cache_enabled: true
analysis_cache_db: "" # empty = default user cache under ~/.slop-detector/
- Cache keys include file path, size,
mtime, content hash, engine version, and config fingerprint. - A changed file or changed config invalidates only the affected entries.
- Current scope is Python file analysis reuse; project aggregation still recomputes from the live file set.
Priority Hotspots
Project scans now rank repair order instead of only printing static scores. The hotspot layer combines:
- deficit score
- recent git churn
- coverage gap from
.coverage
Agent Surface
The FastAPI server exposes an agent-native contract alongside the existing
human-oriented /analyze/* routes:
GET /agent/schemaPOST /agent/filePOST /agent/project
VS Code Extension
Real-time inline diagnostics, debounced lint-on-type, ML score and purity signal in status bar. v3.7.1 rebuilt from a single 855-line monolith into eight focused modules.
What you see:
| Surface | Detail |
|---|---|
| Status bar | $(error) SLOP 45.2 — severity icon + deficit score, updates on save |
| Inline diagnostics | Pattern issues with line references — phantom imports, god functions, lint escapes |
| TreeView sidebar | Activity bar panel: files sorted by deficit score, metric rows (LDR/DDC/Purity/Inflation), issue list with click-to-navigate |
| CodeLens | Line 0: file summary (SLOP 45.2 — 3 CRITICAL); per-function: top severity icon + pattern IDs |
| QuickFix (CodeAction) | Lightbulb on phantom_import/god_function/lint_escape diagnostics — show output or add to .slopconfig.yaml ignore |
| ML signal | ML: 73% [slop] in summary diagnostic when [ml] extra is installed |
Commands (Ctrl+Shift+P > "SLOP"):
| Command | Description |
|---|---|
| Analyze Current File | On-demand single-file scan |
| Analyze Workspace | Project-wide scan, populates TreeView |
| Show 4D Breakdown | Webview: penalty attribution — why a file is not 0.0 |
| Show Cleanup Plan | Webview: confidence-ranked sweep family (safe/needs/unsafe) |
| Show Pulse Dashboard | Webview: project health + priority hotspots |
| Show Changed-Code Review | Webview: diff-aware review, new-vs-inherited slop |
| Auto-Fix Detected Issues | Apply (or dry-run preview) auto-fixable patterns |
| Show Gate Decision (SNP) | PASS/HALT with sr9/di2/jsd/ove metrics |
| Run Cross-File Analysis | Dependency + clone graph across project |
| Show File History | Per-file deficit score trend |
| Show History Trends | 7-day project-wide daily trend table |
| Export History to JSONL | Dump history.db records for external analysis |
| Bootstrap .slopconfig.yaml | Domain-aware config generation (--init) |
| Run Self-Calibration | Advisory 4D weight report (weights are not applied) |
Install from the VS Code Marketplace or build locally:
cd vscode-extension
npm install
npx vsce package # produces vscode-slop-detector-3.7.3.vsix
code --install-extension vscode-slop-detector-3.7.3.vsix
Settings (slopDetector.*): pythonPath, lintOnSave, lintOnType,
failThreshold (default 50), warnThreshold (default 30), recordHistory, enableCodeLens (default true).
Release Highlights
| Version | Highlights |
|---|---|
| v3.13.0 | hold-out release: declared defaults (null objects, named bases, registered hooks, declared no-ops) are no longer placeholders; logged NotImplementedError stubs, constant success reports and discarded results are; blind hold-out v3 on 1119 functions from 47 public repositories: 19 genuine functions flagged, 3 of 4 hollow found, known limits listed; npm wrapper with provenance |
| v3.12.0 | detection release: stubs behind logging, unchanged-argument stubs, catch-all handlers returning the success result, ignored parameters; recall corpus 4/29 -> 18/29 hollow with 0/22 genuine flagged; external replay with every added finding read in the source; parity-checked fast replay and balanced parallel test runner for development |
| v3.11.0 | one engine release: CLI, MCP and REST run the same ScanService (CLI output unchanged over 70 compared cases); MCP and REST are read-only by default, REST history is opt-in, the REST server binds 127.0.0.1; agent responses carry measurement_state / score_valid / reason; findings and scores unchanged |
| v3.10.1 | correctness release: god_function counts logical lines and nesting rises only for if/for/while/except (corpus: -608 / -471 / -472 findings, none added); BOM files and single JS/TS/Go files analyzed; crashes fixed for git repositories under non-ASCII folders and project HTML reports; scan_coverage.measurement_state / score_valid / reason, NOT EVALUATED in human reports, --fail-threshold fails when every file failed; strict config validation before the run; working pre-commit hook installer; --inspector-read-only alias of --read-only |
| v3.10.0 | scan coverage counts unanalyzed code files, custom-rule exclusions, unevaluated tests and partially measured files, and names the checks a fallback analyzer could not run; --init keeps default weights, never writes overrides, merges without losing comments, detects the domain from project code only; import evidence reads Poetry, setup.py, setup.cfg, Pipfile, conda and nested declarations plus script-directory packages; Calibration v2 learns only from comparable, provenance-stamped history; placeholder, clone and inflation findings need evidence (declared intent, protocol methods, claim words only); one finding per bare except: pass, mutable_default_arg CRITICAL only with mutation evidence; static data modules leave the project line weight; analyze_code_string reads no implicit file system context; JS callback-hell locations and declarator names; one project walk and one AST walk per module (cold scans 2.6-3x faster); cache analysis-cache-v30. See CHANGELOG.md |
| v3.9.3 | precision correction from a 12-repository study: elif is not nesting (deep_nesting, nested_complexity); @overload / @typing.overload are not placeholders; no_executable_code modules skip LDR/DDC instead of scoring 97.49; failed analyses are counted in scan_coverage.failed with complete: false and a hard gate fails closed; one JS pass and valid --json --js output; scan_coverage.analysis_modes; cache analysis-cache-v25 |
| v3.9.2 | consistency and correctness release: one band set (CLEAN <30 / SUSPICIOUS / INFLATED_SIGNAL / CRITICAL_DEFICIT >=70) for files, projects, JS and Go; dependency_noise and parse_error are flags; docstrings count like comments in LDR and god_function; deep_nesting under nested_complexity charged once; exact duplicates by function body with an 8-node floor, shared by same-file and cross-file; import evidence from the scan root; byte-identical output across cache, hash seed, Rust helper and cwd; advisory-only self-calibration; unread config keys removed with a warning; --init honoured; cache analysis-cache-v24 |
| v3.9.1 | claim and path checkpoint: jargon counted only in comments and strings, never in import paths, identifiers, or leading license notices; claim evidence states structural / weak / absent / unmeasured with a per-claim support_level; justification only by structural library use; test evidence unmeasured outside test files; --ci-claims-strict keeps failing unmeasured integration evidence; root-relative path facts (fixes 0-file scans under a build/ checkout); phantom_member pattern |
| v3.9.0 | security and evidence release: no model is read from the working directory and nothing is unpickled; JSON ML model contract with one feature extractor for training and scoring; scan --read-only; import resolution that follows CPython (src layouts, namespace packages) with structure_evidence; async def placeholder findings; working --patterns-only / --disable and multi-file pre-commit hook;
... (README truncated for length)