Effect Native Audit
ASecurityUse when asked to audit the Claude Code plugin under plugin/ for places it hand-rolled what Effect ships natively, or to run the Effect-native sweep. Triggers on "effect-native audit", "where are we reinventing Effect", "rolled our own instead of an Effect builtin", "sweep for native Effect helpers", "audit our FP/Effect modelling", "audit our concurrency/error handling". A workflow-driven substitution, structural, modelling and behaviour audit — not a substitute for the Effect language servi...
- 15 stars
- 0 votes
- 0 copies
- 3 views
- Added October 9, 2026
Works with
Security analysis
100/100Pro scans all 3 files and shows the line behind each finding
npx -y skills add CodeForBreakfast/beady-eye --skill effect-native-audit --agent claude-codeAre you the author of Effect Native Audit?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/codeforbreakfast-effect-native-audit-beady-eye)---
name: effect-native-audit
description: Use when asked to audit the Claude Code plugin under plugin/ for places it hand-rolled what Effect ships natively, or to run the Effect-native sweep. Triggers on "effect-native audit", "where are we reinventing Effect", "rolled our own instead of an Effect builtin", "sweep for native Effect helpers", "audit our FP/Effect modelling", "audit our concurrency/error handling". A workflow-driven substitution, structural, modelling and behaviour audit — not a substitute for the Effect language service or the plugin's Biome rules.
---
# Effect-native capability audit
Sweep the plugin's TypeScript on **four axes** for code that isn't Effect-native.
Three axes are **static** and find non-native code that is *present*. One is
**dynamic** and finds native code that is *misused*.
- **Substitution** (verbs) — logic that re-implements what Effect already ships:
a retry loop that is `Schedule`, manual env parsing that is `Config`, custom
equality that is `Equal`/`Data`, a list helper already in `Array`. *Where did
we hand-roll a helper?*
- **Structural** (wiring) — code whose construction, dependency-injection, error
or resource **spine** is still imperative OOP even though the leaves return
Effect: a `new`-able class doing DI, a `throw` inside an Effect-returning
function, a dependency threaded as a config field instead of declared in `R`,
a `try/finally` that wants `acquireRelease`. *Effect used inside functions ≠ an
Effect-native program.*
- **Modelling** (nouns) — data and state whose **type** throws away a guarantee
an Effect data type would give for free: `T | null` that wants `Option`, a
sentinel or pure `throw` that wants `Result`, booleans + optional fields that
want a `Data.taggedEnum`, a bare `string` id that wants a branded type,
in-place array mutation that wants an immutable build, hand-rolled equality
that wants `Equal.equals`, a data-first helper that blocks clean `pipe`
composition. The dishonesty is in the *representation*, so the substitution
and structural axes walk past it.
- **Behaviour** (dynamics) — how effects **run**, **fail**, and what they
**trust**. Independent effects run sequentially that could run under
`Effect.all` concurrency, an unbounded fan-out onto a shared resource, an
`Effect.forkChild` whose failure nothing observes, untrusted data crossing the
edge via `as`/`JSON.parse` with no `Schema` decode, an `E` channel typed
`unknown`, or an `Effect.catch` that swallows a recoverable failure. The construct is already
Effect-native and the smell is the *policy*. Each finder anchors on present
code, not blanket absence ("no timeout anywhere").
The plugin has two per-line linters, and this sweep covers what they cannot see.
`nix flake check` typechecks `plugin/` with Effect's tsgo build of `tsc`, whose
language service fails the check on floating Effects, an unnecessary
`Effect.gen`, a `*Sync` decode or a `try/catch` inside a generator, the global
`Error` in `E`, most one-combinator swaps, and any Effect 3 API name. The Biome GritQL plugin
`plugin/biome-plugins/effect-native-predicates.grit` lints the mechanical
substitutions precise enough to flag deterministically, and leaves the
judgment-heavy axes to this sweep. Neither sees that a 40-line block is a
combinator that exists, that a module is shaped like an OOP service, that a type
lets the caller forget the absent case, or that a fan-out is unbounded. The
finders do not re-report what either linter catches. `domains.md` lists both
sets, and the global-API rules this repo leaves off, which the sweep does report.
## How it works
A `Workflow` script (`effect-native-audit.workflow.js`) fans out four kinds of
finder (see `domains.md`), grounded in the Effect 4 the plugin pins:
- **Source** comes from `plugin/node_modules`, the packages `bun install` puts
there at the versions `plugin/package.json` pins. Each ships its TypeScript
under `src/`.
- **The idiom guide** ships in the same package: `effect/AGENTS.md`, which is
Effect-TS/effect's `LLMS.md`, and the worked examples under `effect/ai-docs/`.
Effect-TS/effect's `migration/` notes, read at the tag for the pinned
version, say how Effect 4 changed an idiom.
- **Docs** come from Effect 4's pages, which effect.website serves under
`/docs/v4/` and `Effect-TS/website` keeps on `main` under
`apps/web/src/content/docs/v4/`. The unprefixed effect.website pages are
Effect 3's.
**Substitution finders** (~23, one per Effect domain) work inventory-first:
1. List the module's *complete* export inventory from source, including the
names Effect re-exports inside `export { … }` blocks. You cannot recognise a
copy of `Array.partition` unless the whole `Array` surface is in your head.
2. Read the module's docs **section** for the *when & why*: the whole section,
not just the intro page (Stream is six pages). API-reference-only modules
(`Array`, `Record`, …) have no prose page and lean on source.
3. Scan the plugin for logic that re-expresses a helper in that inventory.
**Structural finders** (~6, one per smell in the anti-pattern catalogue) work
smell-first. They read the Effect *design* docs (`services`, `expected-errors`,
`scope`, `running-effects`, `the-effect-type`) for why the native shape wins and
when the imperative shape is justified. Then they grep an anchor pattern and read
each candidate to confirm the shape. A grep hit is not a finding.
**Modelling finders** (~7, one per representation smell) work smell-first too,
grounded in the `code-style/*` + `data-types/*` docs (`option`, `result`,
`data`, `branded-types`, `chunk`, `trait/equal`, `dual`). They hunt the *type* of
data and state. Three of them deliberately border a structural or substitution
smell, and each finder states its boundary so the axes don't double-count.
**Behaviour finders** (~5, one per dynamic smell) work smell-first, grounded in
the `concurrency/*`, `error-management/*` and `schema/*` docs. They hunt how
effects **run** (sequential-not-concurrent, unbounded-fanout), **fail**
(untyped-error-channel), and what they **trust** (unvalidated-boundary,
unsupervised-fork). Each anchors on a *present* construct and confirms a
concrete misuse. For concurrency, the data dependency between effects is the
whole question.
Findings then flow, per finding and with no barrier, into an **adversarial
verify** pass with one refuter per finding. The axes take **opposite default
verdicts**, because their failure modes are opposite:
- **Substitution** — the refuter tries to *disprove the swap* (a false twin) and
**defaults to refuted** when unsure. A plausible-but-wrong helper swap is the
risk, so the bar is "positively confirm the swap preserves behaviour."
- **Structural, modelling & behaviour** — the refuter asks only *is this a false
positive?* and **defaults to surfaced** when unsure. It refutes only a misread
or a genuinely justified choice. Whether the rewrite is worth its blast radius
is not a refutation ground, because a surfaced false positive is cheap to drop
while a refuted true smell is invisible. Two refuters carry an extra guard:
`data-first-helper` kills a data-last rewrite that would be gratuitous
point-free, and `sequential-not-concurrent` refutes when the effects are
genuinely data-dependent.
A synthesis agent writes the report, deduping cross-axis sightings as it writes.
The confirmed / low-confidence partition is NOT trusted to that agent. The
workflow derives it deterministically from each survivor's own confidence: dedup
findings that propose the same remedy at one `file:line` on any axis, highest
confidence wins and `foundBy` names every finder, then split. The match is on
the remedy's exact wording, so the returned lists never drop a finding but can
hold two wordings of one fix that the report merges. A finding whose refuter
died is returned as unverified, never as refuted.
## Running it
1. Install the plugin's packages, so the finders read the pinned Effect source:
```bash
(cd plugin && bun install --frozen-lockfile)
```
2. Optionally, check out the Effect 4 docs once, so ~41 finders read files
instead of fetching pages from GitHub:
```bash
git clone --depth 1 https://github.com/Effect-TS/website <dir>
```
3. Get today's date (`date +%F`) and launch the workflow:
```
Workflow({
scriptPath: '.claude/skills/effect-native-audit/effect-native-audit.workflow.js',
args: { date: '<YYYY-MM-DD>', docsDir: '<dir>/apps/web/src/content/docs/v4' },
})
```
Leave out `docsDir` to read the docs from GitHub. The workflow runs
in the background: ~41 finders (23 substitution, 6 structural, 7 modelling,
5 behaviour), then a refuter per finding, then synthesis. Watch it with
`/workflows`. To run one axis, pass `axes: ['behaviour']`. `reportPath`
overrides where the report goes.
4. The workflow writes the report to `docs/effect-native-audit-<date>.md` and
returns `{ confirmed[], lowConfidence[], unverified[], refutedCount, coverage }`.
`coverage` names any finder that failed, any group with unverified findings,
and any that fell back to fetching. Re-run an incomplete group on resume.
## What comes back
The workflow returns findings and files nothing. The report is scratch for
reviewing them, not a permanent record, so it is not committed.
A finding taken into the tracker has to stand on its own once the report is
gone. Carry its `file:line`, the rewrite, the why, its `sourceRef` and its
`docsRef`. Structural, modelling and behaviour findings also carry their
`blastRadius`: the call sites a rewrite ripples to, since a representation change
moves every producer and consumer of the value. Each finding is its own change,
made separately and test-first, not as a batch.
A substitution that recurs and is mechanical enough to flag deterministically
belongs as a rule in the grit file, so the linter catches the next one.
## Refreshing it
Source paths and docs slugs drift with Effect releases, and the language-service
rules that are on drift with effect-tsgo releases. `domains.md` carries the
checklist. The authoritative sets are the `DOMAINS`, `STRUCTURAL_SMELLS`,
`MODELLING_SMELLS` and `BEHAVIOUR_SMELLS` arrays and `LINT_OWNED` in the
workflow script, and `domains.md` mirrors them for humans.
Files in this skill
- SKILL.md
- domains.md
- effect-native-audit.workflow.js
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.