opencode-anti-slop 0.0.0-stage → 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/README.md CHANGED
@@ -1,3 +1,315 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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)
package/install.mjs ADDED
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * install.mjs — install the anti-slop kit into a target repository.
4
+ *
5
+ * Also the npx entrypoint (`bin` in package.json):
6
+ * npx github:hyperbolictonic/opencode-anti-slop # install into cwd
7
+ * npx github:hyperbolictonic/opencode-anti-slop ./app # explicit target
8
+ * npx github:hyperbolictonic/opencode-anti-slop --hooks # + git hooks
9
+ *
10
+ * Copies:
11
+ * .opencode/skills/<skills> -> <target>/.opencode/skills/
12
+ * .opencode/commands/audit-ui.md -> <target>/.opencode/commands/
13
+ * scripts/anti-slop/*.mjs -> <target>/scripts/anti-slop/
14
+ * templates/anti-slop-ci.yml -> <target>/.github/workflows/
15
+ *
16
+ * Then prints the manual setup steps that cannot be safely automated
17
+ * (AGENTS.md snippet, opencode.json instructions, package.json scripts,
18
+ * ESLint setup). --hooks also installs the git pre-commit gate when the
19
+ * target is a git repo.
20
+ *
21
+ * Usage:
22
+ * npx opencode-anti-slop [target-dir] [--force] [--hooks]
23
+ * node install.mjs [target-repo-dir] [--force] [--hooks]
24
+ */
25
+
26
+ import { cpSync, existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from 'node:fs';
27
+ import path from 'node:path';
28
+ import { fileURLToPath } from 'node:url';
29
+
30
+ const kitRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
31
+ const argv = process.argv.slice(2);
32
+ const flags = new Set(argv.filter((a) => a.startsWith('--')));
33
+ const force = flags.has('--force');
34
+ const wantHooks = flags.has('--hooks');
35
+ const targetArg = argv.find((a) => !a.startsWith('--'));
36
+ const helpRequested = flags.has('-h') || flags.has('--help');
37
+
38
+ if (helpRequested) {
39
+ const help = [
40
+ 'Usage: npx github:hyperbolictonic/opencode-anti-slop [target-dir] [--force] [--hooks]',
41
+ '',
42
+ ' target-dir defaults to the current directory',
43
+ ' --force overwrite kit files that already exist in the target',
44
+ ' --hooks also install the git pre-commit gate (requires .git)',
45
+ ].join('\n');
46
+ console.log(help);
47
+ process.exit(0);
48
+ }
49
+
50
+ const target = path.resolve(targetArg ?? process.cwd());
51
+ if (!existsSync(target) || !statSync(target).isDirectory()) {
52
+ console.error(`Target directory not found: ${target}`);
53
+ process.exit(1);
54
+ }
55
+ if (path.resolve(kitRoot) === target) {
56
+ console.error('Refusing to install the kit into itself.');
57
+ process.exit(1);
58
+ }
59
+
60
+ const copyOverwrite = (src, dest) => {
61
+ if (existsSync(dest) && !force) {
62
+ console.error(`[skip] ${dest} already exists (use --force to overwrite)`);
63
+ return false;
64
+ }
65
+ cpSync(src, dest, { recursive: true });
66
+ console.log(`[ok] ${dest}`);
67
+ return true;
68
+ };
69
+
70
+ // --- skills -----------------------------------------------------------------
71
+ const skillsSrc = path.join(kitRoot, '.opencode', 'skills');
72
+ const skillsDest = path.join(target, '.opencode', 'skills');
73
+ mkdirSync(skillsDest, { recursive: true });
74
+
75
+ for (const entry of readdirSync(skillsSrc, { withFileTypes: true })) {
76
+ if (entry.isDirectory() && existsSync(path.join(skillsSrc, entry.name, 'SKILL.md'))) {
77
+ copyOverwrite(path.join(skillsSrc, entry.name), path.join(skillsDest, entry.name));
78
+ }
79
+ }
80
+
81
+ // --- scripts ----------------------------------------------------------------
82
+ const scriptsSrc = path.join(kitRoot, 'scripts', 'anti-slop');
83
+ const scriptsDest = path.join(target, 'scripts', 'anti-slop');
84
+ mkdirSync(path.dirname(scriptsDest), { recursive: true });
85
+ copyOverwrite(scriptsSrc, scriptsDest);
86
+
87
+ // --- CI workflow template -----------------------------------------------------
88
+ const templateSrc = path.join(kitRoot, 'templates', 'anti-slop-ci.yml');
89
+ const templateDest = path.join(target, '.github', 'workflows', 'anti-slop.yml');
90
+ if (existsSync(templateSrc)) {
91
+ mkdirSync(path.dirname(templateDest), { recursive: true });
92
+ copyOverwrite(templateSrc, templateDest);
93
+ }
94
+
95
+ // --- commands ----------------------------------------------------------------
96
+ // Copy command FILES individually so a pre-existing commands dir doesn't nest
97
+ // (dir-into-dir copy would produce commands/commands/x.md → /commands/x).
98
+ const commandsSrc = path.join(kitRoot, '.opencode', 'commands');
99
+ const commandsDest = path.join(target, '.opencode', 'commands');
100
+ mkdirSync(commandsDest, { recursive: true });
101
+ for (const entry of readdirSync(commandsSrc, { withFileTypes: true })) {
102
+ if (entry.isFile() && entry.name.endsWith('.md')) {
103
+ copyOverwrite(path.join(commandsSrc, entry.name), path.join(commandsDest, entry.name));
104
+ }
105
+ }
106
+
107
+ // --- starter config -----------------------------------------------------------
108
+ // Scaffold anti-slop.config.json when the target has none, so the tuning
109
+ // surface exists from day one. JSON has no comments; the reasoning for the
110
+ // starter rules is printed in the manual steps below.
111
+ const configDest = path.join(target, 'anti-slop.config.json');
112
+ if (!existsSync(configDest)) {
113
+ const starter = {
114
+ duplicates: { src: 'src', minTokens: 50, threshold: 3, ignore: [] },
115
+ modularity: { src: 'src', maxFileLines: 300, maxFunctionLines: 80, maxClassMethods: 10 },
116
+ docs: { src: 'src', ignorePaths: [], ignoreExports: [] },
117
+ imports: { deny: [] },
118
+ };
119
+ writeFileSync(configDest, `${JSON.stringify(starter, null, 2)}\n`);
120
+ console.log(`[ok] ${configDest} (starter — tune ignore lists to your framework's declarative files)`);
121
+ }
122
+
123
+ // --- manual steps -----------------------------------------------------------
124
+ console.log(`
125
+ Manual setup in ${target} (one-time):
126
+
127
+ 1. AGENTS.md — add:
128
+ ${'```markdown'}
129
+ ## Anti-slop skills
130
+ - Before creating any new component, hook, or utility: load the \`reuse-before-create\` skill.
131
+ - When building/refactoring React UI: load \`react-composition-patterns\`.
132
+ - When touching state/effects/memo/context: load \`react-state-and-effects\`.
133
+ - Before marking any coding task complete: run the \`verify-clean-code\` checklist.
134
+ ${'```'}
135
+
136
+ 2. opencode.json (optional, makes the inventory always loaded):
137
+ ${'```json'}
138
+ { "$schema": "https://opencode.ai/config.json", "instructions": ["docs/COMPONENT_INDEX.md"] }
139
+ ${'```'}
140
+
141
+ 3. package.json scripts:
142
+ ${'```json'}
143
+ "scripts:anti-slop": {
144
+ "index": "node scripts/anti-slop/generate-component-index.mjs",
145
+ "dupes": "node scripts/anti-slop/check-duplicates.mjs"
146
+ }
147
+ ${'```'}
148
+ (flatten to plain keys if you prefer: "index:components", "check:dupes")
149
+
150
+ 4. ESLint: enable eslint-plugin-react-hooks (and the React Compiler eslint
151
+ plugin if the project uses the compiler) so hook misuse fails in the linter.
152
+ Consider ignoring the vendored gate scripts from your repo lint:
153
+ \`{ ignores: ['scripts/anti-slop/**'] }\` in eslint.config — they are
154
+ self-linted by this kit's own CI.
155
+
156
+ 5. Generate the first inventory: node scripts/anti-slop/generate-component-index.mjs
157
+
158
+ 6. Git gates (recommended): git init && node scripts/anti-slop/install-hooks.mjs
159
+ - pre-commit: modularity + docs on staged files (blocking), duplicate/import reports
160
+ - pre-push: full duplicate + import gates + lint/typecheck/test via run-project-checks
161
+ - /audit-ui in OpenCode produces a prioritized refactor plan from the same checks
162
+
163
+ 7. Layer direction (imports.deny): the import gate detects cycles out of the box;
164
+ it enforces layer direction once you declare rules in anti-slop.config.json.
165
+ Starter rules to adapt (paths must match your layout):
166
+
167
+ "imports": { "deny": [
168
+ { "from": "src/components/**", "forbid": "src/collections/**" },
169
+ { "from": "src/app/**", "forbid": "src/collections/**" },
170
+ { "from": "src/access/**", "forbid": "src/app/**" }
171
+ ] }
172
+
173
+ With no rules the gate prints a cycles-only notice so a green result is never
174
+ mistaken for full architectural enforcement.
175
+ `);
176
+
177
+ // --- optional hooks ------------------------------------------------------------
178
+ if (wantHooks) {
179
+ if (!existsSync(path.join(target, '.git'))) {
180
+ console.error('[skip] --hooks requested but no .git directory in target; run git init first.');
181
+ } else {
182
+ const { spawnSync } = await import('node:child_process');
183
+ const hookScript = path.join(scriptsDest, 'install-hooks.mjs');
184
+ if (existsSync(hookScript)) {
185
+ const res = spawnSync(process.execPath, [hookScript], { stdio: 'inherit', cwd: target });
186
+ if (res.error) console.error(`[warn] hook installer failed: ${res.error.message}`);
187
+ }
188
+ }
189
+ }
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "opencode-anti-slop",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.4",
4
+ "description": "OpenCode enforcement kit: agent skills plus deterministic gates that keep AI-generated React and Node code modular, DRY, and documented",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": "install.mjs",
8
+ "files": [
9
+ "install.mjs",
10
+ "templates",
11
+ "scripts/anti-slop",
12
+ ".opencode"
13
+ ],
14
+ "keywords": [
15
+ "opencode",
16
+ "ai-agents",
17
+ "agent-skills",
18
+ "claude-code",
19
+ "react",
20
+ "nodejs",
21
+ "typescript",
22
+ "code-quality",
23
+ "dry",
24
+ "technical-debt",
25
+ "lint",
26
+ "git-hooks",
27
+ "ci"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/hyperbolictonic/opencode-anti-slop.git"
32
+ },
33
+ "homepage": "https://github.com/hyperbolictonic/opencode-anti-slop#readme",
34
+ "bugs": "https://github.com/hyperbolictonic/opencode-anti-slop/issues",
35
+ "engines": {
36
+ "node": ">=18"
37
+ },
38
+ "scripts": {
39
+ "test": "node --test scripts/anti-slop/lib.test.mjs scripts/anti-slop/cli.test.mjs scripts/anti-slop/check-imports.test.mjs",
40
+ "gates": "node scripts/anti-slop/check-modularity.mjs --src scripts/anti-slop && node scripts/anti-slop/check-imports.mjs --src scripts && node scripts/anti-slop/check-docs.mjs --src scripts/anti-slop && node scripts/anti-slop/check-duplicates.mjs --src scripts",
41
+ "index": "node scripts/anti-slop/generate-component-index.mjs --src scripts"
42
+ }
43
+ }