stopslop 0.1.2 → 0.2.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/README.md +186 -250
- package/dist/cli.js +10 -19
- package/dist/src.js +0 -0
- package/package.json +10 -2
package/README.md
CHANGED
|
@@ -1,331 +1,267 @@
|
|
|
1
|
-
#
|
|
1
|
+
# StopSlop
|
|
2
2
|
|
|
3
|
-
[](https://github.com/BaryshevRS/stopslop)
|
|
3
|
+
[](https://github.com/BaryshevRS/stopslop)
|
|
4
4
|
|
|
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+.
|
|
5
|
+
**Stop AI-generated code from becoming instant legacy.**
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
Coding agents are good at making code compile. They are also good at making too
|
|
8
|
+
much of it: tangled functions, god files, copied blocks, dead exports, and entire
|
|
9
|
+
features that pass their own tests but are never connected to the application.
|
|
10
|
+
|
|
11
|
+
StopSlop catches that structural overproduction before it becomes permanent.
|
|
12
|
+
It complements ESLint, Biome, and Oxlint rather than repeating their style and
|
|
13
|
+
type rules.
|
|
15
14
|
|
|
16
15
|
```bash
|
|
17
16
|
npx stopslop .
|
|
18
17
|
```
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
It runs locally against JavaScript and TypeScript. No account. No source-code
|
|
20
|
+
upload.
|
|
21
|
+
|
|
22
|
+
> **0.1 preview:** Requires Node.js 20.19+ or 22.12+. Public surfaces and the
|
|
23
|
+
> pre-1.0 change policy are documented in [COMPATIBILITY.md](COMPATIBILITY.md).
|
|
24
|
+
|
|
25
|
+
## What it catches
|
|
26
|
+
|
|
27
|
+
- **Tangled functions** using cognitive complexity from the SonarSource
|
|
28
|
+
specification.
|
|
29
|
+
- **God classes and god modules** that mix unrelated responsibilities or have
|
|
30
|
+
grown into densely connected monoliths.
|
|
31
|
+
- **Useful split boundaries** for god units when cohesion exposes responsibility
|
|
32
|
+
groups that should move together.
|
|
33
|
+
- **Copy-pasted blocks**, with optional identifier normalization to catch copies
|
|
34
|
+
whose variables were renamed.
|
|
35
|
+
- **Dead exports, dependencies, and files.**
|
|
36
|
+
- **Orphan features** kept alive only by their own paired test but called by no
|
|
37
|
+
production code.
|
|
38
|
+
- **Files exporting multiple classes**, a common sign that generation kept
|
|
39
|
+
appending instead of designing.
|
|
40
|
+
|
|
41
|
+
Complexity and structural analysis are implemented by StopSlop. Clone detection
|
|
42
|
+
uses [Clone Alert](https://www.npmjs.com/package/clone-alert); dead-code analysis
|
|
43
|
+
uses [Knip](https://knip.dev). StopSlop detects code patterns, not whether a human
|
|
44
|
+
or an AI wrote them.
|
|
45
|
+
|
|
46
|
+
## Start with one command
|
|
47
|
+
|
|
48
|
+
Run without installing:
|
|
21
49
|
|
|
22
50
|
```bash
|
|
23
|
-
|
|
24
|
-
pnpm exec stopslop .
|
|
51
|
+
npx stopslop .
|
|
25
52
|
```
|
|
26
53
|
|
|
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.
|
|
54
|
+
Or pin it in the project so local runs and CI use the same version:
|
|
55
55
|
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
1. close, closeOpenConnections, initHttpServer, trackOpenConnections
|
|
60
|
-
2. applyStreamHeaders, reply, setHeaderIfNotExists
|
|
61
|
-
3. set, setBaseViewsDir, setViewEngine
|
|
62
|
-
4. isMiddlewareApplied, registerParserMiddleware
|
|
56
|
+
```bash
|
|
57
|
+
npm install --save-dev stopslop
|
|
58
|
+
npx stopslop .
|
|
63
59
|
```
|
|
64
60
|
|
|
65
|
-
|
|
61
|
+
The default report gives you the full repository score, the signal densities,
|
|
62
|
+
and a grouped finding list. Add `--details` to show all clone locations and the
|
|
63
|
+
suggested split boundaries for god units; use `--json` for every finding.
|
|
66
64
|
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
```text
|
|
66
|
+
stopslop 27 files 4.3 KLOC 980ms
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
slop level: low (22.3/100) · worse than 49% of reference repos
|
|
69
|
+
god units 0.00/KLOC · complex fns 4.42/KLOC · duplication 0.8%
|
|
70
|
+
baseline: 23 accepted · 0 new
|
|
71
|
+
|
|
72
|
+
✓ no new slop since baseline
|
|
73
73
|
```
|
|
74
74
|
|
|
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:
|
|
75
|
+
Exit codes are made for CI: `0` means clear, `1` means findings, and `2` means
|
|
76
|
+
the analysis itself failed.
|
|
79
77
|
|
|
80
|
-
|
|
81
|
-
score = 100 · Σ wᵢ · sat(xᵢ) / Σ wᵢ (i = enabled signals)
|
|
82
|
-
sat(x) = clamp((x − floorᵢ) / (budgetᵢ − floorᵢ), 0, 1)
|
|
83
|
-
```
|
|
78
|
+
## Adopt an existing codebase without cleaning it first
|
|
84
79
|
|
|
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 |
|
|
80
|
+
You do not need a perfect repository to start. Record today's findings once,
|
|
81
|
+
commit them, and fail only when a change adds an unaccepted finding:
|
|
91
82
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
83
|
+
```bash
|
|
84
|
+
# Record accepted legacy.
|
|
85
|
+
npx stopslop . \
|
|
86
|
+
--baseline .stopslop-baseline.json \
|
|
87
|
+
--update-baseline
|
|
97
88
|
|
|
89
|
+
git add .stopslop-baseline.json
|
|
90
|
+
|
|
91
|
+
# Use the same file as the CI gate.
|
|
92
|
+
npx stopslop . --baseline .stopslop-baseline.json
|
|
98
93
|
```
|
|
99
|
-
slop level: moderate (43.0/100) · worse than 60% of reference repos
|
|
100
|
-
```
|
|
101
94
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
95
|
+
The baseline is a sorted, reviewable list of finding identities. Updating it is
|
|
96
|
+
an explicit acceptance of debt, so baseline changes should be reviewed like
|
|
97
|
+
source changes.
|
|
98
|
+
|
|
99
|
+
The baseline never lowers the score. These three surfaces answer different
|
|
100
|
+
questions:
|
|
101
|
+
|
|
102
|
+
| Surface | What it tells you |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| Findings | What needs attention in this run |
|
|
105
|
+
| Gate | Whether any finding remains unaccepted |
|
|
106
|
+
| Score | How much slop the complete repository carries, including accepted legacy |
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
A baselined run therefore remains honest: the report can show
|
|
109
|
+
`moderate (43/100)`, `30 accepted`, and `0 new` while the gate is green.
|
|
109
110
|
|
|
110
|
-
|
|
111
|
-
badge. `--format shields` prints a shields **endpoint JSON** to stdout — host it
|
|
112
|
-
(a committed file, a gist, anywhere reachable) and point shields at it:
|
|
111
|
+
## Put the gate in your README
|
|
113
112
|
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
The badge is deliberately binary. It is a public CI status, not a miniature
|
|
114
|
+
analytics report.
|
|
115
|
+
|
|
116
|
+
```text
|
|
117
|
+
AI slop | clear 0 unaccepted findings
|
|
118
|
+
AI slop | detected 1 or more unaccepted findings
|
|
116
119
|
```
|
|
117
120
|
|
|
121
|
+
Generate the [Shields endpoint JSON](https://shields.io/badges/endpoint-badge)
|
|
122
|
+
with the same baseline used by CI:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npx stopslop . \
|
|
126
|
+
--baseline .stopslop-baseline.json \
|
|
127
|
+
--format shields > stopslop-badge.json
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Commit or publish `stopslop-badge.json`, then add this Markdown:
|
|
131
|
+
|
|
118
132
|
```md
|
|
119
|
-
[](https://github.com/OWNER/REPO)
|
|
133
|
+
[](https://github.com/OWNER/REPO)
|
|
120
134
|
```
|
|
121
135
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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:
|
|
136
|
+
Without `--baseline` or `--base`, `clear` requires zero findings across the
|
|
137
|
+
entire scan. With a baseline, it requires zero findings outside the committed
|
|
138
|
+
baseline. The full score is still present in the terminal and JSON reports.
|
|
139
|
+
|
|
140
|
+
`--format shields` exits `0` for both `clear` and `detected` so a publishing step
|
|
141
|
+
can write the current state. An incomplete or failed analysis exits `2` before
|
|
142
|
+
emitting a badge.
|
|
143
|
+
|
|
144
|
+
## Gate pull requests
|
|
145
|
+
|
|
146
|
+
Use a committed baseline for a stable, explicitly reviewed allowlist:
|
|
140
147
|
|
|
141
148
|
```yaml
|
|
142
|
-
|
|
143
|
-
|
|
149
|
+
- name: Reject new AI slop
|
|
150
|
+
run: npx stopslop . --baseline .stopslop-baseline.json
|
|
144
151
|
```
|
|
145
152
|
|
|
146
|
-
|
|
153
|
+
Or compare the working tree directly with the target branch and keep no baseline
|
|
154
|
+
file:
|
|
147
155
|
|
|
156
|
+
```bash
|
|
157
|
+
npx stopslop . --base origin/main
|
|
148
158
|
```
|
|
149
|
-
stopslop [path] analyze a directory or file (default: .)
|
|
150
|
-
--format <fmt> text (default) · json · sarif · shields (badge JSON)
|
|
151
|
-
--json alias for --format json (stable, versioned schema)
|
|
152
|
-
--details, -d show cluster composition, hubs, all clone sites
|
|
153
|
-
--fast AST checks only: skip clones and dead code
|
|
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
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
Exit codes: `0` clean · `1` findings · `2` error.
|
|
161
159
|
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
`--base` analyzes both revisions and reports findings that exist only in the
|
|
161
|
+
current tree. CI must check out enough Git history to resolve the base revision.
|
|
162
|
+
The command never switches your branch or changes the index.
|
|
164
163
|
|
|
165
|
-
|
|
164
|
+
For GitHub Code Scanning, emit SARIF before propagating the gate failure:
|
|
166
165
|
|
|
167
166
|
```bash
|
|
168
|
-
stopslop
|
|
167
|
+
npx stopslop . --base origin/main --format sarif > stopslop.sarif
|
|
169
168
|
```
|
|
170
169
|
|
|
171
|
-
|
|
170
|
+
## Send the findings back to the agent
|
|
172
171
|
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
The JSON report is stable and versioned. Feed it into the next coding-agent turn
|
|
173
|
+
instead of paraphrasing terminal output:
|
|
175
174
|
|
|
176
|
-
```
|
|
177
|
-
stopslop . --
|
|
178
|
-
stopslop . --base origin/main --format sarif > stopslop.sarif
|
|
175
|
+
```bash
|
|
176
|
+
npx stopslop . --json > slop-report.json
|
|
179
177
|
```
|
|
180
178
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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.
|
|
179
|
+
God-unit findings include member groups that serve as concrete refactoring
|
|
180
|
+
boundaries. Clone findings include every occurrence. Baseline and Git-base runs
|
|
181
|
+
retain the complete score while returning only new findings.
|
|
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
|
+
## Score: trend the repository, do not gate on the number
|
|
192
184
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
185
|
+
The `0..100` slop score normalizes findings by KLOC, so a repository can be
|
|
186
|
+
compared with itself over time instead of merely growing a larger raw count. It
|
|
187
|
+
combines four densities: god units, duplication, over-complex functions, and
|
|
188
|
+
dead code.
|
|
196
189
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
222
|
-
```
|
|
190
|
+
Floors and budgets are anchored in a benchmark of 41 mature TypeScript
|
|
191
|
+
repositories totaling 9.4 MLOC. The weights remain an explicit judgement call,
|
|
192
|
+
not a scientific truth. That is why the binary gate uses findings rather than an
|
|
193
|
+
arbitrary score cutoff.
|
|
223
194
|
|
|
224
|
-
|
|
195
|
+
The complete formula, calibration rationale, limitations, and corpus comparison
|
|
196
|
+
are in [docs/score.md](docs/score.md) and
|
|
197
|
+
[docs/thresholds.md](docs/thresholds.md).
|
|
225
198
|
|
|
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*."
|
|
199
|
+
## CLI
|
|
230
200
|
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
stopslop . --baseline .stopslop-baseline.json --update-baseline
|
|
201
|
+
```text
|
|
202
|
+
stopslop [path] analyze a directory or file (default: .)
|
|
234
203
|
|
|
235
|
-
|
|
236
|
-
|
|
204
|
+
--format <fmt> text (default) · json · sarif · shields
|
|
205
|
+
--json alias for --format json
|
|
206
|
+
--details, -d show split groups, hubs, and all clone locations
|
|
207
|
+
--fast skip clone and dead-code analysis
|
|
208
|
+
--config <file> use a specific stopslop.json
|
|
209
|
+
--base <ref> gate on findings absent from a Git revision
|
|
210
|
+
--baseline <file> gate on findings absent from a baseline file
|
|
211
|
+
--update-baseline replace the baseline with current findings
|
|
212
|
+
--help, -h show help
|
|
237
213
|
```
|
|
238
214
|
|
|
239
|
-
|
|
240
|
-
|
|
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.
|
|
245
|
-
|
|
246
|
-
Use this committed file when you want a deliberately reviewed, stable allowlist.
|
|
247
|
-
Use `--base` when the target branch itself should be the source of truth and no
|
|
248
|
-
baseline artifact should be maintained. They share fingerprints but are separate
|
|
249
|
-
CLI modes; one does not read or rewrite the other's state.
|
|
215
|
+
Invalid configuration, no supported source files, and hard analysis errors exit
|
|
216
|
+
with code `2` before StopSlop emits a report, badge, or baseline.
|
|
250
217
|
|
|
251
|
-
|
|
252
|
-
honest state of the repo ("how much uncleaned code is here"); the baseline is
|
|
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.
|
|
218
|
+
## Configuration
|
|
256
219
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
Every metric is configurable — thresholds, and each check can be turned off with
|
|
260
|
-
`false`. All fields are optional and merged over the defaults.
|
|
220
|
+
StopSlop works without configuration. To tune a check, add `stopslop.json`:
|
|
261
221
|
|
|
262
222
|
```json
|
|
263
223
|
{
|
|
264
224
|
"$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
|
-
}
|
|
225
|
+
"cognitiveComplexity": 20,
|
|
226
|
+
"duplicates": {
|
|
227
|
+
"minTokens": 100,
|
|
228
|
+
"minLines": 10,
|
|
229
|
+
"ignoreIdentifiers": true
|
|
276
230
|
},
|
|
277
|
-
"score": { "weights": { "god": 0.35 }, "budgets": { "duplication": 0.3 }, "floors": { "duplication": 0.05 } },
|
|
278
|
-
"hubFanInRatio": 0.5,
|
|
279
231
|
"ignore": ["**/*.generated.ts"]
|
|
280
232
|
}
|
|
281
233
|
```
|
|
282
234
|
|
|
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 |
|
|
294
|
-
|
|
295
|
-
Set a check to `false` to disable it, e.g. `"godModule": false`.
|
|
296
|
-
|
|
297
|
-
`stopslop.json` is the only Knip configuration source. StopSlop does not load or
|
|
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.
|
|
235
|
+
Every field is optional. Set a check to `false` to disable it. The published
|
|
236
|
+
schema provides editor completion and rejects unknown or invalid properties.
|
|
303
237
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
238
|
+
See [docs/configuration.md](docs/configuration.md) for every field, default, and
|
|
239
|
+
Knip workspace example. See [stopslop.example.json](stopslop.example.json) for a
|
|
240
|
+
complete configuration.
|
|
307
241
|
|
|
308
|
-
|
|
309
|
-
[docs/configuration.md](docs/configuration.md).** Defaults are calibrated on a
|
|
310
|
-
22-repo TypeScript corpus ([docs/thresholds.md](docs/thresholds.md)).
|
|
242
|
+
## Design and limitations
|
|
311
243
|
|
|
312
|
-
|
|
244
|
+
- StopSlop supports `.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, and `.tsx` source.
|
|
245
|
+
- Tests, generated code, build output, dependencies, and common framework output
|
|
246
|
+
directories are excluded by default.
|
|
247
|
+
- A high score does not prove poor engineering, and a low score does not prove
|
|
248
|
+
correctness. StopSlop measures structural overproduction, not total quality.
|
|
249
|
+
- Dead-code accuracy depends on Knip knowing the project's entry points.
|
|
250
|
+
- A baseline accepts finding identities. If an accepted function becomes more
|
|
251
|
+
complex without changing identity, the score rises but the gate stays clear.
|
|
313
252
|
|
|
314
|
-
Architecture,
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
Under the hood: **complexity and god module/class are ours**; clones are
|
|
318
|
-
[Clone Alert](https://www.npmjs.com/package/clone-alert) and dead code is
|
|
319
|
-
[Knip](https://knip.dev).
|
|
253
|
+
Architecture, research references, and the boundary between published methods
|
|
254
|
+
and StopSlop heuristics are documented in [ARCH.md](ARCH.md).
|
|
320
255
|
|
|
321
256
|
## Development
|
|
322
257
|
|
|
323
258
|
```bash
|
|
324
259
|
pnpm install
|
|
325
260
|
pnpm test
|
|
261
|
+
pnpm typecheck
|
|
326
262
|
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
|
|
263
|
+
pnpm test:package
|
|
264
|
+
pnpm release:check
|
|
331
265
|
```
|
|
266
|
+
|
|
267
|
+
MIT licensed. See [LICENSE](LICENSE).
|
package/dist/cli.js
CHANGED
|
@@ -51,7 +51,7 @@ function renderTerminal(result, details) {
|
|
|
51
51
|
/** The headline: one comparable number, with the densities it came from. */
|
|
52
52
|
function renderScore(result) {
|
|
53
53
|
const { score, level, densities, signalCount, totalSignals, percentile } = result.slop;
|
|
54
|
-
const paint = LEVEL_COLOR
|
|
54
|
+
const paint = LEVEL_COLOR[level];
|
|
55
55
|
const partial = signalCount < totalSignals ? pc.yellow(` — ${signalCount}/${totalSignals} signals, see notes`) : "";
|
|
56
56
|
const anchor = percentile !== void 0 ? pc.dim(` · worse than ${percentile}% of reference repos`) : "";
|
|
57
57
|
const head = ` slop level: ${paint(pc.bold(level))} ${pc.dim(`(${score.toFixed(1)}/100)`)}${anchor}${partial}`;
|
|
@@ -62,7 +62,7 @@ function renderScore(result) {
|
|
|
62
62
|
if (densities.deadCode !== void 0) parts.push(`dead code ${densities.deadCode.toFixed(2)}/KLOC`);
|
|
63
63
|
return parts.length > 0 ? head + "\n " + pc.dim(parts.join(" · ")) : head;
|
|
64
64
|
}
|
|
65
|
-
const LEVEL_COLOR
|
|
65
|
+
const LEVEL_COLOR = {
|
|
66
66
|
clean: pc.green,
|
|
67
67
|
low: pc.green,
|
|
68
68
|
moderate: pc.yellow,
|
|
@@ -290,24 +290,15 @@ function isLine(value) {
|
|
|
290
290
|
}
|
|
291
291
|
//#endregion
|
|
292
292
|
//#region src/report/badge.ts
|
|
293
|
-
|
|
294
|
-
clean: "brightgreen",
|
|
295
|
-
low: "green",
|
|
296
|
-
moderate: "yellow",
|
|
297
|
-
high: "orange",
|
|
298
|
-
"legacy-grade": "red"
|
|
299
|
-
};
|
|
300
|
-
/** shields.io endpoint JSON for a slop badge. */
|
|
293
|
+
/** shields.io endpoint JSON for the AI-slop gate. */
|
|
301
294
|
function toBadge(result) {
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
const payload = {
|
|
295
|
+
const clear = result.findings.length === 0;
|
|
296
|
+
return `${JSON.stringify({
|
|
305
297
|
schemaVersion: 1,
|
|
306
|
-
label: "slop",
|
|
307
|
-
message:
|
|
308
|
-
color:
|
|
309
|
-
}
|
|
310
|
-
return `${JSON.stringify(payload, null, 2)}\n`;
|
|
298
|
+
label: "AI slop",
|
|
299
|
+
message: clear ? "clear" : "detected",
|
|
300
|
+
color: clear ? "brightgreen" : "red"
|
|
301
|
+
}, null, 2)}\n`;
|
|
311
302
|
}
|
|
312
303
|
//#endregion
|
|
313
304
|
//#region src/analysis-completeness.ts
|
|
@@ -413,7 +404,7 @@ async function main() {
|
|
|
413
404
|
if (args.format === "json") process.stdout.write(toJson(reported) + "\n");
|
|
414
405
|
else if (args.format === "sarif") process.stdout.write(toSarif(reported));
|
|
415
406
|
else if (args.format === "shields") {
|
|
416
|
-
process.stdout.write(toBadge(
|
|
407
|
+
process.stdout.write(toBadge(reported));
|
|
417
408
|
process.exit(0);
|
|
418
409
|
} else process.stdout.write(renderTerminal(reported, args.details) + "\n");
|
|
419
410
|
process.exit(reported.findings.length > 0 ? 1 : 0);
|
package/dist/src.js
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stopslop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Detects AI-agent slop that linters miss: over-complex functions, god modules/classes, dumped responsibilities.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,7 +47,12 @@
|
|
|
47
47
|
"calibrate": "tsx scripts/calibrate.ts",
|
|
48
48
|
"typecheck": "tsc --noEmit",
|
|
49
49
|
"schema:generate": "tsx scripts/generate-config-schema.ts",
|
|
50
|
-
"schema:check": "tsx scripts/generate-config-schema.ts --check"
|
|
50
|
+
"schema:check": "tsx scripts/generate-config-schema.ts --check",
|
|
51
|
+
"commitlint": "commitlint",
|
|
52
|
+
"prepare": "simple-git-hooks 1>&2"
|
|
53
|
+
},
|
|
54
|
+
"simple-git-hooks": {
|
|
55
|
+
"commit-msg": "pnpm exec commitlint --edit \"$1\""
|
|
51
56
|
},
|
|
52
57
|
"dependencies": {
|
|
53
58
|
"clone-alert": "^1.1.3",
|
|
@@ -57,8 +62,11 @@
|
|
|
57
62
|
},
|
|
58
63
|
"devDependencies": {
|
|
59
64
|
"@arethetypeswrong/core": "^0.18.1",
|
|
65
|
+
"@commitlint/cli": "^21.1.0",
|
|
66
|
+
"@commitlint/config-conventional": "^21.1.0",
|
|
60
67
|
"@types/node": "^22.10.0",
|
|
61
68
|
"publint": "^0.3.8",
|
|
69
|
+
"simple-git-hooks": "^2.13.1",
|
|
62
70
|
"tsdown": "^0.21.10",
|
|
63
71
|
"tsx": "^4.19.0",
|
|
64
72
|
"typescript": "^5.7.0",
|