stopslop 0.2.0 → 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,6 +1,7 @@
1
1
  # StopSlop
2
2
 
3
- [![AI 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
6
  **Stop AI-generated code from becoming instant legacy.**
6
7
 
@@ -8,9 +9,10 @@ Coding agents are good at making code compile. They are also good at making too
8
9
  much of it: tangled functions, god files, copied blocks, dead exports, and entire
9
10
  features that pass their own tests but are never connected to the application.
10
11
 
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.
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.
14
16
 
15
17
  ```bash
16
18
  npx stopslop .
@@ -19,9 +21,6 @@ npx stopslop .
19
21
  It runs locally against JavaScript and TypeScript. No account. No source-code
20
22
  upload.
21
23
 
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
24
  ## What it catches
26
25
 
27
26
  - **Tangled functions** using cognitive complexity from the SonarSource
@@ -56,6 +55,14 @@ Or pin it in the project so local runs and CI use the same version:
56
55
  ```bash
57
56
  npm install --save-dev stopslop
58
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 .
59
66
  ```
60
67
 
61
68
  The default report gives you the full repository score, the signal densities,
@@ -130,9 +137,12 @@ npx stopslop . \
130
137
  Commit or publish `stopslop-badge.json`, then add this Markdown:
131
138
 
132
139
  ```md
133
- [![AI slop](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/OWNER/REPO/main/stopslop-badge.json)](https://github.com/OWNER/REPO)
140
+ [![AI slop](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/OWNER/REPO/main/stopslop-badge.json)](https://github.com/BaryshevRS/stopslop)
134
141
  ```
135
142
 
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
+
136
146
  Without `--baseline` or `--base`, `clear` requires zero findings across the
137
147
  entire scan. With a baseline, it requires zero findings outside the committed
138
148
  baseline. The full score is still present in the terminal and JSON reports.
@@ -167,6 +177,9 @@ For GitHub Code Scanning, emit SARIF before propagating the gate failure:
167
177
  npx stopslop . --base origin/main --format sarif > stopslop.sarif
168
178
  ```
169
179
 
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.
182
+
170
183
  ## Send the findings back to the agent
171
184
 
172
185
  The JSON report is stable and versioned. Feed it into the next coding-agent turn
@@ -239,6 +252,24 @@ See [docs/configuration.md](docs/configuration.md) for every field, default, and
239
252
  Knip workspace example. See [stopslop.example.json](stopslop.example.json) for a
240
253
  complete configuration.
241
254
 
255
+ ## Node.js API
256
+
257
+ StopSlop ships ESM JavaScript and TypeScript declarations for programmatic use:
258
+
259
+ ```ts
260
+ import { analyze, loadConfig } from 'stopslop';
261
+
262
+ const root = process.cwd();
263
+ const result = await analyze(root, loadConfig(root));
264
+
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
+
242
273
  ## Design and limitations
243
274
 
244
275
  - StopSlop supports `.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, and `.tsx` source.
@@ -253,12 +284,45 @@ complete configuration.
253
284
  Architecture, research references, and the boundary between published methods
254
285
  and StopSlop heuristics are documented in [ARCH.md](ARCH.md).
255
286
 
287
+ ## FAQ
288
+
289
+ ### Is StopSlop an AI-generated code detector?
290
+
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.
318
+
256
319
  ## Development
257
320
 
258
321
  ```bash
259
322
  pnpm install
260
323
  pnpm test
261
324
  pnpm typecheck
325
+ pnpm check:clones
262
326
  pnpm build
263
327
  pnpm test:package
264
328
  pnpm release:check
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
+ }
@@ -0,0 +1,100 @@
1
+ # CI and automation
2
+
3
+ StopSlop has three stable exit codes: `0` means the gate is clear, `1` means
4
+ findings were detected, and `2` means analysis or configuration failed. Pin the
5
+ package version in `devDependencies` so local runs and CI use the same rules.
6
+
7
+ ## Reject new findings with a committed baseline
8
+
9
+ Create the baseline once and review it like source code:
10
+
11
+ ```bash
12
+ npx stopslop . \
13
+ --baseline .stopslop-baseline.json \
14
+ --update-baseline
15
+ git add .stopslop-baseline.json
16
+ ```
17
+
18
+ Then use the same file in CI:
19
+
20
+ ```yaml
21
+ - name: Reject new AI slop
22
+ run: npx stopslop . --baseline .stopslop-baseline.json
23
+ ```
24
+
25
+ The baseline suppresses accepted findings from the gate. It never removes them
26
+ from the full-repository score.
27
+
28
+ ## Compare a pull request with its Git base
29
+
30
+ This mode needs the base revision to exist locally:
31
+
32
+ ```yaml
33
+ - uses: actions/checkout@v7
34
+ with:
35
+ fetch-depth: 0
36
+
37
+ - name: Reject AI slop added by this change
38
+ run: npx stopslop . --base origin/main
39
+ ```
40
+
41
+ `--base` and `--baseline` are alternative gates. Do not pass them together.
42
+
43
+ ## Publish SARIF without hiding the gate failure
44
+
45
+ The scan may exit `1` after writing valid SARIF. Let the upload step run, then
46
+ propagate the original result. The workflow or job needs permission to publish
47
+ code-scanning results:
48
+
49
+ ```yaml
50
+ permissions:
51
+ contents: read
52
+ security-events: write
53
+ ```
54
+
55
+ Then add the scan, upload, and enforcement steps:
56
+
57
+ ```yaml
58
+ - name: Scan with StopSlop
59
+ id: stopslop
60
+ continue-on-error: true
61
+ run: npx stopslop . --base origin/main --format sarif > stopslop.sarif
62
+
63
+ - name: Upload StopSlop SARIF
64
+ if: always() && hashFiles('stopslop.sarif') != ''
65
+ uses: github/codeql-action/upload-sarif@v4
66
+ with:
67
+ sarif_file: stopslop.sarif
68
+
69
+ - name: Enforce StopSlop gate
70
+ if: steps.stopslop.outcome == 'failure'
71
+ run: exit 1
72
+ ```
73
+
74
+ An exit code of `2` does not emit a report, preventing an incomplete analysis
75
+ from being published as a trustworthy result.
76
+
77
+ ## Give structured findings to a coding agent
78
+
79
+ ```bash
80
+ npx stopslop . --format json > slop-report.json
81
+ ```
82
+
83
+ The JSON schema version is included in the document. Findings carry stable
84
+ `kind`, `file`, `symbol`, `line`, `message`, and optional structured `data`
85
+ fields. God-unit data includes responsibility groups; duplicate-code data
86
+ includes clone locations.
87
+
88
+ ## Generate a binary Shields badge
89
+
90
+ Use the same baseline as CI:
91
+
92
+ ```bash
93
+ npx stopslop . \
94
+ --baseline .stopslop-baseline.json \
95
+ --format shields > stopslop-badge.json
96
+ ```
97
+
98
+ The badge says `AI slop | clear` when the gate has no unaccepted findings and
99
+ `AI slop | detected` otherwise. Accepted legacy remains visible in the terminal
100
+ and JSON score.
@@ -0,0 +1,252 @@
1
+ # Configuration
2
+
3
+ stopslop reads `stopslop.json` from the analyzed directory (or `--config <path>`).
4
+ Every field is optional and merged over the defaults, so a config only needs the
5
+ knobs you want to change. Defaults are calibrated on a 22-repo TypeScript corpus
6
+ (see [thresholds.md](thresholds.md)).
7
+
8
+ The CLI validates StopSlop-owned fields at runtime as well as publishing the
9
+ schema: unknown properties, wrong types, and out-of-range values are errors
10
+ rather than silently ignored configuration.
11
+
12
+ ## Full example (all defaults)
13
+
14
+ ```json
15
+ {
16
+ "$schema": "https://unpkg.com/stopslop/schema.json",
17
+ "cognitiveComplexity": 15,
18
+ "godModule": { "minMembers": 15, "minClusters": 3, "minComplexity": 150, "sizeMembers": 37, "sizeComplexity": 350 },
19
+ "godClass": { "minMembers": 12, "minClusters": 3, "minComplexity": 90, "sizeMembers": 30, "sizeComplexity": 200 },
20
+ "multipleExportedClasses": { "maxPerFile": 1 },
21
+ "duplicates": { "minTokens": 50, "minLines": 5, "ignoreIdentifiers": false, "ignoreLiterals": false },
22
+ "deadCode": { "exports": true, "dependencies": true, "files": true, "orphans": true, "production": false },
23
+ "knip": {
24
+ "workspaces": {
25
+ ".": { "entry": ["src/index.ts"] },
26
+ "packages/*": { "entry": ["src/index.ts"], "project": ["src/**/*.ts"] }
27
+ }
28
+ },
29
+ "score": {
30
+ "weights": { "god": 0.35, "duplication": 0.25, "complexity": 0.2, "deadCode": 0.2 },
31
+ "budgets": { "god": 0.1, "duplication": 0.3, "complexity": 4, "deadCode": 4 },
32
+ "floors": { "god": 0, "duplication": 0.05, "complexity": 0, "deadCode": 0 }
33
+ },
34
+ "hubFanInRatio": 0.5,
35
+ "ignore": []
36
+ }
37
+ ```
38
+
39
+ ## Disabling a check
40
+
41
+ Set any check to `false` to turn it off entirely:
42
+
43
+ ```json
44
+ {
45
+ "godModule": false,
46
+ "multipleExportedClasses": false
47
+ }
48
+ ```
49
+
50
+ ## Metrics
51
+
52
+ ### `cognitiveComplexity` — number | false
53
+
54
+ Functions whose cognitive complexity (Campbell / SonarSource spec) is **strictly
55
+ above** this value are reported. Default **15** (the Sonar community default; on
56
+ the corpus it sits between count p95 ≈ 13 and p99 ≈ 38). Lower = stricter.
57
+
58
+ ```json
59
+ { "cognitiveComplexity": 20 }
60
+ ```
61
+
62
+ ### `godClass` / `godModule` — object | false
63
+
64
+ A class (or a module's top-level definitions) is flagged on **either** of two
65
+ axes — see [ARCH.md](../ARCH.md) for the reasoning.
66
+
67
+ | field | meaning | default (class / module) |
68
+ |---|---|---|
69
+ | `minMembers` | dispersion axis: minimum methods / top-level definitions | 12 / 15 |
70
+ | `minClusters` | dispersion axis: minimum **substantive** responsibility groups (≥2 members) | 3 / 3 |
71
+ | `minComplexity` | dispersion axis: minimum total (Σ) cognitive complexity | 90 / 150 |
72
+ | `sizeMembers` | size/WMC axis: method count that alone signals a monolith | 30 / 37 |
73
+ | `sizeComplexity` | size/WMC axis: total complexity (WMC) that alone signals a monolith | 200 / 350 |
74
+
75
+ - **Dispersion** fires when `members ≥ minMembers AND groups ≥ minClusters AND
76
+ Σcomplexity ≥ minComplexity` — a class doing several unrelated things, with
77
+ clean split boundaries in the report.
78
+ - **Size (WMC)** fires when `members ≥ sizeMembers AND Σcomplexity ≥
79
+ sizeComplexity` — a densely-connected monolith too large to be one unit, even
80
+ if fully cohesive. Reported honestly as "no automatic split boundaries".
81
+
82
+ Raise `size*` for fewer, higher-confidence size findings; raise `minClusters` to
83
+ demand more distinct responsibilities before flagging dispersion.
84
+
85
+ ```json
86
+ {
87
+ "godClass": { "sizeMembers": 45, "sizeComplexity": 300 },
88
+ "godModule": false
89
+ }
90
+ ```
91
+
92
+ ### `multipleExportedClasses` — object | false
93
+
94
+ Flags files exporting **more than** `maxPerFile` classes. Default `maxPerFile`
95
+ **1** (i.e. flag 2+). Informational severity.
96
+
97
+ ```json
98
+ { "multipleExportedClasses": { "maxPerFile": 2 } }
99
+ ```
100
+
101
+ ### `duplicates` — object | false
102
+
103
+ Copy-paste detection, powered by [Clone Alert](https://www.npmjs.com/package/clone-alert).
104
+ Only the files stopslop discovered are fed to it, so the ignore rules above apply
105
+ here too.
106
+
107
+ | field | meaning | default |
108
+ |---|---|---|
109
+ | `minTokens` | minimum duplicated token span reported as a clone | 50 |
110
+ | `minLines` | minimum physical lines a clone must span (its tightest occurrence) | 5 |
111
+ | `ignoreIdentifiers` | normalize identifier names before matching (Type-2 clones) | `false` |
112
+ | `ignoreLiterals` | normalize literal values before matching | `false` |
113
+
114
+ The default is **exact matching (Type-1 clones)** — the reported percentage is
115
+ real copy-paste. Set `ignoreIdentifiers: true` to also catch Type-2 clones (an
116
+ agent copy-pasting a block and renaming the variables); the cost is that
117
+ structurally-similar boilerplate matches too, which inflates the duplication
118
+ ratio on component libraries by several points. `minLines` keeps dense
119
+ one-liners (config tables, long template strings) from matching on token count
120
+ alone; turn it up to only see substantial clones.
121
+
122
+ ```json
123
+ { "duplicates": { "minTokens": 100, "minLines": 10, "ignoreIdentifiers": true } }
124
+ ```
125
+
126
+ ### `deadCode` — object | false
127
+
128
+ Unused code, powered by [Knip](https://knip.dev) via its programmatic session API
129
+ (no CLI spawn). This section controls which Knip findings StopSlop reports and
130
+ whether Knip runs in production mode. It is StopSlop policy, not Knip's own
131
+ configuration.
132
+
133
+ | field | meaning | default |
134
+ |---|---|---|
135
+ | `exports` | report unused exports and unused exported types (warn) | `true` |
136
+ | `dependencies` | report unused (dev)dependencies from package.json (warn) | `true` |
137
+ | `files` | report files unreachable from any entry point (info) | `true` |
138
+ | `orphans` | report functions kept alive by nothing but their own paired test (warn) | `true` |
139
+ | `production` | analyze as production-only: usage from tests no longer counts | `false` |
140
+
141
+ Unused files are **info**, not warn, on purpose: they depend on entry-point
142
+ detection, which misfires on projects with unconventional entries. Verify before
143
+ deleting — or configure `entry` under `stopslop.json#knip`.
144
+
145
+ An **orphan feature** is a function or class no production code calls, imported
146
+ by exactly one file: its own paired test. The trace of a generated feature that
147
+ was never wired in — a plain unused export has an innocent explanation ready,
148
+ this one has almost none. The finding names both halves to delete, source symbol
149
+ and test file, because removing either alone breaks the build or leaves a green
150
+ test standing over nothing. It is reported and deliberately never scored: the
151
+ point is to get the pair deleted, and a weight in the number removes no code.
152
+
153
+ Symbols still used inside their own file are excluded, as are shared test
154
+ helpers, stories, and non-callable constants — every one of them a way for a
155
+ live symbol to look like an orphan. Orphans need Knip to see the test files at
156
+ all, which means either a test-runner plugin it recognises or `entry` patterns
157
+ covering the tests.
158
+
159
+ ```json
160
+ { "deadCode": { "files": false, "production": true } }
161
+ ```
162
+
163
+ ### `knip` — object
164
+
165
+ The complete JSON-compatible [Knip configuration](https://knip.dev/reference/configuration).
166
+ This is the **only** Knip configuration StopSlop uses. It never reads, merges,
167
+ or executes project `knip.json`, `.knip.json`, or `knip.config.*` files. The
168
+ root `package.json` is still project metadata, but its `knip` field is discarded
169
+ and never merged. Move any settings StopSlop should use into this property.
170
+
171
+ ```json
172
+ {
173
+ "knip": {
174
+ "ignoreDependencies": ["optional-adapter"],
175
+ "workspaces": {
176
+ ".": { "entry": ["src/index.ts"] },
177
+ "packages/*": { "entry": ["src/index.ts"], "project": ["src/**/*.ts"] },
178
+ "packages/cli": { "entry": ["src/cli.ts"] }
179
+ }
180
+ }
181
+ }
182
+ ```
183
+
184
+ Workspace keys and overrides follow Knip unchanged: `.` configures the root,
185
+ glob keys select groups of packages, and a more specific key can override a
186
+ group. In a detected monorepo without `knip.workspaces`, findings are still
187
+ shown but excluded from the dead-code score because entry-point detection is
188
+ not trustworthy enough to grade the repository.
189
+
190
+ In a single-package project, `entry` and `project` belong directly under
191
+ `knip`. In a monorepo they belong under `knip.workspaces`; Knip ignores the
192
+ top-level forms once multiple workspaces are present.
193
+
194
+ One Knip default is changed: StopSlop sets `ignoreExportsUsedInFile` to `true`.
195
+ Knip otherwise reports an export nothing imports even when the symbol is used
196
+ inside its own file — a `export` keyword wider than it needs to be, which no
197
+ behaviour depends on. That is a style nit rather than the structural waste
198
+ StopSlop reports. Set it back to `false` under `knip` to get Knip's own default;
199
+ the granular per-symbol-type form works as documented upstream.
200
+
201
+ The package ships `schema.json`. Add
202
+ `"$schema": "https://unpkg.com/stopslop/schema.json"` for editor completion,
203
+ field descriptions, and validation. StopSlop consumes configuration only; it
204
+ does not infer or generate project settings. That project inspection is kept as
205
+ a boundary for future external tooling (such as an MCP server).
206
+
207
+ ### `score` — object
208
+
209
+ Weights, floors, and budgets of the slop level. A signal contributes nothing
210
+ below its floor, rises linearly, and saturates at its budget. Full formula and
211
+ rationale (all anchors measured on a 41-repo benchmark of mature TypeScript
212
+ OSS): [score.md](score.md).
213
+
214
+ ```json
215
+ {
216
+ "score": {
217
+ "weights": { "god": 0.35, "duplication": 0.25, "complexity": 0.2, "deadCode": 0.2 },
218
+ "budgets": { "god": 0.1, "duplication": 0.3, "complexity": 4, "deadCode": 4 },
219
+ "floors": { "god": 0, "duplication": 0.05, "complexity": 0, "deadCode": 0 }
220
+ }
221
+ }
222
+ ```
223
+
224
+ Weights need not sum to 1 — the score divides by the sum of the weights that
225
+ actually ran. Disabling a check removes its signal from the formula entirely
226
+ rather than scoring it as clean. The "worse than N% of reference repos" anchor
227
+ is recomputed with your weights/floors/budgets, so a custom config re-ranks the
228
+ corpus with the same yardstick it applies to your repo.
229
+
230
+ ## Engine knobs
231
+
232
+ ### `hubFanInRatio` — number (0..1)
233
+
234
+ A member referenced by more than this fraction of the unit's methods is treated
235
+ as a "hub" (shared glue), removed from the cohesion graph, and the responsibility
236
+ groups are recomputed — so a class held together by one shared field still splits
237
+ into its real parts. Default **0.5**. Lower = more aggressive splitting.
238
+
239
+ ### `ignore` — string[]
240
+
241
+ Extra glob patterns (matched against POSIX-relative paths) to exclude, on top of
242
+ the built-in excludes (`node_modules`, `dist`/`build`, tests, fixtures, `.d.ts`,
243
+ minified/bundled files, and generator subtrees such as openapi-generator).
244
+
245
+ ```json
246
+ { "ignore": ["**/*.generated.ts", "src/legacy/**"] }
247
+ ```
248
+
249
+ ## Reference
250
+
251
+ The JSON report (`--json`) echoes the fully-resolved config under `thresholds`,
252
+ so you can confirm exactly what ran.
@@ -0,0 +1,114 @@
1
+ # Node.js API
2
+
3
+ StopSlop ships a typed ESM API for tools that need findings and metrics in
4
+ memory instead of a CLI report. It requires Node.js `^20.19.0 || >=22.12.0`.
5
+
6
+ ## Analyze with defaults
7
+
8
+ Resolve configuration before calling `analyze`:
9
+
10
+ ```ts
11
+ import { analyze, resolveConfig } from 'stopslop';
12
+
13
+ const result = await analyze(process.cwd(), resolveConfig(undefined));
14
+
15
+ for (const finding of result.findings) {
16
+ console.log(finding.kind, finding.file, finding.line, finding.message);
17
+ }
18
+
19
+ console.log({
20
+ score: result.slop.score,
21
+ level: result.slop.level,
22
+ errors: result.errors,
23
+ notes: result.notes,
24
+ });
25
+ ```
26
+
27
+ `findings` contains the issues that should fail the current gate. `slop` always
28
+ describes the complete analyzed repository. Check `errors` before trusting a
29
+ result; `notes` explains non-fatal skips by project-level engines.
30
+
31
+ ## Load `stopslop.json`
32
+
33
+ `loadConfig` reads `stopslop.json` from the analysis root and returns the
34
+ resolved defaults plus overrides:
35
+
36
+ ```ts
37
+ import { analyze, loadConfig } from 'stopslop';
38
+
39
+ const root = process.cwd();
40
+ const result = await analyze(root, loadConfig(root));
41
+ ```
42
+
43
+ To build configuration in code, pass the user-facing shape to `resolveConfig`:
44
+
45
+ ```ts
46
+ import { analyze, resolveConfig, type StopSlopConfig } from 'stopslop';
47
+
48
+ const input: StopSlopConfig = {
49
+ cognitiveComplexity: 20,
50
+ duplicates: { minTokens: 100, minLines: 10 },
51
+ deadCode: { files: false },
52
+ };
53
+
54
+ const result = await analyze(process.cwd(), resolveConfig(input));
55
+ ```
56
+
57
+ Unknown fields and invalid values throw instead of being ignored.
58
+
59
+ ## Compare with a Git revision
60
+
61
+ `analyzeGitBase` returns only findings absent from the selected revision while
62
+ retaining the current repository's complete score:
63
+
64
+ ```ts
65
+ import { analyzeGitBase, loadConfig } from 'stopslop';
66
+
67
+ const root = process.cwd();
68
+ const result = await analyzeGitBase(root, 'origin/main', loadConfig(root));
69
+
70
+ if (result.errors.length > 0) process.exitCode = 2;
71
+ else if (result.findings.length > 0) process.exitCode = 1;
72
+ ```
73
+
74
+ The ref must resolve in the local Git repository. StopSlop creates a temporary
75
+ detached worktree; it does not switch the current branch or modify the index.
76
+
77
+ ## Extend the analyzer list
78
+
79
+ Pass a third argument to append a custom analyzer to the built-in set:
80
+
81
+ ```ts
82
+ import {
83
+ analyze,
84
+ defaultAnalyzers,
85
+ resolveConfig,
86
+ type Analyzer,
87
+ } from 'stopslop';
88
+
89
+ const custom: Analyzer = {
90
+ name: 'project-policy',
91
+ analyzeFile(file) {
92
+ return file.source.includes('DO NOT SHIP')
93
+ ? [{
94
+ kind: 'unused-file',
95
+ file: file.relPath,
96
+ symbol: 'DO NOT SHIP',
97
+ line: 1,
98
+ severity: 'warn',
99
+ message: 'Unresolved project marker',
100
+ }]
101
+ : [];
102
+ },
103
+ };
104
+
105
+ const result = await analyze(
106
+ process.cwd(),
107
+ resolveConfig(undefined),
108
+ [...defaultAnalyzers(), custom],
109
+ );
110
+ ```
111
+
112
+ Custom analyzers currently return the published `Finding` union, so use an
113
+ existing finding kind for integrations. Treat that surface as pre-1.0; see the
114
+ [compatibility policy](../COMPATIBILITY.md).
package/docs/score.md ADDED
@@ -0,0 +1,133 @@
1
+ # Slop level: the formula
2
+
3
+ The slop level is one number in `0..100` plus a band label. It exists so that a
4
+ repo can be compared **with itself over time** and **with other repos**, which
5
+ raw finding counts cannot do: a 200 KLOC monorepo will always "win" on absolute
6
+ counts without being any sloppier per line written.
7
+
8
+ Nothing here is a black box, and nothing here is science. The **densities are
9
+ the facts**; the score is a weighted aggregate over them, with heuristic weights
10
+ and budgets that you can change.
11
+
12
+ ## The formula
13
+
14
+ ```
15
+ score = 100 · Σ wᵢ · sat(xᵢ) / Σ wᵢ
16
+ sat(x) = clamp((x − floorᵢ) / (budgetᵢ − floorᵢ), 0, 1)
17
+ ```
18
+
19
+ where `i` ranges over the **enabled** signals:
20
+
21
+ | signal | xᵢ (density) | weight wᵢ | floor | budget |
22
+ |---|---|---|---|---|
23
+ | `god` | (god modules + god classes) / KLOC | 0.35 | 0 | 0.1 |
24
+ | `duplication` | duplicated lines / total lines (0..1) | 0.25 | 0.05 | 0.30 |
25
+ | `complexity` | over-complex functions / KLOC | 0.20 | 0 | 4 |
26
+ | `deadCode` | (unused exports + deps) / KLOC | 0.20 | 0 | 4 |
27
+
28
+ Below the floor a signal contributes nothing — that density is the baseline of
29
+ any living codebase, not slop. At the budget the contribution saturates.
30
+
31
+ Only `warn`-severity findings feed the score. `info` findings — unused *files*,
32
+ which hang entirely on Knip's entry-point detection — are reported but never
33
+ scored: a misdetected entry point must not outweigh a real god class.
34
+
35
+ Bands: `clean` <10 · `low` <25 · `moderate` <50 · `high` <75 · `legacy-grade` ≥75.
36
+ The top band is `legacy-grade`, not "severe": a high score means the code reads
37
+ like accumulated legacy — true whether a team wrote it over a decade or an agent
38
+ produced it last week. It is a description, not a verdict on the engineers.
39
+
40
+ Three properties are deliberate:
41
+
42
+ - **Size independence.** Every signal is a rate, not a count. Four god classes in
43
+ 20 KLOC score exactly like one in 5 KLOC.
44
+ - **Saturation.** `min(1, …)` caps each signal at its budget: a repo with 100 god
45
+ modules is not "20× worse" than one with 5 — it is simply maxed out on that
46
+ axis, and the other axes still matter. Without the cap, one pathological file
47
+ would drown out everything else.
48
+ - **A check that did not run leaves the formula.** If you turn off `deadCode` — or
49
+ if Knip bails out because the project's dependencies are not installed, or the
50
+ project is a monorepo without `stopslop.json#knip.workspaces` (where Knip's
51
+ entry-point detection produces a landslide of false "unused") — its weight drops out of the
52
+ denominator instead of contributing zero. Not measuring something must never
53
+ read as a clean bill of health. The terminal report shows only the signals
54
+ that were actually measured, and says in a note why one is missing.
55
+
56
+ `multiple-exported-classes` is reported but not scored: it is a structural hint,
57
+ not a quantity of slop, and it correlates with the god-module signal already.
58
+
59
+ ## Where the numbers come from
60
+
61
+ Honestly: the **weights are a judgement call**; the **floors and budgets are
62
+ measured**, not invented. Every non-zero anchor comes from a 41-repo / 9.4 MLOC
63
+ benchmark run over mature TypeScript OSS (angular, react, playwright, nextjs,
64
+ svelte, vue-core, nest, vite, material-ui and the like — the corpus ships in
65
+ `src/corpus.ts`), by one rule: a *floor* is what ordinary repos carry (~p25), a
66
+ *budget* is "past anything in the reference corpus" (~max/p90). The calibrated
67
+ defaults remain configurable (`score.weights`, `score.budgets`, `score.floors`).
68
+
69
+ - **Weights** encode what we claim slop *is*. Misplaced structure (god units) and
70
+ copy-paste are the expensive kinds — they are what an agent produces when it
71
+ keeps appending instead of refactoring, and they are what a human has to undo
72
+ by hand. A single hairy function or a stale export is cheaper to fix, so
73
+ complexity and dead code weigh less. `god` (0.35) > `duplication` (0.25) >
74
+ `complexity` = `deadCode` (0.20).
75
+ - **`god` budget = 0.1/KLOC** — corpus median 0.013, half the repos at exactly 0,
76
+ worst repo (playwright) at 0.071. No floor: a single god unit is already slop.
77
+ - **`duplication` floor = 5%, budget = 30%** — corpus p25 ≈ 5% (unavoidable
78
+ boilerplate of a living TS codebase), p90 ≈ 31% (mantine). The first-guess
79
+ budget (10%, the jscpd convention) sat *below the corpus median of ~13%*: the
80
+ signal saturated on ¾ of the corpus, became a constant +25 points for nearly
81
+ everyone, and compressed all 42 benchmark scores into 27..56 — a scale with
82
+ only two live bands. Measured beats conventional.
83
+ - **`complexity` budget = 4/KLOC** — the detection threshold is already the count
84
+ p95 of the calibration corpus ([thresholds.md](thresholds.md)); the corpus
85
+ maximum of repos crossing it is 3.31/KLOC (svelte — a compiler, i.e. honestly
86
+ complex). 4 = past anything in the corpus.
87
+ - **`deadCode` budget = 4/KLOC** — ≈ p75 of the sub-corpus where dead code was
88
+ actually measurable; one unused export/dependency every 250 lines means the
89
+ project has stopped removing what it stopped using. This is the noisiest
90
+ signal (it leans on Knip config quality, and a library's public API reads as
91
+ "unused exports"), which is why it carries the smallest weight and never
92
+ enters the corpus percentile below.
93
+
94
+ ## "Worse than N% of reference repos"
95
+
96
+ Alongside the absolute score, the report anchors the repo against the reference
97
+ corpus: the share of the 41 benchmark repos with a strictly lower **core score**
98
+ — the score computed from the three signals measured identically everywhere
99
+ (god, duplication, complexity; dead code is excluded as infrastructure-dependent).
100
+ The comparison uses *your* weights/floors/budgets, so a custom config re-ranks
101
+ the corpus with the same yardstick it applies to your repo. It appears only when
102
+ all three core signals were measured (`--fast` has no percentile).
103
+
104
+ This is context, not a second verdict: "43/100, worse than 60% of reference
105
+ repos" tells you where the number sits among codebases nobody calls slop.
106
+
107
+ ## What the score means — and what it does *not*
108
+
109
+ The score is **"how much uncleaned, suspicious code is left"**, not a verdict on
110
+ engineering quality. The intended reading:
111
+
112
+ - Clean it up and the number goes to zero: no god units, no duplication, no dead
113
+ code → 0, whether the code was written by a human or an agent. Fix the god
114
+ class, deduplicate, remove dead exports — the score follows.
115
+ - Its primary use is comparing a repo **with itself over time** (did the agent's
116
+ last week of commits push it up?). Cross-repo comparison is meaningful but
117
+ needs the caveat below.
118
+ - A genuinely complex domain scores high honestly: playwright tops the corpus
119
+ ranking because it really does carry the corpus's highest god-unit density
120
+ (0.071/KLOC) and near-maximal density of over-complex functions. The score
121
+ reports that; it does not know (or claim to know) that the complexity is
122
+ well-earned. High score = "a lot here would need justification", not "bad
123
+ engineers".
124
+
125
+ Other non-goals:
126
+
127
+ - `deadCode` anchors are the weakest: the signal depends on Knip configuration
128
+ and misreads a library's public API as unused exports (see above).
129
+ - It does not weigh a finding by how bad it is individually (a complexity-80
130
+ function counts the same as a complexity-16 one). Severity lives in the
131
+ findings, not the score.
132
+ - It is not a quality score. Clean code with zero slop signals can still be bad
133
+ code — stopslop only claims to measure *overproduction*.
@@ -0,0 +1,105 @@
1
+ # Threshold calibration
2
+
3
+ Two lenses on the same corpus:
4
+
5
+ - **Count percentiles** — each entity counts once. "pXX = N" means XX% of
6
+ entities score ≤ N. This is what we use for defaults: a god finding should be
7
+ the worst ~1–5% of entities.
8
+ - **LOC-weighted percentiles** — the Alves/Ypma/Visser (ICSM 2010) methodology:
9
+ each entity weighted by LOC, so "pXX = N" means XX% of *code volume* sits in
10
+ entities ≤ N. Kept for reference; it skews high because large entities are
11
+ both more complex and hold more volume.
12
+
13
+ Corpus: 22 TypeScript repositories, 65,817 analyzed files (tests, fixtures,
14
+ generated and bundled code excluded). The per-repository counts are listed
15
+ below.
16
+
17
+ ## Count percentiles (basis for defaults)
18
+
19
+ ### Function
20
+ | metric | n | p50 | p75 | p90 | p95 | p99 |
21
+ |---|---|---|---|---|---|---|
22
+ | cognitive complexity | 108102 | 0 | 2 | 7 | 13 | 38 |
23
+
24
+ ### Class (≥3 methods)
25
+ | metric | n | p50 | p75 | p90 | p95 | p99 |
26
+ |---|---|---|---|---|---|---|
27
+ | methods | 2940 | 6 | 12 | 23 | 32 | 64 |
28
+ | responsibility groups (≥2) | 2940 | 1 | 1 | 2 | 2 | 4 |
29
+ | Σ cognitive complexity | 2940 | 8 | 24 | 58 | 93 | 207 |
30
+
31
+ ### Module (≥5 top-level fns)
32
+ | metric | n | p50 | p75 | p90 | p95 | p99 |
33
+ |---|---|---|---|---|---|---|
34
+ | top-level definitions | 2819 | 8 | 11 | 16 | 22 | 37 |
35
+ | responsibility groups (≥2) | 2819 | 1 | 1 | 2 | 2 | 3 |
36
+ | Σ cognitive complexity | 2819 | 21 | 54 | 109 | 167 | 354 |
37
+
38
+ ## LOC-weighted percentiles (Alves, reference)
39
+
40
+ ### Function
41
+ | metric | n | p50 | p75 | p90 | p95 | p99 |
42
+ |---|---|---|---|---|---|---|
43
+ | cognitive complexity | 108102 | 4 | 16 | 45 | 95 | 309 |
44
+
45
+ ### Class (≥3 methods)
46
+ | metric | n | p50 | p75 | p90 | p95 | p99 |
47
+ |---|---|---|---|---|---|---|
48
+ | methods | 2940 | 15 | 30 | 57 | 74 | 111 |
49
+ | responsibility groups (≥2) | 2940 | 1 | 1 | 2 | 3 | 5 |
50
+ | Σ cognitive complexity | 2940 | 41 | 104 | 205 | 341 | 825 |
51
+
52
+ ### Module (≥5 top-level fns)
53
+ | metric | n | p50 | p75 | p90 | p95 | p99 |
54
+ |---|---|---|---|---|---|---|
55
+ | top-level definitions | 2819 | 10 | 17 | 29 | 38 | 72 |
56
+ | responsibility groups (≥2) | 2819 | 1 | 1 | 2 | 2 | 4 |
57
+ | Σ cognitive complexity | 2819 | 51 | 128 | 280 | 455 | 1175 |
58
+
59
+ ## Final defaults
60
+
61
+ A god finding is rare by construction, but the detector does not compare each
62
+ metric with an independent p95 cutoff. It has two alternative axes:
63
+
64
+ - **dispersion** requires members **and** responsibility groups **and** total
65
+ complexity together, so its member floor can stay sensitive;
66
+ - **size/WMC** requires both a high member count and high total complexity, even
67
+ when the unit is cohesive.
68
+
69
+ | gate | members | groups | Σ complexity | final setting |
70
+ |---|---:|---:|---:|---|
71
+ | cognitive complexity | — | — | 15 | `cognitiveComplexity: 15` |
72
+ | class dispersion | 12 | 3 | 90 | `godClass: 12 / 3 / 90` |
73
+ | class size/WMC | 30 | — | 200 | `godClass: 30 / 200` |
74
+ | module dispersion | 15 | 3 | 150 | `godModule: 15 / 3 / 150` |
75
+ | module size/WMC | 37 | — | 350 | `godModule: 37 / 350` |
76
+
77
+ The size thresholds sit near the count p95–p99 tail. On the dispersion axis,
78
+ three substantive groups are already rare (between class p95 and p99; module
79
+ p99), and the conjunction with complexity supplies the precision. Raising the
80
+ member floor itself to p95 would make the conjunction unnecessarily blind to
81
+ smaller units that clearly contain three unrelated responsibilities.
82
+
83
+ ## Per-repo file counts
84
+ - angular: 1971 files
85
+ - angular-components: 1971 files
86
+ - chakra-ui: 2359 files
87
+ - create-react-app: 122 files
88
+ - gatsby: 3086 files
89
+ - ionic-framework: 508 files
90
+ - material-ui: 26447 files
91
+ - nest: 1306 files
92
+ - nextjs: 4498 files
93
+ - ng-bootstrap: 527 files
94
+ - ng-bootstrap-fresh: 527 files
95
+ - nx: 3330 files
96
+ - playwright: 788 files
97
+ - primeng: 2743 files
98
+ - react: 1711 files
99
+ - react-bootstrap: 440 files
100
+ - remix: 805 files
101
+ - shadcn-ui: 3473 files
102
+ - storybook: 2617 files
103
+ - svelte: 2299 files
104
+ - taiga-ui: 2770 files
105
+ - tanstack-table: 1519 files
package/llms.txt ADDED
@@ -0,0 +1,25 @@
1
+ # StopSlop
2
+
3
+ > Static analysis and CI gating for structural AI code slop in JavaScript and TypeScript. StopSlop finds cognitive complexity, god classes and modules, duplicate code, dead code, orphan features, and responsibility boundaries. It reports code patterns, not authorship.
4
+
5
+ Run without installing: `npx stopslop .`. Exit codes are `0` for a clear gate, `1` for detected findings, and `2` for an analysis error. Requires Node.js 20.19+ or 22.12+.
6
+
7
+ ## Start here
8
+
9
+ - [Overview, installation, CLI and badge](https://raw.githubusercontent.com/BaryshevRS/stopslop/main/README.md)
10
+ - [Configuration reference](https://raw.githubusercontent.com/BaryshevRS/stopslop/main/docs/configuration.md)
11
+ - [CI baselines, Git comparison, SARIF and agent reports](https://raw.githubusercontent.com/BaryshevRS/stopslop/main/docs/ci-and-automation.md)
12
+ - [Typed Node.js API](https://raw.githubusercontent.com/BaryshevRS/stopslop/main/docs/node-api.md)
13
+
14
+ ## How it works
15
+
16
+ - [Score formula and interpretation](https://raw.githubusercontent.com/BaryshevRS/stopslop/main/docs/score.md)
17
+ - [Default thresholds and calibration](https://raw.githubusercontent.com/BaryshevRS/stopslop/main/docs/thresholds.md)
18
+ - [Architecture and methodology](https://raw.githubusercontent.com/BaryshevRS/stopslop/main/ARCH.md)
19
+ - [Compatibility policy](https://raw.githubusercontent.com/BaryshevRS/stopslop/main/COMPATIBILITY.md)
20
+
21
+ ## Package
22
+
23
+ - [npm package](https://www.npmjs.com/package/stopslop)
24
+ - [JSON Schema](https://unpkg.com/stopslop/schema.json)
25
+ - [GitHub repository](https://github.com/BaryshevRS/stopslop)
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "stopslop",
3
- "version": "0.2.0",
4
- "description": "Detects AI-agent slop that linters miss: over-complex functions, god modules/classes, dumped responsibilities.",
3
+ "version": "1.0.0",
4
+ "description": "Static analysis for AI-generated JavaScript and TypeScript: find cognitive complexity, god classes/modules, duplicate code, dead code and orphan features.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
7
9
  "repository": {
8
10
  "type": "git",
9
11
  "url": "git+https://github.com/BaryshevRS/stopslop.git"
@@ -18,12 +20,20 @@
18
20
  "exports": {
19
21
  ".": {
20
22
  "types": "./dist/index.d.ts",
21
- "import": "./dist/index.js"
23
+ "import": "./dist/index.js",
24
+ "default": "./dist/index.js"
22
25
  },
23
- "./schema.json": "./schema.json"
26
+ "./schema.json": "./schema.json",
27
+ "./package.json": "./package.json"
24
28
  },
25
29
  "files": [
26
- "dist",
30
+ "dist/**/*",
31
+ "docs/**/*.md",
32
+ "README.md",
33
+ "COMPATIBILITY.md",
34
+ "LICENSE",
35
+ "llms.txt",
36
+ "context7.json",
27
37
  "schema.json"
28
38
  ],
29
39
  "engines": {
@@ -40,8 +50,9 @@
40
50
  "test": "vitest run",
41
51
  "test:watch": "vitest",
42
52
  "test:package": "node scripts/package-smoke.mjs",
53
+ "check:clones": "clone-alert --minimum-tokens 100 --files src --format text --fail-on-violation",
43
54
  "audit:prod": "pnpm audit --prod",
44
- "ci:check": "pnpm test && pnpm typecheck && pnpm build",
55
+ "ci:check": "pnpm test && pnpm typecheck && pnpm check:clones && pnpm build",
45
56
  "release:check": "pnpm ci:check && pnpm audit:prod && pnpm test:package",
46
57
  "prepublishOnly": "pnpm release:check",
47
58
  "calibrate": "tsx scripts/calibrate.ts",
@@ -74,12 +85,41 @@
74
85
  },
75
86
  "keywords": [
76
87
  "ai",
88
+ "ai-code",
89
+ "ai-generated-code",
90
+ "ai-code-quality",
91
+ "ai-slop",
77
92
  "slop",
93
+ "slop-detector",
94
+ "vibe-coding",
95
+ "static-analysis",
96
+ "code-analysis",
97
+ "code-quality",
78
98
  "code-smell",
99
+ "technical-debt",
100
+ "javascript",
101
+ "typescript",
102
+ "jsx",
103
+ "tsx",
104
+ "cli",
105
+ "lint",
106
+ "ci",
107
+ "quality-gate",
108
+ "code-scanning",
109
+ "github-actions",
110
+ "sarif",
111
+ "baseline",
79
112
  "god-class",
80
113
  "god-module",
81
114
  "cognitive-complexity",
82
115
  "lcom",
83
- "static-analysis"
116
+ "cohesion",
117
+ "duplicate-code",
118
+ "copy-paste-detector",
119
+ "dead-code",
120
+ "unused-code",
121
+ "orphan-code",
122
+ "monorepo",
123
+ "code-review"
84
124
  ]
85
125
  }