stopslop 0.1.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMPATIBILITY.md +26 -0
- package/README.md +247 -247
- package/context7.json +21 -0
- package/dist/cli.js +10 -19
- package/dist/src.js +0 -0
- package/docs/ci-and-automation.md +100 -0
- package/docs/configuration.md +252 -0
- package/docs/node-api.md +114 -0
- package/docs/score.md +133 -0
- package/docs/thresholds.md +105 -0
- package/llms.txt +25 -0
- package/package.json +56 -8
package/COMPATIBILITY.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Compatibility
|
|
2
|
+
|
|
3
|
+
Last updated: 0.2.0 (2026-08-05)
|
|
4
|
+
|
|
5
|
+
StopSlop is pre-1.0. Public surfaces are usable, but none are declared stable
|
|
6
|
+
yet. Breaking preview changes require a minor release and are called out in the
|
|
7
|
+
changelog. Experimental surfaces may change in any release.
|
|
8
|
+
|
|
9
|
+
| Surface | Tier | Compatibility policy |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| CLI flags and exit codes | Preview | Existing flags and exit meanings are preserved within a minor line. |
|
|
12
|
+
| Versioned JSON report | Preview | Breaking payload changes increment `schemaVersion`. |
|
|
13
|
+
| SARIF 2.1.0 report | Preview | New rules and optional properties may be added; existing required fields remain compatible. |
|
|
14
|
+
| `stopslop.json` schema | Preview | Existing valid configuration remains valid within a minor line. |
|
|
15
|
+
| Programmatic Node.js API | Experimental | Exported functions and TypeScript types may change before 1.0. |
|
|
16
|
+
|
|
17
|
+
## Runtime support
|
|
18
|
+
|
|
19
|
+
- Node.js 20.19+, 22.12+, and 24.
|
|
20
|
+
- ESM only.
|
|
21
|
+
- Git-base gating requires the `git` executable and a non-shallow checkout that
|
|
22
|
+
contains the requested base revision.
|
|
23
|
+
|
|
24
|
+
## Deprecations
|
|
25
|
+
|
|
26
|
+
None.
|
package/README.md
CHANGED
|
@@ -1,331 +1,331 @@
|
|
|
1
|
-
#
|
|
1
|
+
# StopSlop
|
|
2
2
|
|
|
3
|
-
[](https://github.com/BaryshevRS/stopslop)
|
|
3
|
+
[](https://github.com/BaryshevRS/stopslop) [](https://github.com/BaryshevRS/clone-alert) [](https://github.com/BaryshevRS/stopslop/actions/workflows/ci.yml) [](https://www.npmjs.com/package/stopslop)
|
|
4
|
+
<!-- clone-alert badge: pnpm exec clone-alert --minimum-tokens 100 --files src --format shields --no-fail-on-violation -->
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
> tiers and the pre-1.0 change policy are documented in
|
|
7
|
-
> [COMPATIBILITY.md](COMPATIBILITY.md). Requires Node.js 20.19+ or 22.12+.
|
|
6
|
+
**Stop AI-generated code from becoming instant legacy.**
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
Coding agents are good at making code compile. They are also good at making too
|
|
9
|
+
much of it: tangled functions, god files, copied blocks, dead exports, and entire
|
|
10
|
+
features that pass their own tests but are never connected to the application.
|
|
11
|
+
|
|
12
|
+
StopSlop is a static analyzer for JavaScript and TypeScript codebases under
|
|
13
|
+
heavy AI-agent change. It catches that structural overproduction before it
|
|
14
|
+
becomes permanent. It complements ESLint, Biome, Oxlint, SonarQube, and
|
|
15
|
+
SonarCloud rather than repeating their style and type rules.
|
|
15
16
|
|
|
16
17
|
```bash
|
|
17
18
|
npx stopslop .
|
|
18
19
|
```
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
It runs locally against JavaScript and TypeScript. No account. No source-code
|
|
22
|
+
upload.
|
|
23
|
+
|
|
24
|
+
## What it catches
|
|
25
|
+
|
|
26
|
+
- **Tangled functions** using cognitive complexity from the SonarSource
|
|
27
|
+
specification.
|
|
28
|
+
- **God classes and god modules** that mix unrelated responsibilities or have
|
|
29
|
+
grown into densely connected monoliths.
|
|
30
|
+
- **Useful split boundaries** for god units when cohesion exposes responsibility
|
|
31
|
+
groups that should move together.
|
|
32
|
+
- **Copy-pasted blocks**, with optional identifier normalization to catch copies
|
|
33
|
+
whose variables were renamed.
|
|
34
|
+
- **Dead exports, dependencies, and files.**
|
|
35
|
+
- **Orphan features** kept alive only by their own paired test but called by no
|
|
36
|
+
production code.
|
|
37
|
+
- **Files exporting multiple classes**, a common sign that generation kept
|
|
38
|
+
appending instead of designing.
|
|
39
|
+
|
|
40
|
+
Complexity and structural analysis are implemented by StopSlop. Clone detection
|
|
41
|
+
uses [Clone Alert](https://www.npmjs.com/package/clone-alert); dead-code analysis
|
|
42
|
+
uses [Knip](https://knip.dev). StopSlop detects code patterns, not whether a human
|
|
43
|
+
or an AI wrote them.
|
|
44
|
+
|
|
45
|
+
## Start with one command
|
|
46
|
+
|
|
47
|
+
Run without installing:
|
|
21
48
|
|
|
22
49
|
```bash
|
|
23
|
-
|
|
24
|
-
pnpm exec stopslop .
|
|
50
|
+
npx stopslop .
|
|
25
51
|
```
|
|
26
52
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- **Cognitive complexity** — functions that are hard to follow, scored by the
|
|
30
|
-
G. Ann Campbell / SonarSource specification.
|
|
31
|
-
- **God class** — flagged on either of two axes: **dispersion** (methods split
|
|
32
|
-
into several unrelated responsibility groups — LCOM4 cohesion + hub removal,
|
|
33
|
-
with clean split boundaries) or **size/WMC** (a densely-connected monolith too
|
|
34
|
-
large to be one unit — the "everything is honestly connected but it's 2k lines"
|
|
35
|
-
class an agent produces, which cohesion can't and shouldn't split).
|
|
36
|
-
- **God module** — the same, applied to a file's top-level definitions: a
|
|
37
|
-
"dump" of unrelated functions/classes/constants.
|
|
38
|
-
- **Multiple exported classes** in one file.
|
|
39
|
-
- **Clones** — copy-pasted blocks, *including ones whose variables were renamed*
|
|
40
|
-
(identifiers are normalized before matching). Engine:
|
|
41
|
-
[Clone Alert](https://www.npmjs.com/package/clone-alert).
|
|
42
|
-
- **Dead code** — unused exports, unused dependencies, unreachable files. Engine:
|
|
43
|
-
[Knip](https://knip.dev), driven through its programmatic session API and
|
|
44
|
-
configured exclusively under `stopslop.json#knip`.
|
|
45
|
-
- **Orphan features** — a function no production code calls, kept alive by
|
|
46
|
-
nothing but its own paired test. A feature that was generated together with
|
|
47
|
-
its test and never wired in: the test is green, the code does nothing. Read
|
|
48
|
-
off Knip's module graph; the finding names both halves to delete.
|
|
49
|
-
|
|
50
|
-
The last two checks are other people's engines, and we say so in the output.
|
|
51
|
-
Everything above them is ours.
|
|
52
|
-
|
|
53
|
-
Each god finding lists the responsibility groups by member name — ready-made
|
|
54
|
-
refactor boundaries.
|
|
53
|
+
Or pin it in the project so local runs and CI use the same version:
|
|
55
54
|
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
```bash
|
|
56
|
+
npm install --save-dev stopslop
|
|
57
|
+
npx stopslop .
|
|
58
|
+
|
|
59
|
+
# pnpm
|
|
60
|
+
pnpm add -D stopslop
|
|
61
|
+
pnpm exec stopslop .
|
|
62
|
+
|
|
63
|
+
# Yarn
|
|
64
|
+
yarn add --dev stopslop
|
|
65
|
+
yarn stopslop .
|
|
63
66
|
```
|
|
64
67
|
|
|
65
|
-
|
|
68
|
+
The default report gives you the full repository score, the signal densities,
|
|
69
|
+
and a grouped finding list. Add `--details` to show all clone locations and the
|
|
70
|
+
suggested split boundaries for god units; use `--json` for every finding.
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
```text
|
|
73
|
+
stopslop 27 files 4.3 KLOC 980ms
|
|
69
74
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
slop level: low (22.3/100) · worse than 49% of reference repos
|
|
76
|
+
god units 0.00/KLOC · complex fns 4.42/KLOC · duplication 0.8%
|
|
77
|
+
baseline: 23 accepted · 0 new
|
|
78
|
+
|
|
79
|
+
✓ no new slop since baseline
|
|
73
80
|
```
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
living codebase — not slop) to a *budget* (the density at which the signal is as
|
|
78
|
-
bad as it gets), then saturates, and is weighted:
|
|
82
|
+
Exit codes are made for CI: `0` means clear, `1` means findings, and `2` means
|
|
83
|
+
the analysis itself failed.
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
score = 100 · Σ wᵢ · sat(xᵢ) / Σ wᵢ (i = enabled signals)
|
|
82
|
-
sat(x) = clamp((x − floorᵢ) / (budgetᵢ − floorᵢ), 0, 1)
|
|
83
|
-
```
|
|
85
|
+
## Adopt an existing codebase without cleaning it first
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
| god units | (god modules + god classes) / KLOC | 0.35 | 0 | 0.1 |
|
|
88
|
-
| duplication | duplicated lines / total lines | 0.25 | 0.05 | 0.30 |
|
|
89
|
-
| complexity | over-complex functions / KLOC | 0.20 | 0 | 4 |
|
|
90
|
-
| dead code | (unused exports + deps + files) / KLOC | 0.20 | 0 | 4 |
|
|
87
|
+
You do not need a perfect repository to start. Record today's findings once,
|
|
88
|
+
commit them, and fail only when a change adds an unaccepted finding:
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
```bash
|
|
91
|
+
# Record accepted legacy.
|
|
92
|
+
npx stopslop . \
|
|
93
|
+
--baseline .stopslop-baseline.json \
|
|
94
|
+
--update-baseline
|
|
97
95
|
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
git add .stopslop-baseline.json
|
|
97
|
+
|
|
98
|
+
# Use the same file as the CI gate.
|
|
99
|
+
npx stopslop . --baseline .stopslop-baseline.json
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
[docs/score.md](docs/score.md). **The densities are the facts; the score is an
|
|
106
|
-
aggregate.**
|
|
102
|
+
The baseline is a sorted, reviewable list of finding identities. Updating it is
|
|
103
|
+
an explicit acceptance of debt, so baseline changes should be reviewed like
|
|
104
|
+
source changes.
|
|
107
105
|
|
|
108
|
-
|
|
106
|
+
The baseline never lowers the score. These three surfaces answer different
|
|
107
|
+
questions:
|
|
109
108
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
| Surface | What it tells you |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| Findings | What needs attention in this run |
|
|
112
|
+
| Gate | Whether any finding remains unaccepted |
|
|
113
|
+
| Score | How much slop the complete repository carries, including accepted legacy |
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
```
|
|
115
|
+
A baselined run therefore remains honest: the report can show
|
|
116
|
+
`moderate (43/100)`, `30 accepted`, and `0 new` while the gate is green.
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
[](https://github.com/OWNER/REPO)
|
|
120
|
-
```
|
|
118
|
+
## Put the gate in your README
|
|
121
119
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
fixed scale, tuned to reward near-zero slop:
|
|
125
|
-
|
|
126
|
-
| Result | Color | |
|
|
127
|
-
| --- | --- | --- |
|
|
128
|
-
| **`0 slop`** | 🟢 bright green | the flex — nothing to clean up |
|
|
129
|
-
| **`clean` <10** | 🟢 bright green | clean |
|
|
130
|
-
| **`low` <25** | 🟢 green | a little debt |
|
|
131
|
-
| **`moderate` <50** | 🟡 yellow | has debt |
|
|
132
|
-
| **`high` <75** | 🟠 orange | needs attention |
|
|
133
|
-
| **`legacy-grade` ≥75** | 🔴 red | reads like accumulated legacy |
|
|
134
|
-
|
|
135
|
-
`--format shields` always exits `0` (it is badge generation, not a gate), so a CI
|
|
136
|
-
step can regenerate it without failing the build. A score built from a subset of
|
|
137
|
-
the signals (a check was disabled or bailed out) is inflated, so its message is
|
|
138
|
-
marked with a `*` — run the full set for a comparable badge. Regenerate it in CI
|
|
139
|
-
to keep it fresh:
|
|
120
|
+
The badge is deliberately binary. It is a public CI status, not a miniature
|
|
121
|
+
analytics report.
|
|
140
122
|
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
|
|
123
|
+
```text
|
|
124
|
+
AI slop | clear 0 unaccepted findings
|
|
125
|
+
AI slop | detected 1 or more unaccepted findings
|
|
144
126
|
```
|
|
145
127
|
|
|
146
|
-
|
|
128
|
+
Generate the [Shields endpoint JSON](https://shields.io/badges/endpoint-badge)
|
|
129
|
+
with the same baseline used by CI:
|
|
147
130
|
|
|
131
|
+
```bash
|
|
132
|
+
npx stopslop . \
|
|
133
|
+
--baseline .stopslop-baseline.json \
|
|
134
|
+
--format shields > stopslop-badge.json
|
|
148
135
|
```
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
--config <file> path to a stopslop.json
|
|
155
|
-
--base <ref> gate only on findings absent from this Git revision
|
|
156
|
-
--baseline <file> gate only on findings not in this baseline
|
|
157
|
-
--update-baseline write current findings to --baseline and exit 0
|
|
136
|
+
|
|
137
|
+
Commit or publish `stopslop-badge.json`, then add this Markdown:
|
|
138
|
+
|
|
139
|
+
```md
|
|
140
|
+
[](https://github.com/BaryshevRS/stopslop)
|
|
158
141
|
```
|
|
159
142
|
|
|
160
|
-
|
|
143
|
+
Replace `OWNER/REPO` in the image URL with your repository. Keep the outer link
|
|
144
|
+
unchanged: clicking the badge takes readers to StopSlop.
|
|
145
|
+
|
|
146
|
+
Without `--baseline` or `--base`, `clear` requires zero findings across the
|
|
147
|
+
entire scan. With a baseline, it requires zero findings outside the committed
|
|
148
|
+
baseline. The full score is still present in the terminal and JSON reports.
|
|
149
|
+
|
|
150
|
+
`--format shields` exits `0` for both `clear` and `detected` so a publishing step
|
|
151
|
+
can write the current state. An incomplete or failed analysis exits `2` before
|
|
152
|
+
emitting a badge.
|
|
153
|
+
|
|
154
|
+
## Gate pull requests
|
|
161
155
|
|
|
162
|
-
|
|
163
|
-
exit `2` before StopSlop emits a report, badge, or baseline.
|
|
156
|
+
Use a committed baseline for a stable, explicitly reviewed allowlist:
|
|
164
157
|
|
|
165
|
-
|
|
158
|
+
```yaml
|
|
159
|
+
- name: Reject new AI slop
|
|
160
|
+
run: npx stopslop . --baseline .stopslop-baseline.json
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Or compare the working tree directly with the target branch and keep no baseline
|
|
164
|
+
file:
|
|
166
165
|
|
|
167
166
|
```bash
|
|
168
|
-
stopslop
|
|
167
|
+
npx stopslop . --base origin/main
|
|
169
168
|
```
|
|
170
169
|
|
|
171
|
-
|
|
170
|
+
`--base` analyzes both revisions and reports findings that exist only in the
|
|
171
|
+
current tree. CI must check out enough Git history to resolve the base revision.
|
|
172
|
+
The command never switches your branch or changes the index.
|
|
172
173
|
|
|
173
|
-
For
|
|
174
|
-
given Git revision, then reports only findings absent from the base:
|
|
174
|
+
For GitHub Code Scanning, emit SARIF before propagating the gate failure:
|
|
175
175
|
|
|
176
|
-
```
|
|
177
|
-
stopslop . --base origin/main
|
|
178
|
-
stopslop . --base origin/main --format sarif > stopslop.sarif
|
|
176
|
+
```bash
|
|
177
|
+
npx stopslop . --base origin/main --format sarif > stopslop.sarif
|
|
179
178
|
```
|
|
180
179
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
directory. StopSlop links the installed dependency context (`node_modules` and
|
|
184
|
-
supported generated directories) into that checkout, analyzes the same project
|
|
185
|
-
subdirectory on both sides, and removes the temporary worktree in `finally`.
|
|
186
|
-
It never switches the user's branch or edits the index or working files.
|
|
180
|
+
See [CI and automation](docs/ci-and-automation.md) for a complete workflow that
|
|
181
|
+
uploads SARIF and still preserves StopSlop's exit code.
|
|
187
182
|
|
|
188
|
-
|
|
189
|
-
StopSlop fingerprint is accepted; a fingerprint seen only in the current tree is
|
|
190
|
-
new. The full current slop score remains unchanged. `--base` is mutually
|
|
191
|
-
exclusive with `--baseline` and `--update-baseline`.
|
|
183
|
+
## Send the findings back to the agent
|
|
192
184
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
re-failing the job:
|
|
185
|
+
The JSON report is stable and versioned. Feed it into the next coding-agent turn
|
|
186
|
+
instead of paraphrasing terminal output:
|
|
196
187
|
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
on: pull_request
|
|
200
|
-
|
|
201
|
-
permissions:
|
|
202
|
-
contents: read
|
|
203
|
-
security-events: write
|
|
204
|
-
|
|
205
|
-
jobs:
|
|
206
|
-
stopslop:
|
|
207
|
-
runs-on: ubuntu-latest
|
|
208
|
-
steps:
|
|
209
|
-
- uses: actions/checkout@v6
|
|
210
|
-
with:
|
|
211
|
-
fetch-depth: 0
|
|
212
|
-
- id: scan
|
|
213
|
-
continue-on-error: true
|
|
214
|
-
run: npx stopslop . --base "origin/${{ github.base_ref }}" --format sarif > stopslop.sarif
|
|
215
|
-
- if: always() && steps.scan.outcome != 'cancelled'
|
|
216
|
-
uses: github/codeql-action/upload-sarif@v4
|
|
217
|
-
with:
|
|
218
|
-
sarif_file: stopslop.sarif
|
|
219
|
-
category: stopslop
|
|
220
|
-
- if: steps.scan.outcome == 'failure'
|
|
221
|
-
run: exit 1
|
|
188
|
+
```bash
|
|
189
|
+
npx stopslop . --json > slop-report.json
|
|
222
190
|
```
|
|
223
191
|
|
|
224
|
-
|
|
192
|
+
God-unit findings include member groups that serve as concrete refactoring
|
|
193
|
+
boundaries. Clone findings include every occurrence. Baseline and Git-base runs
|
|
194
|
+
retain the complete score while returning only new findings.
|
|
225
195
|
|
|
226
|
-
|
|
227
|
-
you accept today's findings as legacy and gate CI only on what is added
|
|
228
|
-
afterwards — the honest answer to "this codebase is old and complex, but I don't
|
|
229
|
-
want the agent making it *worse*."
|
|
196
|
+
## Score: trend the repository, do not gate on the number
|
|
230
197
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
198
|
+
The `0..100` slop score normalizes findings by KLOC, so a repository can be
|
|
199
|
+
compared with itself over time instead of merely growing a larger raw count. It
|
|
200
|
+
combines four densities: god units, duplication, over-complex functions, and
|
|
201
|
+
dead code.
|
|
234
202
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
203
|
+
Floors and budgets are anchored in a benchmark of 41 mature TypeScript
|
|
204
|
+
repositories totaling 9.4 MLOC. The weights remain an explicit judgement call,
|
|
205
|
+
not a scientific truth. That is why the binary gate uses findings rather than an
|
|
206
|
+
arbitrary score cutoff.
|
|
207
|
+
|
|
208
|
+
The complete formula, calibration rationale, limitations, and corpus comparison
|
|
209
|
+
are in [docs/score.md](docs/score.md) and
|
|
210
|
+
[docs/thresholds.md](docs/thresholds.md).
|
|
238
211
|
|
|
239
|
-
|
|
240
|
-
A finding's identity is `kind + file + symbol` — no line numbers, no messages — so
|
|
241
|
-
a baselined god class stays accepted as it grows, and the file produces a stable,
|
|
242
|
-
churn-free diff. Clones key on their content fingerprint alone, so they stay
|
|
243
|
-
suppressed even when moved between files. Re-run `--update-baseline` to re-adopt
|
|
244
|
-
after an intentional change.
|
|
212
|
+
## CLI
|
|
245
213
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
214
|
+
```text
|
|
215
|
+
stopslop [path] analyze a directory or file (default: .)
|
|
216
|
+
|
|
217
|
+
--format <fmt> text (default) · json · sarif · shields
|
|
218
|
+
--json alias for --format json
|
|
219
|
+
--details, -d show split groups, hubs, and all clone locations
|
|
220
|
+
--fast skip clone and dead-code analysis
|
|
221
|
+
--config <file> use a specific stopslop.json
|
|
222
|
+
--base <ref> gate on findings absent from a Git revision
|
|
223
|
+
--baseline <file> gate on findings absent from a baseline file
|
|
224
|
+
--update-baseline replace the baseline with current findings
|
|
225
|
+
--help, -h show help
|
|
226
|
+
```
|
|
250
227
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
only a CI gate ("don't add more"). A gated run reports the new findings and its
|
|
254
|
-
score line reads e.g. `moderate (43.0/100)` with `baseline: 30 accepted · 1 new`
|
|
255
|
-
below it — the number stays truthful while CI fails only on the one new finding.
|
|
228
|
+
Invalid configuration, no supported source files, and hard analysis errors exit
|
|
229
|
+
with code `2` before StopSlop emits a report, badge, or baseline.
|
|
256
230
|
|
|
257
|
-
##
|
|
231
|
+
## Configuration
|
|
258
232
|
|
|
259
|
-
|
|
260
|
-
`false`. All fields are optional and merged over the defaults.
|
|
233
|
+
StopSlop works without configuration. To tune a check, add `stopslop.json`:
|
|
261
234
|
|
|
262
235
|
```json
|
|
263
236
|
{
|
|
264
237
|
"$schema": "https://unpkg.com/stopslop/schema.json",
|
|
265
|
-
"cognitiveComplexity":
|
|
266
|
-
"
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
"deadCode": { "exports": true, "dependencies": true, "files": true, "production": false },
|
|
271
|
-
"knip": {
|
|
272
|
-
"workspaces": {
|
|
273
|
-
".": { "entry": ["src/index.ts"] },
|
|
274
|
-
"packages/*": { "entry": ["src/index.ts"], "project": ["src/**/*.ts"] }
|
|
275
|
-
}
|
|
238
|
+
"cognitiveComplexity": 20,
|
|
239
|
+
"duplicates": {
|
|
240
|
+
"minTokens": 100,
|
|
241
|
+
"minLines": 10,
|
|
242
|
+
"ignoreIdentifiers": true
|
|
276
243
|
},
|
|
277
|
-
"score": { "weights": { "god": 0.35 }, "budgets": { "duplication": 0.3 }, "floors": { "duplication": 0.05 } },
|
|
278
|
-
"hubFanInRatio": 0.5,
|
|
279
244
|
"ignore": ["**/*.generated.ts"]
|
|
280
245
|
}
|
|
281
246
|
```
|
|
282
247
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
| `cognitiveComplexity` | report functions above this score (or `false` to disable) |
|
|
286
|
-
| `godClass` / `godModule` | `min*` = dispersion axis (unrelated groups); `size*` = size/WMC axis (a monolith too big even if fully connected); or `false` |
|
|
287
|
-
| `multipleExportedClasses` | `maxPerFile` classes per file before flagging; or `false` |
|
|
288
|
-
| `duplicates` | clone size floors and token normalization; or `false` |
|
|
289
|
-
| `deadCode` | which Knip issue classes to report; or `false` |
|
|
290
|
-
| `knip` | complete JSON-compatible Knip config, including `workspaces` |
|
|
291
|
-
| `score` | slop-level weights, floors, and budgets |
|
|
292
|
-
| `hubFanInRatio` | fan-in fraction that marks a shared "hub" member for removal |
|
|
293
|
-
| `ignore` | extra exclude globs on top of the built-ins |
|
|
248
|
+
Every field is optional. Set a check to `false` to disable it. The published
|
|
249
|
+
schema provides editor completion and rejects unknown or invalid properties.
|
|
294
250
|
|
|
295
|
-
|
|
251
|
+
See [docs/configuration.md](docs/configuration.md) for every field, default, and
|
|
252
|
+
Knip workspace example. See [stopslop.example.json](stopslop.example.json) for a
|
|
253
|
+
complete configuration.
|
|
296
254
|
|
|
297
|
-
|
|
298
|
-
execute `knip.json`, `.knip.json`, or `knip.config.*`; `package.json#knip` is
|
|
299
|
-
discarded and never merged with `knip`. The published `schema.json` provides editor
|
|
300
|
-
completion and validation for both StopSlop fields and the installed Knip
|
|
301
|
-
configuration surface. StopSlop deliberately has no config generator or init
|
|
302
|
-
wizard; project-aware generation belongs to separate tooling.
|
|
255
|
+
## Node.js API
|
|
303
256
|
|
|
304
|
-
|
|
305
|
-
For a monorepo, put them under `knip.workspaces`: Knip intentionally ignores
|
|
306
|
-
top-level `entry` and `project` when multiple workspaces are present.
|
|
257
|
+
StopSlop ships ESM JavaScript and TypeScript declarations for programmatic use:
|
|
307
258
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
22-repo TypeScript corpus ([docs/thresholds.md](docs/thresholds.md)).
|
|
259
|
+
```ts
|
|
260
|
+
import { analyze, loadConfig } from 'stopslop';
|
|
311
261
|
|
|
312
|
-
|
|
262
|
+
const root = process.cwd();
|
|
263
|
+
const result = await analyze(root, loadConfig(root));
|
|
313
264
|
|
|
314
|
-
|
|
315
|
-
|
|
265
|
+
console.log(result.slop.score, result.findings);
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The API also exports the resolved defaults, scoring helpers, baseline helpers,
|
|
269
|
+
and report types. See the [Node.js API guide](docs/node-api.md) for Git-base
|
|
270
|
+
analysis, typed configuration, result semantics, and custom analyzers. The API
|
|
271
|
+
is experimental before 1.0; see [COMPATIBILITY.md](COMPATIBILITY.md).
|
|
272
|
+
|
|
273
|
+
## Design and limitations
|
|
274
|
+
|
|
275
|
+
- StopSlop supports `.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, and `.tsx` source.
|
|
276
|
+
- Tests, generated code, build output, dependencies, and common framework output
|
|
277
|
+
directories are excluded by default.
|
|
278
|
+
- A high score does not prove poor engineering, and a low score does not prove
|
|
279
|
+
correctness. StopSlop measures structural overproduction, not total quality.
|
|
280
|
+
- Dead-code accuracy depends on Knip knowing the project's entry points.
|
|
281
|
+
- A baseline accepts finding identities. If an accepted function becomes more
|
|
282
|
+
complex without changing identity, the score rises but the gate stays clear.
|
|
283
|
+
|
|
284
|
+
Architecture, research references, and the boundary between published methods
|
|
285
|
+
and StopSlop heuristics are documented in [ARCH.md](ARCH.md).
|
|
286
|
+
|
|
287
|
+
## FAQ
|
|
288
|
+
|
|
289
|
+
### Is StopSlop an AI-generated code detector?
|
|
316
290
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
291
|
+
No. It does not guess who wrote a line of code. It finds structural waste that
|
|
292
|
+
coding agents often produce—duplicate blocks, over-complex functions, god
|
|
293
|
+
classes and modules, dead code, and orphan features—and reports the same issue
|
|
294
|
+
when a human wrote it.
|
|
295
|
+
|
|
296
|
+
### How is StopSlop different from ESLint, SonarQube, SonarCloud, or Knip?
|
|
297
|
+
|
|
298
|
+
ESLint and similar linters focus on local rules. SonarQube and SonarCloud are
|
|
299
|
+
broad quality platforms. Knip specializes in unused code and dependencies.
|
|
300
|
+
StopSlop is a local, zero-account AI code quality gate focused on structural
|
|
301
|
+
overproduction. It combines several signals into one report and keeps accepted
|
|
302
|
+
legacy visible through its score while blocking only new findings through a
|
|
303
|
+
baseline or Git comparison. Knip and
|
|
304
|
+
[Clone Alert](https://www.npmjs.com/package/clone-alert) power two of those
|
|
305
|
+
signals.
|
|
306
|
+
|
|
307
|
+
### Can I add StopSlop to a legacy repository?
|
|
308
|
+
|
|
309
|
+
Yes. Generate `.stopslop-baseline.json`, review and commit it, then use that same
|
|
310
|
+
file in CI. Existing findings remain in the score and report as accepted debt;
|
|
311
|
+
only unaccepted findings fail the gate.
|
|
312
|
+
|
|
313
|
+
### Does StopSlop support monorepos and CI code scanning?
|
|
314
|
+
|
|
315
|
+
Yes. Configure Knip workspaces under `stopslop.json#knip.workspaces` for accurate
|
|
316
|
+
dead-code analysis in a monorepo. Use SARIF for GitHub Code Scanning, JSON for
|
|
317
|
+
automation and coding agents, or the Shields format for a README status badge.
|
|
320
318
|
|
|
321
319
|
## Development
|
|
322
320
|
|
|
323
321
|
```bash
|
|
324
322
|
pnpm install
|
|
325
323
|
pnpm test
|
|
324
|
+
pnpm typecheck
|
|
325
|
+
pnpm check:clones
|
|
326
326
|
pnpm build
|
|
327
|
-
pnpm test:package
|
|
328
|
-
pnpm release:check
|
|
329
|
-
pnpm schema:generate # refresh schema.json after a Knip/config change
|
|
330
|
-
pnpm calibrate # regenerate docs/thresholds.md from a corpus
|
|
327
|
+
pnpm test:package
|
|
328
|
+
pnpm release:check
|
|
331
329
|
```
|
|
330
|
+
|
|
331
|
+
MIT licensed. See [LICENSE](LICENSE).
|
package/context7.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://context7.com/schema/context7.json",
|
|
3
|
+
"projectTitle": "StopSlop",
|
|
4
|
+
"description": "Static analysis and CI gate for AI code slop in JavaScript and TypeScript: cognitive complexity, god units, duplicate code, dead code, orphan features, SARIF and baselines.",
|
|
5
|
+
"folders": ["docs"],
|
|
6
|
+
"excludeFolders": [],
|
|
7
|
+
"excludeFiles": ["CHANGELOG.md", "ISSUE_KNIP.md", "RELEASING.md"],
|
|
8
|
+
"rules": [
|
|
9
|
+
"StopSlop detects structural code patterns; it does not determine whether code was written by a human or AI.",
|
|
10
|
+
"Run StopSlop without installing it with `npx stopslop .`. It requires Node.js ^20.19.0 or >=22.12.0 and analyzes JavaScript, JSX, TypeScript and TSX.",
|
|
11
|
+
"Exit code 0 means no unaccepted findings, 1 means findings were detected, and 2 means analysis or configuration failed.",
|
|
12
|
+
"Adopt an existing repository with `--baseline .stopslop-baseline.json --update-baseline`, commit that file, then use the same `--baseline` option in CI to reject only new findings.",
|
|
13
|
+
"A baseline changes the gate and reported findings but never lowers the full-repository slop score.",
|
|
14
|
+
"Use `--base <git-ref>` to compare against a Git revision without maintaining a baseline file. CI must fetch enough Git history to resolve that ref.",
|
|
15
|
+
"Report formats are text, json, sarif and shields. Use JSON for coding agents, SARIF for code scanning and shields for the binary `AI slop | clear` badge.",
|
|
16
|
+
"StopSlop works without configuration. Put optional overrides in `stopslop.json` and use `$schema: https://unpkg.com/stopslop/schema.json` for editor validation.",
|
|
17
|
+
"Use `--fast` only when intentionally skipping duplicate-code and dead-code analysis; it runs the AST checks for cognitive complexity and god units.",
|
|
18
|
+
"The Node API is ESM and typed: call `resolveConfig()` before `analyze()`, or call `loadConfig()` to read stopslop.json. The slop score always describes the complete analyzed repository.",
|
|
19
|
+
"Clone detection is powered by Clone Alert and dead-code analysis by Knip. Configure Knip only under the `knip` property in stopslop.json."
|
|
20
|
+
]
|
|
21
|
+
}
|