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/.opencode/commands/audit-ui.md +34 -0
- package/.opencode/skills/abstraction-review/SKILL.md +76 -0
- package/.opencode/skills/layout-composition/SKILL.md +78 -0
- package/.opencode/skills/modular-architecture/SKILL.md +65 -0
- package/.opencode/skills/react-composition-patterns/SKILL.md +43 -0
- package/.opencode/skills/react-state-and-effects/SKILL.md +61 -0
- package/.opencode/skills/reuse-before-create/SKILL.md +108 -0
- package/.opencode/skills/verify-clean-code/SKILL.md +81 -0
- package/LICENSE +21 -0
- package/README.md +315 -3
- package/install.mjs +189 -0
- package/package.json +41 -4
- package/scripts/anti-slop/baseline.mjs +147 -0
- package/scripts/anti-slop/check-docs.mjs +109 -0
- package/scripts/anti-slop/check-duplicates.mjs +207 -0
- package/scripts/anti-slop/check-imports.mjs +251 -0
- package/scripts/anti-slop/check-imports.test.mjs +128 -0
- package/scripts/anti-slop/check-modularity.mjs +213 -0
- package/scripts/anti-slop/cli.mjs +259 -0
- package/scripts/anti-slop/cli.test.mjs +202 -0
- package/scripts/anti-slop/generate-component-index.mjs +162 -0
- package/scripts/anti-slop/install-hooks.mjs +97 -0
- package/scripts/anti-slop/lib.mjs +210 -0
- package/scripts/anti-slop/lib.test.mjs +178 -0
- package/scripts/anti-slop/run-project-checks.mjs +73 -0
- package/templates/anti-slop-ci.yml +22 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Audit code for duplication, monolithic structure, missing docs, and layout-reuse violations
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Load the `reuse-before-create`, `layout-composition`, `react-composition-patterns`, and `modular-architecture` skills before auditing.
|
|
6
|
+
|
|
7
|
+
Run the deterministic checks first and read their full output:
|
|
8
|
+
|
|
9
|
+
!`node scripts/anti-slop/check-duplicates.mjs --min-tokens 20 --threshold 0`
|
|
10
|
+
|
|
11
|
+
!`node scripts/anti-slop/check-modularity.mjs --report-only`
|
|
12
|
+
|
|
13
|
+
Scope: $ARGUMENTS. If no scope was given, audit the whole source tree.
|
|
14
|
+
|
|
15
|
+
Produce a prioritized report (no code changes — a refactor plan only):
|
|
16
|
+
|
|
17
|
+
1. **Duplication clusters** (from the clone report): for each clone, name the
|
|
18
|
+
existing component/hook/util that should absorb it, or the new shared
|
|
19
|
+
abstraction to extract, with exact file paths.
|
|
20
|
+
2. **Monoliths** (from the size report): propose a concrete decomposition for
|
|
21
|
+
each flagged file/function/class — transport/service/domain/data or
|
|
22
|
+
container/presentational splits, one paragraph each.
|
|
23
|
+
3. **Layout reuse**: pages that re-implement the same skeletons (columns,
|
|
24
|
+
shells, headers). Propose the shared template + slot design.
|
|
25
|
+
4. **Missing documentation**: shared components/hooks/utils whose exports lack
|
|
26
|
+
a one-line `/** When to use ... */` JSDoc; list them in priority order.
|
|
27
|
+
5. **Misplaced logic and abstraction candidates**: load the `abstraction-review`
|
|
28
|
+
skill and apply it to the scope — load-bearing rules implemented inside
|
|
29
|
+
presentational components, and structural twins (same skeleton, different
|
|
30
|
+
slot content) that no longer trip the exact-clone threshold.
|
|
31
|
+
|
|
32
|
+
Order items by impact: duplication that will multiply next, then monoliths
|
|
33
|
+
actively being extended, then layout rewrites waiting to happen, then docs.
|
|
34
|
+
End with the three highest-value first actions.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Abstraction Review
|
|
3
|
+
description: Post-refactor review for missed abstractions — load-bearing domain logic living in UI components, and repeated skeletons with different content. Trigger AFTER completing a refactor or extracting components, and before finishing any task that moved shared logic. Reports; does not auto-extract.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Abstraction Review
|
|
7
|
+
|
|
8
|
+
Gates catch duplication that already exists. This review catches duplication
|
|
9
|
+
that has not happened yet: logic that is single-consumer today but load-bearing,
|
|
10
|
+
and the same skeleton built twice with different fillings.
|
|
11
|
+
|
|
12
|
+
Run it **after** a refactor lands, not during. It reports and ranks; it does
|
|
13
|
+
not auto-extract. Speculative abstraction is the same failure as speculative
|
|
14
|
+
feature — the owner chooses what to build from the report.
|
|
15
|
+
|
|
16
|
+
## 1. Misplaced logic — the load-bearing test
|
|
17
|
+
|
|
18
|
+
Scan every file the task touched, especially anything under `components/` or
|
|
19
|
+
route files:
|
|
20
|
+
|
|
21
|
+
- Does a presentational file contain a rule that decides **meaning** — evidence
|
|
22
|
+
strength, permission, money, time/thresholds, domain state names, dates that
|
|
23
|
+
carry legal weight?
|
|
24
|
+
- The test: *could a second surface (back office, export, report, audit) need
|
|
25
|
+
this exact rule?* If yes, it belongs in `src/lib/` (or `src/access/`), with
|
|
26
|
+
the UI consuming it. Load-bearing rules — evidence, access, audit, money —
|
|
27
|
+
are extracted even at one consumer; ordinary helpers wait for the second
|
|
28
|
+
consumer (Rule of Two).
|
|
29
|
+
- Check the rule-locations table in AGENTS.md: after this change, is every row
|
|
30
|
+
still true? Add or update a row for every rule the task wrote or moved. A
|
|
31
|
+
rule whose location is not written down is a rule that will be re-implemented.
|
|
32
|
+
|
|
33
|
+
## 2. Same skeleton, different filling — the slot test
|
|
34
|
+
|
|
35
|
+
Run the clone report at low sensitivity
|
|
36
|
+
(`node scripts/anti-slop/check-duplicates.mjs --min-tokens 20 --threshold 0`)
|
|
37
|
+
and look past exact clones for **structural twins**:
|
|
38
|
+
|
|
39
|
+
- Two components rendering the same row/card/section shape with different
|
|
40
|
+
trailing content (a badge vs a button vs a form) → one shared presentational
|
|
41
|
+
component with a slot prop. Both consumers must exist today.
|
|
42
|
+
- Cross-surface candidates must respect the surfaces doctrine: only
|
|
43
|
+
presentational primitives go in `components/ui/`; surface behavior stays in
|
|
44
|
+
the surface.
|
|
45
|
+
- Two views that differ for *product* reasons are not duplication — do not
|
|
46
|
+
force unification by turning the differences into config flags.
|
|
47
|
+
|
|
48
|
+
## 3. Near-clone triage
|
|
49
|
+
|
|
50
|
+
For each block in the low-sensitivity clone report, classify:
|
|
51
|
+
|
|
52
|
+
- **Real logic duplicated** → extract now (Rule of Two met; see
|
|
53
|
+
reuse-before-create).
|
|
54
|
+
- **Framework scaffolding / declarative shape** → leave alone (see
|
|
55
|
+
reuse-before-create, "What NOT to extract").
|
|
56
|
+
- **Unclear** → list it with the single question whose answer would settle it.
|
|
57
|
+
|
|
58
|
+
## Output contract — Second-order opportunities
|
|
59
|
+
|
|
60
|
+
End the task report with a ranked list; nothing is built without the owner
|
|
61
|
+
picking from it:
|
|
62
|
+
|
|
63
|
+
1. **What** the abstraction is and **where** it would live (exact file path).
|
|
64
|
+
2. **Why**: load-bearing rule (name the failure a diverged copy would cause),
|
|
65
|
+
or second consumer (name both).
|
|
66
|
+
3. **Cost**: S / M / L.
|
|
67
|
+
4. Only items that meet the bar. A review listing twenty items is noise.
|
|
68
|
+
|
|
69
|
+
## Anti-patterns
|
|
70
|
+
|
|
71
|
+
- "We might need it in the back office someday" — that is speculation, not a
|
|
72
|
+
load-bearing rule and not a second consumer.
|
|
73
|
+
- Forcing two product-different views into one config-flagged component.
|
|
74
|
+
- Reporting everything the low-sensitivity scan finds instead of triaging it.
|
|
75
|
+
- Extracting during the refactor instead of reporting after it — the review
|
|
76
|
+
keeps the refactor's scope clean.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Layout Composition
|
|
3
|
+
description: Enforces shared layout skeletons with slotted content — pages with the same structure (2-column, dashboard, settings) MUST reuse one template with different sub-components, not re-implement markup. Trigger when building or refactoring pages, layouts, screens, grids, or navigation shells.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Layout Composition
|
|
7
|
+
|
|
8
|
+
A layout is a skeleton (columns, regions, chrome), not content. Pages with the
|
|
9
|
+
same structure must share one skeleton and differ only in what they put in the
|
|
10
|
+
slots.
|
|
11
|
+
|
|
12
|
+
## Core rule
|
|
13
|
+
|
|
14
|
+
When N pages share the same structural shape:
|
|
15
|
+
|
|
16
|
+
1. Extract the skeleton once: `<TwoColumnLayout>`, `<DashboardShell>`,
|
|
17
|
+
`<SettingsLayout>` — placement only, no page-specific content, no data
|
|
18
|
+
fetching.
|
|
19
|
+
2. Each page composes: page decides content, skeleton decides placement.
|
|
20
|
+
3. Page-specific widgets are their own components, passed in via slots.
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
// Good: three pages, one skeleton
|
|
24
|
+
<TwoColumnLayout sidebar={<AuditFilters />}>
|
|
25
|
+
<AuditTable events={events} />
|
|
26
|
+
</TwoColumnLayout>
|
|
27
|
+
|
|
28
|
+
<TwoColumnLayout sidebar={<UserFilters />}>
|
|
29
|
+
<UserTable users={users} />
|
|
30
|
+
</TwoColumnLayout>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
// Bad: the second and third page re-implement the 2-column markup
|
|
35
|
+
export function UsersPage() {
|
|
36
|
+
return (
|
|
37
|
+
<div className="grid grid-cols-[280px_1fr] gap-6">
|
|
38
|
+
<aside>...</aside>
|
|
39
|
+
<main>...</main>
|
|
40
|
+
</div>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Signals that a skeleton wants to exist:
|
|
46
|
+
|
|
47
|
+
- Copy-pasting grid/flex structure between two or more pages.
|
|
48
|
+
- Repeating the same `<aside>`/`<main>`/header/scaffold markup.
|
|
49
|
+
- "New page" tasks that start by writing container divs instead of picking a
|
|
50
|
+
template.
|
|
51
|
+
|
|
52
|
+
## Design rules for skeletons
|
|
53
|
+
|
|
54
|
+
- **Slots over config.** Named slots (`sidebar`, `header`, `actions`,
|
|
55
|
+
`footer`) take JSX. Never pass booleans like `showSidebar` down a template —
|
|
56
|
+
pass the content or don't render the slot.
|
|
57
|
+
- **Skeletons are dumb.** No fetching, no derived state, no store access.
|
|
58
|
+
Only responsive behavior and placement.
|
|
59
|
+
- **Empty regions stay empty.** An unfilled slot renders nothing — not a
|
|
60
|
+
placeholder "coming soon" block.
|
|
61
|
+
- **Nesting composes.** `<DashboardShell>` may use `<PageHeader>` and
|
|
62
|
+
`<TwoColumnLayout>` internally; pages never reach past the outermost shell.
|
|
63
|
+
- **Responsive rules live in the skeleton** (where the grid is), not repeated
|
|
64
|
+
per page.
|
|
65
|
+
|
|
66
|
+
## Extraction timing
|
|
67
|
+
|
|
68
|
+
- First occurrence: build it locally but structure it as slots from day one.
|
|
69
|
+
- Second occurrence: extract the skeleton (Rule of Two — see
|
|
70
|
+
`reuse-before-create`) and make both pages use it. Never write a third copy.
|
|
71
|
+
|
|
72
|
+
## Anti-patterns
|
|
73
|
+
|
|
74
|
+
- `UsersLayout.tsx` + `AuditLayout.tsx` + `ReportsLayout.tsx` that differ only
|
|
75
|
+
in imported children.
|
|
76
|
+
- A layout component with 10 `show*/hide*` props instead of slots.
|
|
77
|
+
- "Template + slot" where the template secretly renders page content.
|
|
78
|
+
- Pages hand-rolling the same max-width/spacing/padding wrapper.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Modular Architecture
|
|
3
|
+
description: Enforces modular, object-oriented design for UI and non-UI code — single-responsibility modules, dependency injection behind adapters, and no monolithic files, god classes, or god functions. Trigger when writing or refactoring Node/backend services, API routes, domain logic, shared libraries, or splitting large files.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Modular Architecture
|
|
7
|
+
|
|
8
|
+
Monolithic code is a defect, not a style. Every module has one reason to
|
|
9
|
+
change; behavior enters and leaves through narrow interfaces. Applies to UI
|
|
10
|
+
and non-UI code alike.
|
|
11
|
+
|
|
12
|
+
## Module rules
|
|
13
|
+
|
|
14
|
+
- **Single responsibility per module/file.** A file that mixes persistence,
|
|
15
|
+
business rules, and transport (HTTP) is a monolith — split by responsibility:
|
|
16
|
+
transport -> service -> domain -> data access.
|
|
17
|
+
- **Program to interfaces.** Domain logic depends on small interfaces/ports
|
|
18
|
+
(`UserRepository`, `MailSender`, `Clock`), never directly on SDKs, DB
|
|
19
|
+
clients, `fetch`, or `process.env`. Infrastructure lives in adapters that
|
|
20
|
+
implement those ports.
|
|
21
|
+
- **Inject dependencies; don't import globals.** Constructors/factories accept
|
|
22
|
+
their dependencies. This makes composition explicit and testing trivial
|
|
23
|
+
(pass fakes, no module mocking). One composition root (or config module per
|
|
24
|
+
entrypoint) wires concrete adapters to ports.
|
|
25
|
+
- **One direction of dependencies.** Domain logic imports nothing from
|
|
26
|
+
transport or infrastructure. Framework code (Express handlers, Next.js
|
|
27
|
+
route files) is a thin shell delegating to framework-free services.
|
|
28
|
+
- **Shared logic is extracted, not copied** (see `reuse-before-create`); shared
|
|
29
|
+
modules are consumed through their public exports, never by reaching into
|
|
30
|
+
internals.
|
|
31
|
+
- **State stays narrow.** No module-level mutable singletons beyond the
|
|
32
|
+
composition root. Class state is `private`/`#private` until proven needed.
|
|
33
|
+
|
|
34
|
+
## Object-oriented rules
|
|
35
|
+
|
|
36
|
+
- **Classes for things with state + behavior** (entities, adapters, parsers,
|
|
37
|
+
clients). Plain functions for stateless operations. Don't force everything
|
|
38
|
+
into classes, and don't write 500-line procedural scripts either.
|
|
39
|
+
- **Small public surface.** Export the minimum; if a class exposes > ~10
|
|
40
|
+
public methods, it is doing several jobs — split by responsibility.
|
|
41
|
+
- **Composition over inheritance.** Extend behavior by wrapping/injecting,
|
|
42
|
+
not by deep class hierarchies. Inheritance more than one level deep needs a
|
|
43
|
+
justification in the Reuse Plan.
|
|
44
|
+
- **God-object test:** naming a class `Manager`, `Helper`, `Utils`, `Common`,
|
|
45
|
+
or `Service` alone is a design smell — name by responsibility
|
|
46
|
+
(`InvoiceMailer`, `SessionStore`).
|
|
47
|
+
|
|
48
|
+
## Size budgets (enforced by scripts/anti-slop/check-modularity.mjs)
|
|
49
|
+
|
|
50
|
+
- File: <= ~300 lines.
|
|
51
|
+
- Function/method: <= ~80 lines.
|
|
52
|
+
- Class: <= ~10 public methods.
|
|
53
|
+
|
|
54
|
+
Exceeding a budget is a signal to split by responsibility — not to compress
|
|
55
|
+
code or disable the check.
|
|
56
|
+
|
|
57
|
+
## Anti-patterns
|
|
58
|
+
|
|
59
|
+
- 500-line `utils.ts` accumulating everything nobody could place elsewhere.
|
|
60
|
+
- Route/handler files containing validation + business rules + DB queries +
|
|
61
|
+
email sending inline.
|
|
62
|
+
- `import { pool } from "../db"` inside domain logic instead of an injected
|
|
63
|
+
repository interface.
|
|
64
|
+
- A class with 25 public methods, 3 of which are used.
|
|
65
|
+
- "Refactoring" a monolith by moving it into two monoliths.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: React Composition Patterns
|
|
3
|
+
description: Enforces composable React architecture — container/presentational separation, slots and children over prop drilling, compound components, and feature colocation. Trigger when building, refactoring, or reviewing React UI components, pages, and layouts.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# React Composition Patterns
|
|
7
|
+
|
|
8
|
+
## Architectural rules
|
|
9
|
+
|
|
10
|
+
- **Separate data from UI.** Data fetching, routing params, and mutations stay in
|
|
11
|
+
pages/containers. Presentational components receive plain data and callbacks as
|
|
12
|
+
props and stay stateless where possible. A component that both fetches and
|
|
13
|
+
styles is a smell — split it.
|
|
14
|
+
- **Children and slots over prop drilling.** Pass JSX through `children` and named
|
|
15
|
+
slot props (`header`, `actions`, `footer`) instead of threading config objects
|
|
16
|
+
down multiple levels. If you are passing a prop through a component that doesn't
|
|
17
|
+
use it, restructure with slots.
|
|
18
|
+
- **Compound components for multi-part UI.** Cards, modals, tables, tabs, toolbars:
|
|
19
|
+
expose sub-components (`<Card.Header>`, `<Card.Body>`) rather than one component
|
|
20
|
+
with a pile of boolean/config props. More than ~10 props, or several booleans
|
|
21
|
+
that toggle unrelated regions, means the component wants to be compound.
|
|
22
|
+
- **Variant props, not sibling components.** Small appearance differences are
|
|
23
|
+
`variant`/`size` props on one component, not `PrimaryButton.tsx` next to
|
|
24
|
+
`SecondaryButton.tsx`.
|
|
25
|
+
- **Colocate, then promote.** Feature-specific subcomponents and hooks live in the
|
|
26
|
+
feature directory. Promote to shared `components/` only when a second feature
|
|
27
|
+
needs them (see the reuse-before-create skill).
|
|
28
|
+
|
|
29
|
+
## Layout rules
|
|
30
|
+
|
|
31
|
+
- One page shell per application concern. New pages compose existing layouts;
|
|
32
|
+
they never re-implement header/sidebar/footer scaffolding.
|
|
33
|
+
- Layout components take slots so pages decide content; layouts decide placement.
|
|
34
|
+
|
|
35
|
+
## Anti-patterns (gotchas)
|
|
36
|
+
|
|
37
|
+
- Monolithic page components: one file doing fetching, derived state, layout,
|
|
38
|
+
and markup.
|
|
39
|
+
- Prop drilling: a `config`/`settings` object threaded 3+ levels deep instead of
|
|
40
|
+
composing children.
|
|
41
|
+
- Boolean-prop explosion: `<Widget isPrimary isCompact showHeader hideFooter>`.
|
|
42
|
+
- Premature abstraction on first use (violates Rule of Two) — colocate first.
|
|
43
|
+
- Over-modularizing trivial one-offs into many files of boilerplate.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: React State, Effects and Context
|
|
3
|
+
description: Enforces correct usage of useState, useEffect, useMemo, useCallback, and React Context — bans derived-state effects, event-handling effects, premature memoization, and Context misuse. Trigger when managing state, side effects, memoization, or shared data in React code.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# React State, Effects and Context
|
|
7
|
+
|
|
8
|
+
## 1. `useEffect` — strict bans
|
|
9
|
+
|
|
10
|
+
- **Never compute derived state in an effect.** Calculate during render:
|
|
11
|
+
`const fullName = `${first} ${last}``. If it needs to be cached, `useMemo` —
|
|
12
|
+
never `useEffect` + `setState`.
|
|
13
|
+
- **Never react to user events with an effect.** Form submits, clicks, toasts,
|
|
14
|
+
analytics belong in event handlers, not in an effect watching state change.
|
|
15
|
+
- **Never hand-roll data fetching in `useEffect` with raw `fetch`** when the
|
|
16
|
+
project has React Query / SWR / RTK / Server Components (check `package.json`
|
|
17
|
+
first). Use the project's existing mechanism.
|
|
18
|
+
- **Valid uses are narrow:** synchronizing with external systems outside React —
|
|
19
|
+
DOM APIs (observers, measurement), WebSockets, `localStorage` listeners,
|
|
20
|
+
third-party widget instances, non-React subscriptions. Always return a cleanup
|
|
21
|
+
function that undoes exactly what was set up.
|
|
22
|
+
- **Effect dependency arrays must be complete.** No suppression comments to make
|
|
23
|
+
warnings go away; if the array needs a lie, the design is wrong.
|
|
24
|
+
|
|
25
|
+
## 2. `useMemo` / `useCallback` — no premature memoization
|
|
26
|
+
|
|
27
|
+
Banned unless one of the "allowed" cases below applies:
|
|
28
|
+
|
|
29
|
+
- Never wrap primitives, string formatting, small array maps, or cheap boolean
|
|
30
|
+
logic.
|
|
31
|
+
- `useMemo` is allowed only for: genuinely expensive transformations
|
|
32
|
+
(filter/sort/aggregate on large datasets), or preserving referential identity
|
|
33
|
+
of an object/array that flows into a `React.memo` child or an effect
|
|
34
|
+
dependency array.
|
|
35
|
+
- `useCallback` is allowed only for: callbacks passed to memoized children, or
|
|
36
|
+
functions used inside another hook's dependency array.
|
|
37
|
+
- The React Compiler (if enabled in the project) makes most manual memoization
|
|
38
|
+
redundant — do not add it "just in case".
|
|
39
|
+
|
|
40
|
+
## 3. Context — composition first
|
|
41
|
+
|
|
42
|
+
- **Try composition first.** Before creating a Context to avoid prop drilling,
|
|
43
|
+
check whether passing JSX via `children`/slots solves it. Most "prop drilling"
|
|
44
|
+
problems are actually composition problems (see react-composition-patterns).
|
|
45
|
+
- **Context is for low-frequency global or subtree values:** theme, auth user,
|
|
46
|
+
locale, i18n, compound-component coordination.
|
|
47
|
+
- **Never put high-frequency state in Context:** form inputs, scroll position,
|
|
48
|
+
hover state, server-cache data. These re-render every consumer on every
|
|
49
|
+
keystroke. Use component-local state, or a store library if the project has
|
|
50
|
+
one.
|
|
51
|
+
- If a Context mixes a fast-changing value with a stable one, split into two
|
|
52
|
+
contexts (value vs. dispatch/actions) so consumers can subscribe separately.
|
|
53
|
+
|
|
54
|
+
## Anti-patterns (gotchas)
|
|
55
|
+
|
|
56
|
+
- `useEffect(() => setState(derive(state)), [state])` — infinite-loop-prone and
|
|
57
|
+
always wrong.
|
|
58
|
+
- `useEffect` + `fetch` + local loading flags when React Query exists.
|
|
59
|
+
- `useMemo` on `<100 item` arrays to "optimize" a table that renders fine.
|
|
60
|
+
- A `FormContext` wrapping every input on the page.
|
|
61
|
+
- New Context created where a slot prop would do.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Reuse Before Create
|
|
3
|
+
description: Enforces reuse of existing layouts, UI components, hooks, services, and utilities before creating any new file, component, hook, helper, or abstraction. Trigger BEFORE creating any new file, component, hook, utility, service, or module — and before duplicating anything that might already exist.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Reuse Before Create
|
|
7
|
+
|
|
8
|
+
Agents duplicate code because they don't know what already exists. This skill makes
|
|
9
|
+
"what already exists" a mandatory check, not an afterthought.
|
|
10
|
+
|
|
11
|
+
## 1. Pre-flight check (mandatory, before writing ANY new file)
|
|
12
|
+
|
|
13
|
+
In order:
|
|
14
|
+
|
|
15
|
+
1. **Read the inventory.** Read `docs/COMPONENT_INDEX.md` if it exists. If it does not
|
|
16
|
+
exist, generate it first:
|
|
17
|
+
`node scripts/anti-slop/generate-component-index.mjs`
|
|
18
|
+
(paths are relative to the repo root; adjust only if the repo uses different locations).
|
|
19
|
+
2. **Search the tree.** Grep/glob the source directories for anything that already
|
|
20
|
+
solves the problem: components, layouts, hooks, utils, services, constants.
|
|
21
|
+
Search by concept ("format date", "modal", "auth guard"), not just by exact name.
|
|
22
|
+
3. **Check `package.json`.** Before writing any custom utility, check whether an
|
|
23
|
+
installed library already does it (date formatting, class merging, debounce,
|
|
24
|
+
deep-clone, validation, state management, data fetching). Prefer the installed
|
|
25
|
+
library. Only suggest a new dependency if nothing installed fits, and say so
|
|
26
|
+
explicitly with the reason.
|
|
27
|
+
4. **Check for near-duplicates.** If an existing component solves 80% of the need,
|
|
28
|
+
the answer is to extend it (props, `children`, variants) — never to clone it
|
|
29
|
+
into a new file.
|
|
30
|
+
|
|
31
|
+
## 2. Hard rules
|
|
32
|
+
|
|
33
|
+
- **Never rewrite page shells.** Wrap pages/screens in the existing layout
|
|
34
|
+
components. If a new page needs something the layout doesn't support, extend the
|
|
35
|
+
layout with a slot (`children`, `header`, `actions` props) instead of building a
|
|
36
|
+
parallel layout.
|
|
37
|
+
- **Rule of Two.** Create a NEW shared component/hook/util only when the pattern is
|
|
38
|
+
used in 2+ places, or a single file exceeds ~150 lines. Otherwise keep it local
|
|
39
|
+
to the feature. Conversely: on the second duplication, extract — do not write a
|
|
40
|
+
third copy.
|
|
41
|
+
- **Extend, don't clone.** Existing primitives are extended via props, variants,
|
|
42
|
+
render props, `children`, or slots. "I needed a slightly different version" is an
|
|
43
|
+
extension task, not a new-file task.
|
|
44
|
+
- **Colocate, then promote.** Feature-specific helpers live inside the feature
|
|
45
|
+
directory. Only promote to shared directories (`components/`, `hooks/`, `lib/`)
|
|
46
|
+
when a second feature needs it — and then delete the local copy.
|
|
47
|
+
|
|
48
|
+
## 2b. What NOT to extract — framework scaffolding is not duplication
|
|
49
|
+
|
|
50
|
+
- **Declarative config is shape, not logic.** Repetition a framework *makes you
|
|
51
|
+
write* — collection/field definitions, route tables, schema registrations,
|
|
52
|
+
DI container setup — cannot "drift" the way two copies of logic can. The
|
|
53
|
+
Rule of Two does not apply to it, and the clone gate excludes such files
|
|
54
|
+
(list them under `ignore` in `anti-slop.config.json`). Do not wrap framework
|
|
55
|
+
scaffolding in your own abstraction layers just because it repeats.
|
|
56
|
+
- **Framework primitives beat hand-rolled ones.** Before building any
|
|
57
|
+
abstraction, check whether the framework already provides the primitive:
|
|
58
|
+
auth/session handling, field-level access, validation, caching, file
|
|
59
|
+
handling, background jobs. Reimplementing what the platform gives you
|
|
60
|
+
creates a second source of truth — that is worse than repetition.
|
|
61
|
+
- **The dividing line is drift.** Extract a repeated block only when its copies
|
|
62
|
+
could *behave differently* — a rule enforced in two places, an algorithm
|
|
63
|
+
fixed in two spots. Shape that stays identical by nature of being
|
|
64
|
+
declarative needs no extraction. If a declarative block embeds a real rule
|
|
65
|
+
(e.g. who may read a record), extract that *rule* into one place and have
|
|
66
|
+
every block reference it — the config stays, the behavior is single-source.
|
|
67
|
+
|
|
68
|
+
## 3. Backend / Node rules
|
|
69
|
+
|
|
70
|
+
- **Wrap external services behind adapters.** Direct SDK calls, `fetch` to third
|
|
71
|
+
parties, and database clients live inside adapter/service modules. Feature code
|
|
72
|
+
consumes the adapter interface. This keeps services swappable and mockable.
|
|
73
|
+
- **One env/config loader.** Never add a second way to read configuration. Extend
|
|
74
|
+
the existing config module.
|
|
75
|
+
- **Shared logic goes in shared modules.** Validation, date/time, money, IDs,
|
|
76
|
+
serialization: if it exists in one service, the next service imports it — it does
|
|
77
|
+
not copy it.
|
|
78
|
+
- **Monoliths are defects.** If a file is growing past ~300 lines or a function
|
|
79
|
+
past ~80, stop and split by responsibility (transport/service/domain/data —
|
|
80
|
+
see the `modular-architecture` skill). Do not keep extending a god file.
|
|
81
|
+
|
|
82
|
+
## 4. Output requirement — the Reuse Plan
|
|
83
|
+
|
|
84
|
+
Before writing code, state briefly:
|
|
85
|
+
|
|
86
|
+
1. What you are reusing (exact file paths from the index/tree/package.json).
|
|
87
|
+
2. What you are extending (which file, which mechanism: props/slots/variant).
|
|
88
|
+
3. Anything genuinely new, and why existing abstractions cannot cover it.
|
|
89
|
+
|
|
90
|
+
If the Reuse Plan says "nothing new" there should be no new files in the diff.
|
|
91
|
+
If a task produces a new file that duplicates an existing one, that is a bug in
|
|
92
|
+
the plan, not in the code.
|
|
93
|
+
|
|
94
|
+
## 5. Keep the index true
|
|
95
|
+
|
|
96
|
+
Whenever this session adds a new shared component, hook, layout, or utility,
|
|
97
|
+
give it a one-line `/** When to use ... */` JSDoc above the export and update
|
|
98
|
+
`docs/COMPONENT_INDEX.md` (rerun the generator — it picks the description up
|
|
99
|
+
from the JSDoc automatically). A stale or undocumented index defeats the whole
|
|
100
|
+
system: the next session will duplicate what it cannot find.
|
|
101
|
+
|
|
102
|
+
## Anti-patterns (gotchas)
|
|
103
|
+
|
|
104
|
+
- Over-modularizing: tiny one-off features split into 5 files of boilerplate.
|
|
105
|
+
- Copying an existing component and changing one class name.
|
|
106
|
+
- Writing `formatDate`, `cn`, `debounce`, or `truncate` by hand while the
|
|
107
|
+
equivalent sits in `package.json`.
|
|
108
|
+
- Creating a "v2" layout next to the existing layout instead of extending it.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Verify Clean Code
|
|
3
|
+
description: Deterministic end-of-task verification — lint, typecheck, tests, duplicate-code detection, import boundaries, docs, and component-index freshness. Trigger before marking ANY coding task complete.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Verify Clean Code
|
|
7
|
+
|
|
8
|
+
A task is not complete when the code works; it is complete when the checks pass.
|
|
9
|
+
Run from the repo root. Derive exact commands from `package.json` — do not guess.
|
|
10
|
+
|
|
11
|
+
## 1. Standard verification (in this order)
|
|
12
|
+
|
|
13
|
+
1. **Typecheck** — e.g. `tsc --noEmit` (or the repo's `typecheck` script).
|
|
14
|
+
2. **Lint** — the repo's `lint` script. Fix violations you introduced; do not
|
|
15
|
+
disable rules or add `eslint-disable` comments to pass.
|
|
16
|
+
3. **Targeted tests** — run only tests covering the files you changed first;
|
|
17
|
+
the full suite only if the change is cross-cutting.
|
|
18
|
+
|
|
19
|
+
## 2. Duplicate-code check
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
node scripts/anti-slop/check-duplicates.mjs
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
It tokenizes the source tree and fails when copy-paste duplication exceeds the
|
|
26
|
+
threshold (defaults tuned for typical repos; adjust in the target repo if it
|
|
27
|
+
has legacy duplication). If it flags your new code: extract into the existing
|
|
28
|
+
shared module (see reuse-before-create) rather than adjusting the threshold.
|
|
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.
|
|
32
|
+
|
|
33
|
+
## 3. Modularity check
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
node scripts/anti-slop/check-modularity.mjs
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Flags files over ~300 lines, functions over ~80, and classes with > ~10
|
|
40
|
+
public methods. The files you touched must pass. If the repo has pre-existing
|
|
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.
|
|
43
|
+
|
|
44
|
+
## 4. Import-boundary check
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
node scripts/anti-slop/check-imports.mjs
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Fails on circular dependencies and on any `imports.deny` layer rules the repo
|
|
51
|
+
has declared. A cycle means two responsibilities are fused — extract the shared
|
|
52
|
+
part into a module both can import; never fix a cycle with a re-export shim.
|
|
53
|
+
|
|
54
|
+
## 5. Documentation of shared exports
|
|
55
|
+
|
|
56
|
+
Every shared component, hook, util, service, or type you added or changed must
|
|
57
|
+
carry a one-line JSDoc above the export:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
/** Renders the standard 2-column page shell; use for any new listing/settings page. */
|
|
61
|
+
export function TwoColumnLayout({ sidebar, children }: TwoColumnLayoutProps) {
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
This is mechanically enforced: `node scripts/anti-slop/check-docs.mjs` fails on
|
|
65
|
+
undocumented exports (the pre-commit hook runs it on staged files). Rerun
|
|
66
|
+
`node scripts/anti-slop/generate-component-index.mjs` after adding or removing
|
|
67
|
+
shared exports, and confirm the new entries show their description (not `-`) in
|
|
68
|
+
`docs/COMPONENT_INDEX.md`.
|
|
69
|
+
|
|
70
|
+
## 6. If the task was a refactor, review what you may have missed
|
|
71
|
+
|
|
72
|
+
Gates catch duplication that exists; they cannot catch load-bearing logic left
|
|
73
|
+
in the wrong layer or a skeleton built twice with different fillings. When the
|
|
74
|
+
task extracted components, merged duplicated flows, or moved rules between
|
|
75
|
+
files, run the `abstraction-review` skill before reporting, and include its
|
|
76
|
+
"Second-order opportunities" list in the report.
|
|
77
|
+
|
|
78
|
+
## 7. Report
|
|
79
|
+
|
|
80
|
+
End the task with a short summary: checks run, results, files touched, and any
|
|
81
|
+
pre-existing issues you noticed but did not fix (report, don't drive-by-fix).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hyperbolictonic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|