opencode-anti-slop 0.1.1 → 0.1.4
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/.opencode/skills/verify-clean-code/SKILL.md +4 -1
- package/README.md +315 -275
- package/install.mjs +34 -2
- package/package.json +1 -1
- package/scripts/anti-slop/baseline.mjs +147 -0
- package/scripts/anti-slop/check-docs.mjs +109 -70
- package/scripts/anti-slop/check-duplicates.mjs +44 -19
- package/scripts/anti-slop/check-imports.mjs +72 -30
- package/scripts/anti-slop/check-modularity.mjs +72 -9
- package/scripts/anti-slop/cli.mjs +199 -115
- package/scripts/anti-slop/cli.test.mjs +98 -2
- package/scripts/anti-slop/generate-component-index.mjs +162 -133
- package/scripts/anti-slop/install-hooks.mjs +5 -7
- package/scripts/anti-slop/lib.mjs +47 -41
- package/scripts/anti-slop/run-project-checks.mjs +73 -0
- package/templates/anti-slop-ci.yml +1 -1
|
@@ -26,7 +26,9 @@ It tokenizes the source tree and fails when copy-paste duplication exceeds the
|
|
|
26
26
|
threshold (defaults tuned for typical repos; adjust in the target repo if it
|
|
27
27
|
has legacy duplication). If it flags your new code: extract into the existing
|
|
28
28
|
shared module (see reuse-before-create) rather than adjusting the threshold.
|
|
29
|
-
Never weaken the check to make it pass.
|
|
29
|
+
Never weaken the check to make it pass. Repos with a committed
|
|
30
|
+
`anti-slop.baseline.json` instead fail only on duplication newer than the
|
|
31
|
+
baseline — do not update the baseline to absorb your new debt.
|
|
30
32
|
|
|
31
33
|
## 3. Modularity check
|
|
32
34
|
|
|
@@ -37,6 +39,7 @@ node scripts/anti-slop/check-modularity.mjs
|
|
|
37
39
|
Flags files over ~300 lines, functions over ~80, and classes with > ~10
|
|
38
40
|
public methods. The files you touched must pass. If the repo has pre-existing
|
|
39
41
|
violations elsewhere, report them — do not drive-by-fix unrelated files.
|
|
42
|
+
With a committed baseline, the gate fails only on violations beyond it.
|
|
40
43
|
|
|
41
44
|
## 4. Import-boundary check
|
|
42
45
|
|
package/README.md
CHANGED
|
@@ -1,275 +1,315 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
|
|
3
|
-
```text
|
|
4
|
-
█████╗ ███╗ ██╗████████╗ ██╗ ███████╗ ██╗ ██████╗ ██████╗
|
|
5
|
-
██╔══██╗ ████╗ ██║╚══██╔══╝ ██║ ██╔════╝ ██║ ██╔═══██╗ ██╔══██╗
|
|
6
|
-
███████║ ██╔══██║ ██║ ██║ ═════ ███████╗ ██║ ██║ ██║ ██████╔╝
|
|
7
|
-
██╔══██║ ██║ ╚███║ ██║ ██║ ╚════██║ ██║ ██║ ██║ ██╔═══╝
|
|
8
|
-
██║ ██║ ██║ ╚████ ██║ ██║ ███████║ ███████╗ ╚██████╔╝ ██║
|
|
9
|
-
╚═╝ ╚═╝ ╚═╝ ╚═══ ╚═╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚═╝
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
**Stop your AI coding agent from producing slop.**
|
|
13
|
-
|
|
14
|
-
Agent skills + deterministic checks that force [OpenCode](https://opencode.ai)
|
|
15
|
-
(and other AI coding agents) to reuse what exists, compose instead of rewrite,
|
|
16
|
-
use React primitives correctly, and keep files modular — enforced at write
|
|
17
|
-
time, commit time, and push time.
|
|
18
|
-
|
|
19
|
-
[](https://github.com/hyperbolictonic/opencode-anti-slop/actions/workflows/ci.yml)
|
|
20
|
-

|
|
22
|
-
|
|
23
|
-
</div>
|
|
24
|
-
|
|
25
|
-
Built for **React and Node** projects. The gates run on **any JS/TS codebase**;
|
|
26
|
-
the skills use the standard Agent Skills format, so they also work with Claude
|
|
27
|
-
Code by copying them into `.claude/skills/`. The scripts and git hooks are
|
|
28
|
-
agent-agnostic.
|
|
29
|
-
|
|
30
|
-
## Quick start
|
|
31
|
-
|
|
32
|
-
One command — installs skills, the `/audit-ui` command, gate scripts, the CI
|
|
33
|
-
workflow, and (with `--hooks`) the git gates into the current directory:
|
|
34
|
-
|
|
35
|
-
```sh
|
|
36
|
-
npx opencode-anti-slop --hooks
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
> **npm >= 11 and
|
|
40
|
-
> `npx --allow-git=all github:hyperbolictonic/opencode-anti-slop --hooks`.
|
|
41
|
-
> The `--allow-git=all` flag is npm's gate for git-hosted packages; older npm
|
|
42
|
-
> runs without it. `--force` overwrites previously installed kit files; an
|
|
43
|
-
> explicit target dir can be passed as the first argument.
|
|
44
|
-
|
|
45
|
-
Manual equivalent (clone first, no npm involvement):
|
|
46
|
-
|
|
47
|
-
```sh
|
|
48
|
-
git clone https://github.com/hyperbolictonic/opencode-anti-slop.git
|
|
49
|
-
node opencode-anti-slop/install.mjs path/to/your-project
|
|
50
|
-
node scripts/anti-slop/generate-component-index.mjs # in your project
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Requires Node >= 18 and git. No npm dependencies — the kit installs clean into
|
|
54
|
-
any project. The installer prints the manual steps that cannot be safely
|
|
55
|
-
automated: an `AGENTS.md` snippet, optional `opencode.json` instructions,
|
|
56
|
-
`package.json` scripts, and the ESLint hooks plugin.
|
|
57
|
-
|
|
58
|
-
Skills are auto-discovered by OpenCode from `.opencode/skills/` in the target
|
|
59
|
-
repo — no registration needed. They are advertised to the model by their
|
|
60
|
-
`description` and load on demand via the `skill` tool. The `/audit-ui` command
|
|
61
|
-
arrives with the kit and produces a prioritized refactor plan from the same
|
|
62
|
-
deterministic checks.
|
|
63
|
-
|
|
64
|
-
## The problem
|
|
65
|
-
|
|
66
|
-
AI agents write code that works and rots: the same layout rebuilt per page, the
|
|
67
|
-
same helper rewritten in three files, `useEffect` doing derived-state work,
|
|
68
|
-
one 400-line file doing six jobs. Prompts like "keep it DRY" drift within a
|
|
69
|
-
session and vanish between sessions. What does not drift is a script that exits
|
|
70
|
-
non-zero — so this kit pairs model-level skills with deterministic gates:
|
|
71
|
-
|
|
72
|
-
- the **agent** is instructed (skills) to check before building;
|
|
73
|
-
- the **inventory** (`docs/COMPONENT_INDEX.md`, generated) tells it what exists;
|
|
74
|
-
- the **gates** (scripts) catch whatever slips through;
|
|
75
|
-
- the **hooks** (git) make violations fail loudly at commit and push.
|
|
76
|
-
|
|
77
|
-
## What it catches
|
|
78
|
-
|
|
79
|
-
| Slop pattern | Caught by |
|
|
80
|
-
|---|---|
|
|
81
|
-
| Rewritten layouts instead of reused components | `reuse-before-create` + `layout-composition` skills |
|
|
82
|
-
| Duplicated components, hooks, utils, server actions | duplicate gate (token-based clone detection) |
|
|
83
|
-
| Misused `useEffect`, premature `useMemo`/`useCallback`, Context abuse | `react-state-and-effects` skill |
|
|
84
|
-
| Monolithic files, god classes, oversized functions | modularity gate (size budgets) |
|
|
85
|
-
| Undocumented shared exports, stale component inventory | docs gate + index generator |
|
|
86
|
-
| Load-bearing domain rules buried in UI components | `abstraction-review` skill (post-refactor) |
|
|
87
|
-
|
|
88
|
-
A blocked commit looks like this:
|
|
89
|
-
|
|
90
|
-
```text
|
|
91
|
-
[anti-slop] modularity check on staged files...
|
|
92
|
-
src/lib/monolith.ts (321 lines):
|
|
93
|
-
[file] line 1: 321 lines (> 300)
|
|
94
|
-
[function] line 16: 93 lines (> 80)
|
|
95
|
-
[class] line 1: class ReportOrchestrator: 12 methods (> 10)
|
|
96
|
-
|
|
97
|
-
[x] Modularity check FAILED.
|
|
98
|
-
Fix: split by responsibility (see the modular-architecture skill) — transport/service/domain/data,
|
|
99
|
-
smaller functions, smaller public surfaces. Do NOT compress code, raise budgets, or disable the check.
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Every failure message names the fix and the skill that owns it. Gate thresholds
|
|
103
|
-
are per-repo tunable; weakening the check to pass it is the one forbidden fix.
|
|
104
|
-
|
|
105
|
-
## What's inside
|
|
106
|
-
|
|
107
|
-
```
|
|
108
|
-
.opencode/skills/
|
|
109
|
-
reuse-before-create/ Mandatory pre-flight: search inventory + package.json
|
|
110
|
-
before creating ANY new file. Rule of Two, extend
|
|
111
|
-
don't clone, adapter pattern for external services.
|
|
112
|
-
layout-composition/ Pages with the same structure share ONE skeleton
|
|
113
|
-
(2-column, dashboard, settings) filled via slots;
|
|
114
|
-
page-specific widgets are sub-components.
|
|
115
|
-
react-composition-patterns/ Data/UI separation, slots & children over prop
|
|
116
|
-
drilling, compound components, colocation.
|
|
117
|
-
react-state-and-effects/ useEffect/useMemo/useCallback/Context decision
|
|
118
|
-
rules with explicit bans and gotchas.
|
|
119
|
-
modular-architecture/ UI and non-UI: single-responsibility modules,
|
|
120
|
-
ports & adapters with injected dependencies, OO
|
|
121
|
-
composition, no god files/classes/functions.
|
|
122
|
-
abstraction-review/ Post-refactor review for duplication that has
|
|
123
|
-
not happened yet: load-bearing rules left in UI
|
|
124
|
-
components, structural twins, near-clone triage.
|
|
125
|
-
Reports with a cost-ranked contract; never
|
|
126
|
-
auto-extracts.
|
|
127
|
-
verify-clean-code/ End-of-task gate: typecheck -> lint -> tests ->
|
|
128
|
-
duplicate + modularity + docs checks -> index
|
|
129
|
-
freshness -> abstraction review for refactors.
|
|
130
|
-
|
|
131
|
-
.opencode/commands/
|
|
132
|
-
audit-ui.md /audit-ui — prioritized refactor plan built from
|
|
133
|
-
the deterministic checks (duplication, monoliths,
|
|
134
|
-
layout reuse, missing docs, misplaced logic).
|
|
135
|
-
|
|
136
|
-
scripts/anti-slop/
|
|
137
|
-
lib.mjs Shared scanning helpers (walk, stripNoise,
|
|
138
|
-
tokenize, lineOf, export extraction).
|
|
139
|
-
cli.mjs Shared CLI/config plumbing (flag parsing,
|
|
140
|
-
per-repo config, path resolution).
|
|
141
|
-
generate-component-index.mjs Scans src/ and (re)generates docs/COMPONENT_INDEX.md;
|
|
142
|
-
pulls each export's "When to use" from its JSDoc.
|
|
143
|
-
check-duplicates.mjs Copy/paste detector (token-based, zero deps);
|
|
144
|
-
exits non-zero when duplication exceeds the
|
|
145
|
-
per-repo threshold.
|
|
146
|
-
check-modularity.mjs Monolith gate: file >300 / function >80 lines,
|
|
147
|
-
class >10 methods. Per-file mode for git hooks.
|
|
148
|
-
check-imports.mjs Import-boundary gate: circular dependencies
|
|
149
|
-
(default) plus configurable layer rules
|
|
150
|
-
(imports.deny). Per-file cycles are tree-wide.
|
|
151
|
-
check-docs.mjs Docs gate: fails when exports lack a one-line
|
|
152
|
-
"When to use" JSDoc. Per-file mode for hooks.
|
|
153
|
-
install-hooks.mjs Installs pre-commit (staged files: modularity +
|
|
154
|
-
docs, blocking) and pre-push (duplicate +
|
|
155
|
-
import gates + lint/typecheck/test) git hooks.
|
|
156
|
-
|
|
157
|
-
templates/anti-slop-ci.yml GitHub Actions workflow the installer drops
|
|
158
|
-
into target repos (.github/workflows/).
|
|
159
|
-
install.mjs Installs skills + commands + scripts + CI
|
|
160
|
-
template into a target repo.
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
## How enforcement works
|
|
164
|
-
|
|
165
|
-
| Layer | Mechanism | Catches |
|
|
166
|
-
|---|---|---|
|
|
167
|
-
| Skills (model) | Loaded by trigger description before relevant work | Rewritten layouts, cloned components, wrong hook usage, monolith growth — at write time |
|
|
168
|
-
| Component index (data) | Regenerated by script; JSDoc feeds the "When to use" column | Duplicating things that already exist because "nothing existed" |
|
|
169
|
-
| Duplicates gate (script) | Built-in clone detector fails on duplication above threshold | Duplicates that slip past the model |
|
|
170
|
-
| Modularity gate (script) | Size budgets: file >300 lines, function >80, class >10 methods | Monolithic files, god classes/functions |
|
|
171
|
-
| Import gate (script) | Cycle detection + configurable `imports.deny` layer rules | Circular dependencies, domain logic importing transport |
|
|
172
|
-
| Git hooks (target repo) | pre-commit checks staged files only (modularity + docs); pre-push runs full gates | New violations and undocumented exports at commit time without blocking on legacy code |
|
|
173
|
-
| ESLint (config, target repo) | `eslint-plugin-react-hooks`, React Compiler rules | Hook dependency errors, redundant memoization |
|
|
174
|
-
|
|
175
|
-
Rules of thumb baked into the skills:
|
|
176
|
-
|
|
177
|
-
- **Rule of Two** — extract a shared abstraction only on the second use (or a
|
|
178
|
-
file over ~150 lines); never clone on the first variation.
|
|
179
|
-
- **One skeleton, many fillers** — pages with the same structure share one
|
|
180
|
-
layout template via slots; they differ in sub-components, not markup.
|
|
181
|
-
- **Monoliths are defects** — one responsibility per module; ports & adapters
|
|
182
|
-
with injected dependencies; size budgets enforced by the gate.
|
|
183
|
-
- **Composition before Context** — slots/`children` before creating a Context.
|
|
184
|
-
- **Effects only for external sync** — derived state during render, events in
|
|
185
|
-
handlers, data via the project's fetching library.
|
|
186
|
-
- **No threshold weakening** — fix the duplication/monolith, don't tune the check.
|
|
187
|
-
|
|
188
|
-
## Tuning per repo: `anti-slop.config.json`
|
|
189
|
-
|
|
190
|
-
Each gate reads `anti-slop.config.json` from the repo root
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
"
|
|
202
|
-
"
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
"
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
```text
|
|
4
|
+
█████╗ ███╗ ██╗████████╗ ██╗ ███████╗ ██╗ ██████╗ ██████╗
|
|
5
|
+
██╔══██╗ ████╗ ██║╚══██╔══╝ ██║ ██╔════╝ ██║ ██╔═══██╗ ██╔══██╗
|
|
6
|
+
███████║ ██╔══██║ ██║ ██║ ═════ ███████╗ ██║ ██║ ██║ ██████╔╝
|
|
7
|
+
██╔══██║ ██║ ╚███║ ██║ ██║ ╚════██║ ██║ ██║ ██║ ██╔═══╝
|
|
8
|
+
██║ ██║ ██║ ╚████ ██║ ██║ ███████║ ███████╗ ╚██████╔╝ ██║
|
|
9
|
+
╚═╝ ╚═╝ ╚═╝ ╚═══ ╚═╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═════╝ ╚═╝
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**Stop your AI coding agent from producing slop.**
|
|
13
|
+
|
|
14
|
+
Agent skills + deterministic checks that force [OpenCode](https://opencode.ai)
|
|
15
|
+
(and other AI coding agents) to reuse what exists, compose instead of rewrite,
|
|
16
|
+
use React primitives correctly, and keep files modular — enforced at write
|
|
17
|
+
time, commit time, and push time.
|
|
18
|
+
|
|
19
|
+
[](https://github.com/hyperbolictonic/opencode-anti-slop/actions/workflows/ci.yml)
|
|
20
|
+
[](https://www.npmjs.com/package/opencode-anti-slop)
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
Built for **React and Node** projects. The gates run on **any JS/TS codebase**;
|
|
26
|
+
the skills use the standard Agent Skills format, so they also work with Claude
|
|
27
|
+
Code by copying them into `.claude/skills/`. The scripts and git hooks are
|
|
28
|
+
agent-agnostic.
|
|
29
|
+
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
One command — installs skills, the `/audit-ui` command, gate scripts, the CI
|
|
33
|
+
workflow, and (with `--hooks`) the git gates into the current directory:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
npx opencode-anti-slop --hooks
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
> **npm >= 11 and installing from GitHub instead of npm?** Use
|
|
40
|
+
> `npx --allow-git=all github:hyperbolictonic/opencode-anti-slop --hooks`.
|
|
41
|
+
> The `--allow-git=all` flag is npm's gate for git-hosted packages; older npm
|
|
42
|
+
> runs without it. `--force` overwrites previously installed kit files; an
|
|
43
|
+
> explicit target dir can be passed as the first argument.
|
|
44
|
+
|
|
45
|
+
Manual equivalent (clone first, no npm involvement):
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
git clone https://github.com/hyperbolictonic/opencode-anti-slop.git
|
|
49
|
+
node opencode-anti-slop/install.mjs path/to/your-project
|
|
50
|
+
node scripts/anti-slop/generate-component-index.mjs # in your project
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Requires Node >= 18 and git. No npm dependencies — the kit installs clean into
|
|
54
|
+
any project. The installer prints the manual steps that cannot be safely
|
|
55
|
+
automated: an `AGENTS.md` snippet, optional `opencode.json` instructions,
|
|
56
|
+
`package.json` scripts, and the ESLint hooks plugin.
|
|
57
|
+
|
|
58
|
+
Skills are auto-discovered by OpenCode from `.opencode/skills/` in the target
|
|
59
|
+
repo — no registration needed. They are advertised to the model by their
|
|
60
|
+
`description` and load on demand via the `skill` tool. The `/audit-ui` command
|
|
61
|
+
arrives with the kit and produces a prioritized refactor plan from the same
|
|
62
|
+
deterministic checks.
|
|
63
|
+
|
|
64
|
+
## The problem
|
|
65
|
+
|
|
66
|
+
AI agents write code that works and rots: the same layout rebuilt per page, the
|
|
67
|
+
same helper rewritten in three files, `useEffect` doing derived-state work,
|
|
68
|
+
one 400-line file doing six jobs. Prompts like "keep it DRY" drift within a
|
|
69
|
+
session and vanish between sessions. What does not drift is a script that exits
|
|
70
|
+
non-zero — so this kit pairs model-level skills with deterministic gates:
|
|
71
|
+
|
|
72
|
+
- the **agent** is instructed (skills) to check before building;
|
|
73
|
+
- the **inventory** (`docs/COMPONENT_INDEX.md`, generated) tells it what exists;
|
|
74
|
+
- the **gates** (scripts) catch whatever slips through;
|
|
75
|
+
- the **hooks** (git) make violations fail loudly at commit and push.
|
|
76
|
+
|
|
77
|
+
## What it catches
|
|
78
|
+
|
|
79
|
+
| Slop pattern | Caught by |
|
|
80
|
+
|---|---|
|
|
81
|
+
| Rewritten layouts instead of reused components | `reuse-before-create` + `layout-composition` skills |
|
|
82
|
+
| Duplicated components, hooks, utils, server actions | duplicate gate (token-based clone detection) |
|
|
83
|
+
| Misused `useEffect`, premature `useMemo`/`useCallback`, Context abuse | `react-state-and-effects` skill |
|
|
84
|
+
| Monolithic files, god classes, oversized functions | modularity gate (size budgets) |
|
|
85
|
+
| Undocumented shared exports, stale component inventory | docs gate + index generator |
|
|
86
|
+
| Load-bearing domain rules buried in UI components | `abstraction-review` skill (post-refactor) |
|
|
87
|
+
|
|
88
|
+
A blocked commit looks like this:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
[anti-slop] modularity check on staged files...
|
|
92
|
+
src/lib/monolith.ts (321 lines):
|
|
93
|
+
[file] line 1: 321 lines (> 300)
|
|
94
|
+
[function] line 16: 93 lines (> 80)
|
|
95
|
+
[class] line 1: class ReportOrchestrator: 12 methods (> 10)
|
|
96
|
+
|
|
97
|
+
[x] Modularity check FAILED.
|
|
98
|
+
Fix: split by responsibility (see the modular-architecture skill) — transport/service/domain/data,
|
|
99
|
+
smaller functions, smaller public surfaces. Do NOT compress code, raise budgets, or disable the check.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Every failure message names the fix and the skill that owns it. Gate thresholds
|
|
103
|
+
are per-repo tunable; weakening the check to pass it is the one forbidden fix.
|
|
104
|
+
|
|
105
|
+
## What's inside
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
.opencode/skills/
|
|
109
|
+
reuse-before-create/ Mandatory pre-flight: search inventory + package.json
|
|
110
|
+
before creating ANY new file. Rule of Two, extend
|
|
111
|
+
don't clone, adapter pattern for external services.
|
|
112
|
+
layout-composition/ Pages with the same structure share ONE skeleton
|
|
113
|
+
(2-column, dashboard, settings) filled via slots;
|
|
114
|
+
page-specific widgets are sub-components.
|
|
115
|
+
react-composition-patterns/ Data/UI separation, slots & children over prop
|
|
116
|
+
drilling, compound components, colocation.
|
|
117
|
+
react-state-and-effects/ useEffect/useMemo/useCallback/Context decision
|
|
118
|
+
rules with explicit bans and gotchas.
|
|
119
|
+
modular-architecture/ UI and non-UI: single-responsibility modules,
|
|
120
|
+
ports & adapters with injected dependencies, OO
|
|
121
|
+
composition, no god files/classes/functions.
|
|
122
|
+
abstraction-review/ Post-refactor review for duplication that has
|
|
123
|
+
not happened yet: load-bearing rules left in UI
|
|
124
|
+
components, structural twins, near-clone triage.
|
|
125
|
+
Reports with a cost-ranked contract; never
|
|
126
|
+
auto-extracts.
|
|
127
|
+
verify-clean-code/ End-of-task gate: typecheck -> lint -> tests ->
|
|
128
|
+
duplicate + modularity + docs checks -> index
|
|
129
|
+
freshness -> abstraction review for refactors.
|
|
130
|
+
|
|
131
|
+
.opencode/commands/
|
|
132
|
+
audit-ui.md /audit-ui — prioritized refactor plan built from
|
|
133
|
+
the deterministic checks (duplication, monoliths,
|
|
134
|
+
layout reuse, missing docs, misplaced logic).
|
|
135
|
+
|
|
136
|
+
scripts/anti-slop/
|
|
137
|
+
lib.mjs Shared scanning helpers (walk, stripNoise,
|
|
138
|
+
tokenize, lineOf, export extraction).
|
|
139
|
+
cli.mjs Shared CLI/config plumbing (flag parsing,
|
|
140
|
+
per-repo config, path resolution).
|
|
141
|
+
generate-component-index.mjs Scans src/ and (re)generates docs/COMPONENT_INDEX.md;
|
|
142
|
+
pulls each export's "When to use" from its JSDoc.
|
|
143
|
+
check-duplicates.mjs Copy/paste detector (token-based, zero deps);
|
|
144
|
+
exits non-zero when duplication exceeds the
|
|
145
|
+
per-repo threshold.
|
|
146
|
+
check-modularity.mjs Monolith gate: file >300 / function >80 lines,
|
|
147
|
+
class >10 methods. Per-file mode for git hooks.
|
|
148
|
+
check-imports.mjs Import-boundary gate: circular dependencies
|
|
149
|
+
(default) plus configurable layer rules
|
|
150
|
+
(imports.deny). Per-file cycles are tree-wide.
|
|
151
|
+
check-docs.mjs Docs gate: fails when exports lack a one-line
|
|
152
|
+
"When to use" JSDoc. Per-file mode for hooks.
|
|
153
|
+
install-hooks.mjs Installs pre-commit (staged files: modularity +
|
|
154
|
+
docs, blocking) and pre-push (duplicate +
|
|
155
|
+
import gates + lint/typecheck/test) git hooks.
|
|
156
|
+
|
|
157
|
+
templates/anti-slop-ci.yml GitHub Actions workflow the installer drops
|
|
158
|
+
into target repos (.github/workflows/).
|
|
159
|
+
install.mjs Installs skills + commands + scripts + CI
|
|
160
|
+
template into a target repo.
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## How enforcement works
|
|
164
|
+
|
|
165
|
+
| Layer | Mechanism | Catches |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| Skills (model) | Loaded by trigger description before relevant work | Rewritten layouts, cloned components, wrong hook usage, monolith growth — at write time |
|
|
168
|
+
| Component index (data) | Regenerated by script; JSDoc feeds the "When to use" column | Duplicating things that already exist because "nothing existed" |
|
|
169
|
+
| Duplicates gate (script) | Built-in clone detector fails on duplication above threshold | Duplicates that slip past the model |
|
|
170
|
+
| Modularity gate (script) | Size budgets: file >300 lines, function >80, class >10 methods | Monolithic files, god classes/functions |
|
|
171
|
+
| Import gate (script) | Cycle detection + configurable `imports.deny` layer rules | Circular dependencies, domain logic importing transport |
|
|
172
|
+
| Git hooks (target repo) | pre-commit checks staged files only (modularity + docs); pre-push runs full gates | New violations and undocumented exports at commit time without blocking on legacy code |
|
|
173
|
+
| ESLint (config, target repo) | `eslint-plugin-react-hooks`, React Compiler rules | Hook dependency errors, redundant memoization |
|
|
174
|
+
|
|
175
|
+
Rules of thumb baked into the skills:
|
|
176
|
+
|
|
177
|
+
- **Rule of Two** — extract a shared abstraction only on the second use (or a
|
|
178
|
+
file over ~150 lines); never clone on the first variation.
|
|
179
|
+
- **One skeleton, many fillers** — pages with the same structure share one
|
|
180
|
+
layout template via slots; they differ in sub-components, not markup.
|
|
181
|
+
- **Monoliths are defects** — one responsibility per module; ports & adapters
|
|
182
|
+
with injected dependencies; size budgets enforced by the gate.
|
|
183
|
+
- **Composition before Context** — slots/`children` before creating a Context.
|
|
184
|
+
- **Effects only for external sync** — derived state during render, events in
|
|
185
|
+
handlers, data via the project's fetching library.
|
|
186
|
+
- **No threshold weakening** — fix the duplication/monolith, don't tune the check.
|
|
187
|
+
|
|
188
|
+
## Tuning per repo: `anti-slop.config.json`
|
|
189
|
+
|
|
190
|
+
Each gate reads `anti-slop.config.json` from the repo root. A JSON schema ships
|
|
191
|
+
with the kit (`anti-slop.schema.json`) and every key is validated on load —
|
|
192
|
+
unknown keys and wrong types are warned about instead of silently disabling
|
|
193
|
+
your tuning. Editor autocomplete:
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"$schema": "https://raw.githubusercontent.com/hyperbolictonic/opencode-anti-slop/master/anti-slop.schema.json",
|
|
198
|
+
"duplicates": {
|
|
199
|
+
"src": "src",
|
|
200
|
+
"minTokens": 50,
|
|
201
|
+
"threshold": 3,
|
|
202
|
+
"ignore": ["src/collections/**"]
|
|
203
|
+
},
|
|
204
|
+
"modularity": {
|
|
205
|
+
"src": "src",
|
|
206
|
+
"maxFileLines": 300,
|
|
207
|
+
"maxFunctionLines": 80,
|
|
208
|
+
"maxClassMethods": 10,
|
|
209
|
+
"ignorePaths": ["src/generated/**"]
|
|
210
|
+
},
|
|
211
|
+
"docs": {
|
|
212
|
+
"src": "src",
|
|
213
|
+
"ignorePaths": ["src/collections/**"],
|
|
214
|
+
"ignoreExports": ["metadata", "dynamic"]
|
|
215
|
+
},
|
|
216
|
+
"imports": {
|
|
217
|
+
"deny": [{ "from": "src/app/**", "forbid": "src/collections/**" }]
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
- **`duplicates`** — `minTokens`: clone window size in normalized tokens
|
|
223
|
+
(lower = more sensitive). `threshold`: max duplicated-lines **percent across
|
|
224
|
+
the scanned tree**; `0` disables failing (report only). `ignore`: globs
|
|
225
|
+
relative to `src` for **framework-idiomatic declarative files** — their
|
|
226
|
+
repetition is shape, not logic. Exclusions are printed on every run.
|
|
227
|
+
- **`modularity`** — size budgets in lines/methods. `ignorePaths`: globs
|
|
228
|
+
(scan-root- or repo-relative) for files exempt from the budgets.
|
|
229
|
+
- **`docs`** — `ignorePaths`: files exempt from the docs gate **and excluded
|
|
230
|
+
from the component index**, so the generator and the gate always agree.
|
|
231
|
+
`ignoreExports`: export names that are framework configuration; the Next.js
|
|
232
|
+
segment-config set is auto-defaulted when `next.config.*` exists.
|
|
233
|
+
- **`imports.deny`** — dependency-direction rules: a file matching `from`
|
|
234
|
+
importing a file matching `forbid` is a violation. With no rules, the import
|
|
235
|
+
gate still detects circular dependencies and says so. Deny violations are
|
|
236
|
+
never baselined.
|
|
237
|
+
- CLI flags override the config; the config overrides defaults. Hooks call the
|
|
238
|
+
scripts bare, so the config governs them automatically.
|
|
239
|
+
|
|
240
|
+
## Adopting a repo with legacy debt: baseline mode
|
|
241
|
+
|
|
242
|
+
Whole-tree gates fail on pre-existing debt, and a red CI on day one trains
|
|
243
|
+
people to ignore it. The solution is a **ratchet baseline**: snapshot today's
|
|
244
|
+
debt once, and gates fail only on violations **newer** than the snapshot.
|
|
245
|
+
|
|
246
|
+
```sh
|
|
247
|
+
# one-time, per gate — writes/merges anti-slop.baseline.json
|
|
248
|
+
node scripts/anti-slop/check-modularity.mjs --update-baseline
|
|
249
|
+
node scripts/anti-slop/check-duplicates.mjs --update-baseline
|
|
250
|
+
node scripts/anti-slop/check-docs.mjs --update-baseline
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Commit the baseline file. From then on the gates pass while the tree is no
|
|
254
|
+
worse than the snapshot, and fail the moment anyone adds new debt. After
|
|
255
|
+
cleanups, re-run `--update-baseline` to shrink the baseline — the goal is for
|
|
256
|
+
it to reach empty. The baseline records accepted debt; do **not** update it to
|
|
257
|
+
absorb new debt.
|
|
258
|
+
|
|
259
|
+
`check-imports` baselines cycles the same way; `imports.deny` layer-rule
|
|
260
|
+
violations are never baselined (rules are declared deliberately, so violating
|
|
261
|
+
one is always new).
|
|
262
|
+
|
|
263
|
+
## What counts as duplication — and what doesn't
|
|
264
|
+
|
|
265
|
+
- **Counts:** logic whose copies can drift — repeated guards, repeated
|
|
266
|
+
fetch/map flows, hand-rolled helpers for what a library or the framework
|
|
267
|
+
already provides.
|
|
268
|
+
- **Doesn't count:** declarative scaffolding a framework requires you to write.
|
|
269
|
+
The dividing line is *drift*: extract a repeated block only when its copies
|
|
270
|
+
could behave differently. If a declarative block embeds a real rule (e.g.
|
|
271
|
+
who may read a record), extract that rule to one place and reference it —
|
|
272
|
+
the config stays, the behavior is single-source.
|
|
273
|
+
- **Prefer the framework's own primitives** (auth, field-level access,
|
|
274
|
+
validation, versions) over hand-rolled equivalents — a second source of
|
|
275
|
+
truth is worse than repetition.
|
|
276
|
+
|
|
277
|
+
## Tests and self-enforcement
|
|
278
|
+
|
|
279
|
+
The gates are regression-tested with `node --test` (zero dependencies — same
|
|
280
|
+
constraint as the scripts). The `jsdocBefore` cases exist because a one-line
|
|
281
|
+
regex version of that function shipped a real bug: every export in an index
|
|
282
|
+
reported the file's first docblock. If you touch the scanners, `npm test` is
|
|
283
|
+
not optional.
|
|
284
|
+
|
|
285
|
+
```sh
|
|
286
|
+
npm test # unit tests for lib.mjs and cli.mjs
|
|
287
|
+
npm run gates # the kit runs its own gates on its own source
|
|
288
|
+
npm run index # regenerate the kit's own component index
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
CI runs the tests and the self-gates on Node 20/22/24.
|
|
292
|
+
|
|
293
|
+
## Scope, requirements and limitations
|
|
294
|
+
|
|
295
|
+
- **Stacks:** React/Node rules in the skills; gates scan `.ts` `.tsx` `.js`
|
|
296
|
+
`.jsx` `.mjs` `.mts`. Other languages need their own gates — the system
|
|
297
|
+
design (inventory + drift principle + gates + hooks) still applies.
|
|
298
|
+
- **Heuristics, not compilers:** the clone detector tokenizes rather than
|
|
299
|
+
parses; the modularity gate brace-matches rather than builds an AST. Treat
|
|
300
|
+
findings as strong signals, and `anti-slop.config.json` as the way to live
|
|
301
|
+
with rare false positives.
|
|
302
|
+
- **Agent support:** skills follow the Agent Skills format — native for
|
|
303
|
+
OpenCode, copyable to `.claude/skills/` for Claude Code. The scripts and
|
|
304
|
+
hooks work with any agent plus git. `/audit-ui` is OpenCode-specific.
|
|
305
|
+
|
|
306
|
+
## Reusable vs. project-specific
|
|
307
|
+
|
|
308
|
+
The skills are project-agnostic: they direct the agent to discover each repo's
|
|
309
|
+
own structure (`package.json`, source tree, `docs/COMPONENT_INDEX.md`) at
|
|
310
|
+
runtime. Tuning per project happens in the target repo's `anti-slop.config.json`
|
|
311
|
+
and its own `AGENTS.md` — not by editing these skills.
|
|
312
|
+
|
|
313
|
+
## License
|
|
314
|
+
|
|
315
|
+
[MIT](LICENSE)
|