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.
@@ -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
- # stopslop
1
+ # StopSlop
2
2
 
3
- [![slop](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/BaryshevRS/stopslop/main/stopslop-badge.json)](https://github.com/BaryshevRS/stopslop)
3
+ [![AI slop](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/BaryshevRS/stopslop/main/stopslop-badge.json)](https://github.com/BaryshevRS/stopslop) [![clone-alert: 0 clones](https://img.shields.io/badge/clone--alert-0%20clones-brightgreen)](https://github.com/BaryshevRS/clone-alert) [![CI](https://github.com/BaryshevRS/stopslop/actions/workflows/ci.yml/badge.svg)](https://github.com/BaryshevRS/stopslop/actions/workflows/ci.yml) [![npm version](https://img.shields.io/npm/v/stopslop.svg)](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
- > **0.1 preview.** The CLI and report formats are usable in CI; compatibility
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
- **Slop is instant legacy.** A linter checks *how* code is written. **stopslop**
10
- checks whether the agent *overproduced junk* — over-complex functions, god
11
- modules/classes, and responsibilities dumped into one file. It measures how much
12
- uncleaned, unrefactored overproduction a repo carries — whether that took an
13
- agent a week or a team a decade. Zero style or type rules; it is not a competitor
14
- to ESLint/Biome/Oxlint.
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
- Or install it in the project so CI uses the version pinned in your lockfile:
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
- pnpm add --save-dev stopslop
24
- pnpm exec stopslop .
50
+ npx stopslop .
25
51
  ```
26
52
 
27
- ## What it finds
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
- god class Class ExpressAdapter: 41 methods across 4 unrelated responsibility groups (+27 standalone)
58
- 4 responsibility groups (suggested split boundaries):
59
- 1. close, closeOpenConnections, initHttpServer, trackOpenConnections
60
- 2. applyStreamHeaders, reply, setHeaderIfNotExists
61
- 3. set, setBaseViewsDir, setViewEngine
62
- 4. isMiddlewareApplied, registerParserMiddleware
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
- ## Slop level
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
- One number, so a repo can be compared with itself over time and with other repos
68
- regardless of size — absolute counts just track how big the codebase is.
72
+ ```text
73
+ stopslop 27 files 4.3 KLOC 980ms
69
74
 
70
- ```
71
- slop level: moderate (49.4/100)
72
- god units 0.00/KLOC · complex fns 5.81/KLOC · duplication 3.7% · dead code 5.09/KLOC
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
- Every signal is a **density** (per KLOC; duplication is a share of lines). Each
76
- rises linearly from a *floor* (below it the density is the baseline of any
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
- | signal | xᵢ | weight | floor | budget |
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
- Floors and budgets are calibrated on a benchmark of **41 mature TypeScript OSS
93
- repos (9.4 MLOC)** — angular, react, playwright, nextjs, svelte, vue and the
94
- like: a floor is what ordinary repos carry (~p25), a budget is past anything in
95
- the reference corpus (~max/p90).
96
- The report also anchors the number against that corpus directly:
90
+ ```bash
91
+ # Record accepted legacy.
92
+ npx stopslop . \
93
+ --baseline .stopslop-baseline.json \
94
+ --update-baseline
97
95
 
98
- ```
99
- slop level: moderate (43.0/100) · worse than 60% of reference repos
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
- Bands: `clean` <10 · `low` <25 · `moderate` <50 · `high` <75 · `legacy-grade` ≥75.
103
- A disabled check leaves the formula entirely (it is not scored as clean).
104
- Weights, floors, and budgets are configurable — details and rationale in
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
- ## Slop badge
106
+ The baseline never lowers the score. These three surfaces answer different
107
+ questions:
109
108
 
110
- Show off a clean codebase with a [shields.io](https://shields.io/badges/endpoint-badge)
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:
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
- ```sh
115
- stopslop . --format shields > stopslop-badge.json
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
- ```md
119
- [![slop](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/OWNER/REPO/main/stopslop-badge.json)](https://github.com/OWNER/REPO)
120
- ```
118
+ ## Put the gate in your README
121
119
 
122
- shields fetches the JSON and renders the badge, so it refreshes whenever you
123
- regenerate the file. The message is the level and score; the color comes from a
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
- ```yaml
142
- - run: npx stopslop . --format shields > stopslop-badge.json
143
- # then commit the file (or push it to a gist) so shields serves the latest value
123
+ ```text
124
+ AI slop | clear 0 unaccepted findings
125
+ AI slop | detected 1 or more unaccepted findings
144
126
  ```
145
127
 
146
- ## Usage
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
- 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
136
+
137
+ Commit or publish `stopslop-badge.json`, then add this Markdown:
138
+
139
+ ```md
140
+ [![AI slop](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/OWNER/REPO/main/stopslop-badge.json)](https://github.com/BaryshevRS/stopslop)
158
141
  ```
159
142
 
160
- Exit codes: `0` clean · `1` findings · `2` error.
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
- Invalid configuration, zero supported source files, and hard analysis errors
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
- ### For an agent loop
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 --json > slop.json # feed to the agent: clusters are the split boundaries
167
+ npx stopslop . --base origin/main
169
168
  ```
170
169
 
171
- ### Git-base gate
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 pull requests, `--base <ref>` analyzes both the current working tree and the
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
- ```sh
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
- The current tree is analyzed as-is, including uncommitted changes. The base is
182
- checked out detached into a fresh directory under the operating system's temp
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
- This is an identity gate, not a metric diff: an existing finding with the same
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
- `--format sarif` emits SARIF 2.1.0 for GitHub Code Scanning. It has the normal
194
- gate exit codes (`0` clean, `1` findings, `2` error), so upload the report before
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
- ```yaml
198
- name: stopslop
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
- ## Baseline (adopting an existing project)
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
- A mature or already-sloppy repo can light up red on day one. A **baseline** lets
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
- ```sh
232
- # 1. Record today's findings (writes the file, exits 0)
233
- stopslop . --baseline .stopslop-baseline.json --update-baseline
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
- # 2. In CI: fail only on findings not in the baseline
236
- stopslop . --baseline .stopslop-baseline.json
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
- The baseline is a small, sorted JSON file you commit and review in pull requests.
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
- 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.
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
- **The baseline never touches the slop score or the badge.** The score is the
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.
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
- ## Config (`stopslop.json`)
231
+ ## Configuration
258
232
 
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.
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": 15,
266
- "godModule": { "minMembers": 15, "minClusters": 3, "minComplexity": 150, "sizeMembers": 37, "sizeComplexity": 350 },
267
- "godClass": { "minMembers": 12, "minClusters": 3, "minComplexity": 90, "sizeMembers": 30, "sizeComplexity": 200 },
268
- "multipleExportedClasses": { "maxPerFile": 1 },
269
- "duplicates": { "minTokens": 50, "minLines": 5, "ignoreIdentifiers": true, "ignoreLiterals": false },
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
- | key | what it tunes |
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
- Set a check to `false` to disable it, e.g. `"godModule": false`.
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
- `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.
255
+ ## Node.js API
303
256
 
304
- For a single-package project, put `entry` and `project` directly under `knip`.
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
- **Full reference with every field, defaults, and examples:
309
- [docs/configuration.md](docs/configuration.md).** Defaults are calibrated on a
310
- 22-repo TypeScript corpus ([docs/thresholds.md](docs/thresholds.md)).
259
+ ```ts
260
+ import { analyze, loadConfig } from 'stopslop';
311
261
 
312
- ## How it works & why the numbers
262
+ const root = process.cwd();
263
+ const result = await analyze(root, loadConfig(root));
313
264
 
314
- Architecture, literature references, and which parts are our heuristics:
315
- [ARCH.md](ARCH.md). Implementation plan: [PLAN.md](PLAN.md).
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
- 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).
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 # install and execute the packed npm artifact
328
- pnpm release:check # complete pre-publish gate
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
+ }