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.
@@ -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
- [![ci](https://github.com/hyperbolictonic/opencode-anti-slop/actions/workflows/ci.yml/badge.svg)](https://github.com/hyperbolictonic/opencode-anti-slop/actions/workflows/ci.yml)
20
- ![npm](https://img.shields.io/badge/install-npx--from--git-blue)
21
- ![zero dependencies](https://img.shields.io/badge/dependencies-zero-success)
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 the kit is git-hosted?** 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 (defaults shown):
191
-
192
- ```json
193
- {
194
- "duplicates": {
195
- "src": "src",
196
- "minTokens": 50,
197
- "threshold": 3,
198
- "ignore": ["src/collections/**"]
199
- },
200
- "modularity": {
201
- "src": "src",
202
- "maxFileLines": 300,
203
- "maxFunctionLines": 80,
204
- "maxClassMethods": 10
205
- },
206
- "imports": {
207
- "deny": [{ "from": "src/access/**", "forbid": "src/app/**" }]
208
- }
209
- }
210
- ```
211
-
212
- - `ignore` (duplicates only): globs relative to `src` for **framework-idiomatic
213
- declarative files** — collection/field configs, generated route scaffolding.
214
- Their repetition is shape a framework requires, not logic that can drift;
215
- excluding them keeps the gate focused on logic clones. Exclusions are printed
216
- on every run, so nothing is silently ignored.
217
- - `imports.deny`: rules encoding dependency direction — any file matching
218
- `from` importing a file matching `forbid` is a violation. With no rules, the
219
- import gate still detects circular dependencies.
220
- - CLI flags override the config; the config overrides defaults. Hooks call the
221
- scripts bare, so the config governs them automatically.
222
-
223
- ## What counts as duplication — and what doesn't
224
-
225
- - **Counts:** logic whose copies can drift — repeated guards, repeated
226
- fetch/map flows, hand-rolled helpers for what a library or the framework
227
- already provides.
228
- - **Doesn't count:** declarative scaffolding a framework requires you to write.
229
- The dividing line is *drift*: extract a repeated block only when its copies
230
- could behave differently. If a declarative block embeds a real rule (e.g.
231
- who may read a record), extract that rule to one place and reference it —
232
- the config stays, the behavior is single-source.
233
- - **Prefer the framework's own primitives** (auth, field-level access,
234
- validation, versions) over hand-rolled equivalents — a second source of
235
- truth is worse than repetition.
236
-
237
- ## Tests and self-enforcement
238
-
239
- The gates are regression-tested with `node --test` (zero dependencies — same
240
- constraint as the scripts). The `jsdocBefore` cases exist because a one-line
241
- regex version of that function shipped a real bug: every export in an index
242
- reported the file's first docblock. If you touch the scanners, `npm test` is
243
- not optional.
244
-
245
- ```sh
246
- npm test # unit tests for lib.mjs and cli.mjs
247
- npm run gates # the kit runs its own gates on its own source
248
- npm run index # regenerate the kit's own component index
249
- ```
250
-
251
- CI runs the tests and the self-gates on Node 20/22/24.
252
-
253
- ## Scope, requirements and limitations
254
-
255
- - **Stacks:** React/Node rules in the skills; gates scan `.ts` `.tsx` `.js`
256
- `.jsx` `.mjs` `.mts`. Other languages need their own gates — the system
257
- design (inventory + drift principle + gates + hooks) still applies.
258
- - **Heuristics, not compilers:** the clone detector tokenizes rather than
259
- parses; the modularity gate brace-matches rather than builds an AST. Treat
260
- findings as strong signals, and `anti-slop.config.json` as the way to live
261
- with rare false positives.
262
- - **Agent support:** skills follow the Agent Skills format — native for
263
- OpenCode, copyable to `.claude/skills/` for Claude Code. The scripts and
264
- hooks work with any agent plus git. `/audit-ui` is OpenCode-specific.
265
-
266
- ## Reusable vs. project-specific
267
-
268
- The skills are project-agnostic: they direct the agent to discover each repo's
269
- own structure (`package.json`, source tree, `docs/COMPONENT_INDEX.md`) at
270
- runtime. Tuning per project happens in the target repo's `anti-slop.config.json`
271
- and its own `AGENTS.md` — not by editing these skills.
272
-
273
- ## License
274
-
275
- [MIT](LICENSE)
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
+ [![ci](https://github.com/hyperbolictonic/opencode-anti-slop/actions/workflows/ci.yml/badge.svg)](https://github.com/hyperbolictonic/opencode-anti-slop/actions/workflows/ci.yml)
20
+ [![npm](https://img.shields.io/npm/v/opencode-anti-slop)](https://www.npmjs.com/package/opencode-anti-slop)
21
+ ![zero dependencies](https://img.shields.io/badge/dependencies-zero-success)
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)