stopslop
v1.2.0
Published
Static analysis for AI-generated JavaScript and TypeScript: find cognitive complexity, god classes/modules, duplicate code, dead code and orphan features.
Downloads
463
Maintainers
Keywords
Readme
StopSlop
Stop AI-generated code from becoming instant legacy.
Coding agents are good at making code compile. They are also good at making too much of it: tangled functions, god files, copied blocks, dead exports, and entire features that pass their own tests but are never connected to the application.
StopSlop is a static analyzer for JavaScript and TypeScript codebases under heavy AI-agent change. It catches that structural overproduction before it becomes permanent. It complements ESLint, Biome, Oxlint, SonarQube, and SonarCloud rather than repeating their style and type rules.
npx stopslop .It runs locally against JavaScript and TypeScript. No account. No source-code upload.
What it catches
- Tangled functions using cognitive complexity from the SonarSource specification.
- God classes and god modules that mix unrelated responsibilities or have grown into densely connected monoliths.
- Useful split boundaries for god units when cohesion exposes responsibility groups that should move together.
- Copy-pasted blocks, with optional identifier normalization to catch copies whose variables were renamed.
- Dead exports, dependencies, and files.
- Orphan features kept alive only by their own paired test but called by no production code.
- Files exporting multiple classes, a common sign that generation kept appending instead of designing.
Complexity and structural analysis are implemented by StopSlop. Clone detection uses Clone Alert; dead-code analysis uses Knip. StopSlop detects code patterns, not whether a human or an AI wrote them.
Start with one command
Run without installing:
npx stopslop .Or pin it in the project so local runs and CI use the same version:
npm install --save-dev stopslop
npx stopslop .
# pnpm
pnpm add -D stopslop
pnpm exec stopslop .
# Yarn
yarn add --dev stopslop
yarn stopslop .The default report gives you the full repository score, the signal densities,
and a grouped finding list. Add --details to show all clone locations and the
suggested split boundaries for god units; use --json for every finding.
stopslop 27 files 4.3 KLOC 980ms
slop level: low (22.3/100) · worse than 49% of reference repos
god units 0.00/KLOC · complex fns 4.42/KLOC · duplication 0.8%
baseline: 23 accepted · 0 new
✓ no new slop since baselineExit codes are made for CI: 0 means clear, 1 means findings, and 2 means
the analysis itself failed.
Adopt an existing codebase without cleaning it first
You do not need a perfect repository to start. Record today's findings once, commit them, and fail only when a change adds an unaccepted finding:
# Record accepted legacy.
npx stopslop . \
--baseline .stopslop-baseline.json \
--update-baseline
git add .stopslop-baseline.json
# Use the same file as the CI gate.
npx stopslop . --baseline .stopslop-baseline.jsonThe baseline is a sorted, reviewable list of finding identities. Updating it is an explicit acceptance of debt, so baseline changes should be reviewed like source changes.
The baseline never lowers the score. These three surfaces answer different questions:
| Surface | What it tells you | | --- | --- | | Findings | What needs attention in this run | | Gate | Whether any finding remains unaccepted | | Score | How much slop the complete repository carries, including accepted legacy |
A baselined run therefore remains honest: the report can show
moderate (43/100), 30 accepted, and 0 new while the gate is green.
Put the gate in your README
The badge is deliberately binary. It is a public CI status, not a miniature analytics report.
AI slop | clear 0 unaccepted findings
AI slop | detected 1 or more unaccepted findingsGenerate the Shields endpoint JSON with the same baseline used by CI:
npx stopslop . \
--baseline .stopslop-baseline.json \
--format shields > stopslop-badge.jsonCommit or publish stopslop-badge.json, then add this Markdown:
[](https://github.com/BaryshevRS/stopslop)Replace OWNER/REPO in the image URL with your repository. Keep the outer link
unchanged: clicking the badge takes readers to StopSlop.
Without --baseline or --base, clear requires zero findings across the
entire scan. With a baseline, it requires zero findings outside the committed
baseline. The full score is still present in the terminal and JSON reports.
--format shields exits 0 for both clear and detected so a publishing step
can write the current state. An incomplete or failed analysis exits 2 before
emitting a badge.
Gate pull requests
Use a committed baseline for a stable, explicitly reviewed allowlist:
- name: Reject new AI slop
run: npx stopslop . --baseline .stopslop-baseline.jsonOr compare the working tree directly with the target branch and keep no baseline file:
npx stopslop . --base origin/main--base analyzes both revisions and reports findings that exist only in the
current tree. CI must check out enough Git history to resolve the base revision.
The command never switches your branch or changes the index.
For GitHub Code Scanning, emit SARIF before propagating the gate failure:
npx stopslop . --base origin/main --format sarif > stopslop.sarifEvery rule in the SARIF report carries its help, so the alert says why the finding matters and how to fix it, not only what failed.
See CI and automation for a complete workflow that uploads SARIF and still preserves StopSlop's exit code.
Catch slop while the agent is still working
A check that first runs on the pull request reports code nobody remembers writing. StopSlop can run inside the coding session instead, while the code is still in front of the agent.
This repository is a plugin for Claude Code, Codex, and Cursor and an extension
for Gemini CLI. Each agent gets the stopslop skill, which covers how to run the
check, read the report, and fix each kind of finding without gaming it: no
raised thresholds, no ignore globs, no baseline edits on the agent's own
initiative. Where the agent supports it, the install also adds a stop hook.
| Agent | Install | Stop hook |
| --- | --- | --- |
| Claude Code | /plugin marketplace add BaryshevRS/stopslop/plugin install stopslop@stopslop | included |
| Codex | codex plugin marketplace add BaryshevRS/stopslopcodex plugin add stopslop@stopslop | included; trust it when Codex asks at session start |
| Cursor | Customize → import BaryshevRS/stopslop from a GitHub repository | included |
| Gemini CLI | gemini extensions install https://github.com/BaryshevRS/stopslop | in project settings |
| Others | npx skills add BaryshevRS/stopslop (skill only) | — |
Before the agent ends a turn that changed JS/TS source, the hook checks the
uncommitted changes against HEAD, or against .stopslop-baseline.json when
the project has one, and hands any new findings back with how to fix each kind.
Legacy never blocks a turn, a turn that changed no source costs one
git status, and the agent gets one nudge per turn: accepting a finding stays
your decision. The hook runs the project's pinned stopslop from node_modules
and falls back to npx stopslop@1.
The hook also ships in the npm package, so a project that pins stopslop can
commit it to its own agent settings and give it to every contributor; see
Commit the hook to the project.
Any agent or script. The JSON report is stable and versioned. Feed it into the next turn instead of paraphrasing terminal output:
npx stopslop . --base HEAD --json > slop-report.jsonEach finding carries kind, file, line, and message, and rules says for
each reported kind why it matters and what resolves it: the same text as
docs/rules.md and the rule help in GitHub Code Scanning.
God-unit findings include member groups that serve as concrete refactoring
boundaries. Clone findings include every occurrence. Baseline and Git-base runs
retain the complete score while returning only new findings.
Score: trend the repository, do not gate on the number
The 0..100 slop score normalizes findings by KLOC, so a repository can be
compared with itself over time instead of merely growing a larger raw count. It
combines four densities: god units, duplication, over-complex functions, and
dead code.
Floors and budgets are anchored in a benchmark of 41 mature TypeScript repositories totaling 9.4 MLOC. The weights remain an explicit judgement call, not a scientific truth. That is why the binary gate uses findings rather than an arbitrary score cutoff.
The complete formula, calibration rationale, limitations, and corpus comparison are in docs/score.md and docs/thresholds.md.
CLI
stopslop [path] analyze a directory or file (default: .)
--format <fmt> text (default) · json · sarif · shields
--json alias for --format json
--details, -d show split groups, hubs, and all clone locations
--fast skip clone and dead-code analysis
--config <file> use a specific stopslop.json
--base <ref> gate on findings absent from a Git revision
--baseline <file> gate on findings absent from a baseline file
--update-baseline replace the baseline with current findings
--help, -h show helpInvalid configuration, no supported source files, and hard analysis errors exit
with code 2 before StopSlop emits a report, badge, or baseline.
Configuration
StopSlop works without configuration. To tune a check, add stopslop.json:
{
"$schema": "https://unpkg.com/stopslop/schema.json",
"cognitiveComplexity": 20,
"duplicates": {
"minTokens": 100,
"minLines": 10,
"ignoreIdentifiers": true
},
"ignore": ["**/*.generated.ts"]
}Every field is optional. Set a check to false to disable it. The published
schema provides editor completion and rejects unknown or invalid properties.
See docs/configuration.md for every field, default, and Knip workspace example. See stopslop.example.json for a complete configuration.
Dead-code configuration
Knip settings go under knip in stopslop.json. A project's own knip.json,
.knip.json, knip.config.* and package.json#knip are never read, so an
analysis does not change with whatever happens to sit in the directory.
Everything in Knip's configuration reference
works there unchanged.
You do not have to work out what belongs there. Run the analysis and Knip reports what its configuration is missing — which workspace it never reached, which declared entry point resolved to nothing — under config hints, with the file count each one accounts for:
config hints (from Knip)
packages/core · 12 files
Add entry and/or refine project files in workspaces["packages/core"] (12 unused files)
→ Knip settings live under `knip` in stopslop.json (knip.json is not read)
reference: https://knip.dev/reference/configurationAddress them, run again, repeat until they are gone. Until then dead code is
reported but left out of the score, because a run without entry points reports
live files as unreachable. The same list is in configHints of --format json.
Node.js API
StopSlop ships ESM JavaScript and TypeScript declarations for programmatic use:
import { analyze, loadConfig } from 'stopslop';
const root = process.cwd();
const result = await analyze(root, loadConfig(root));
console.log(result.slop.score, result.findings);The API also exports the resolved defaults, scoring helpers, baseline helpers, and report types. See the Node.js API guide for Git-base analysis, typed configuration, result semantics, and custom analyzers. The API is experimental and may change in a minor release; see COMPATIBILITY.md.
Design and limitations
- StopSlop supports
.js,.jsx,.mjs,.cjs,.ts, and.tsxsource. - Tests, generated code, build output, dependencies, and common framework output directories are excluded by default.
- A high score does not prove poor engineering, and a low score does not prove correctness. StopSlop measures structural overproduction, not total quality.
- Dead-code accuracy depends on Knip knowing the project's entry points; the run says so through config hints when it does not.
- A baseline accepts finding identities. If an accepted function becomes more complex without changing identity, the score rises but the gate stays clear.
Architecture, research references, and the boundary between published methods and StopSlop heuristics are documented in ARCH.md.
FAQ
Is StopSlop an AI-generated code detector?
No. It does not guess who wrote a line of code. It finds structural waste that coding agents often produce—duplicate blocks, over-complex functions, god classes and modules, dead code, and orphan features—and reports the same issue when a human wrote it.
How is StopSlop different from ESLint, SonarQube, SonarCloud, or Knip?
ESLint and similar linters focus on local rules. SonarQube and SonarCloud are broad quality platforms. Knip specializes in unused code and dependencies. StopSlop is a local, zero-account AI code quality gate focused on structural overproduction. It combines several signals into one report and keeps accepted legacy visible through its score while blocking only new findings through a baseline or Git comparison. Knip and Clone Alert power two of those signals.
Can I add StopSlop to a legacy repository?
Yes. Generate .stopslop-baseline.json, review and commit it, then use that same
file in CI. Existing findings remain in the score and report as accepted debt;
only unaccepted findings fail the gate.
Does StopSlop support monorepos and CI code scanning?
Yes. Configure Knip workspaces under stopslop.json#knip.workspaces for accurate
dead-code analysis in a monorepo. Use SARIF for GitHub Code Scanning, JSON for
automation and coding agents, or the Shields format for a README status badge.
Development
pnpm install
pnpm test
pnpm typecheck
pnpm check:clones
pnpm build
pnpm test:package
pnpm release:checkMIT licensed. See LICENSE.
