effect-model-audit
Skill2 starsChanged 27 days agoApache-2.0View on GitHub
Use when asked to audit this codebase's type modelling — whether the domain types are honest, whether they can represent illegal states, or for a periodic model-level type review. Triggers on "audit our type modelling", "are our types honest", "can our types represent illegal states", "model-level type review", "audit our domain types". The JUDGMENT sibling to effect-native-audit — a workflow-driven reasoning sweep over the port/domain type surface, not the anchor-driven regression net and not the @effect/language-service linter.
1 other project uses a version of this, 1 of them edited.
Adopted by
Most recently taken up by
- CodeForBreakfast/beady-eyeedited, first seen Oct 13, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 1
- CodeForBreakfast/beady-eye★ 2720% same
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository4 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- effect-native-auditSkill
- renovateSkill
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 2 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 95
HARD INVARIANT — a verified partition: Every exported domain type lands in exactly one family.
- Distinctive: line 183
Do not point at the report.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Effect-model type audit
Sweep the port/domain type surface for model-level dishonesty — types whose shape lets a caller construct, or forces a caller to fake, a value the domain does not mean. This is not "did we hand-roll a helper" (that's the sibling) and not "is this line idiomatic" (that's the LSP). It is: does the type's value space match the domain's, and do all its producers and consumers mean the same thing by it?
Four orthogonal value-space lenses, each run against each type-family:
- L1 Illegal-representable — can the type represent a value that is invalid in the domain? A combination of fields, an optional that should be required, a bare primitive admitting an out-of-domain string, a boolean pair whose illegal corner is representable. The general form of "flags-and-optionals for state" and "stringly-typed value", unbound from any grep costume — reason about the value space, don't match a token. Invalid state must be unrepresentable.
- L2 Under-expressive — the dual: is there a legal domain state the type cannot represent, forcing a sentinel, a "transparent placeholder", an impossible branch, or a comment apologising for a field that is "always absent here"? An under-expressive type pushes the missing case into convention or into a lie.
- L3 Role coherence — from the producer/consumer census: do all
construction sites and all read sites mean the same thing by this type, or is
it two roles in a trenchcoat — an address (what a caller supplies to target
something) vs an observation (what the substrate hands back)? The tell is a
producer that can legally build a value that is illegal for some consumer's
role. This is the reasoning that isolation skips — and exactly what let the
comms-ro14
ChannelRef/ThreadRef/MessageRefconflation through: every field was honest alone, the illegality was relational across the census. - L4 Boundary honesty — at a substrate/trust boundary, does the type's role
demand a parsed-once strong representation (a
Schema.Structwith required fields, branded members, a decoded-once refinement) that it does not have — a loose interface the boundary must defend by convention? Parse at boundaries, trust inside.
Why this is the complement the sibling can't reach
effect-native-audit is a frequent, cheap, anchored regression net: every one
of its ~41 finders is anchor-driven — it starts from a grep: pattern and
confirms a shape at the hit. That is exactly what makes it cheap to re-run and
deterministic to reason about, and it is also a structural blind spot: a
finder can only find a defect that has a lexical anchor. Role-conflation has
none. The comms-ro14 address/observation defect — a ThreadRef whose optional
narrow was honest for the address role but illegal for the observation role —
was not caught by any effect-native-audit sweep. It was noticed by hand while
building resolve_thread, because you cannot grep for a concept: every field
was honest in isolation; the illegality was relational.
This skill is the periodic, expensive, judgment sweep that reaches those
defects. It keeps the catalogue's discipline that matters — coverage by
decomposition, one bounded agent per enumerated cell, deterministic coverage, no
agent cherry-picking — but changes the cell from pattern to unit × lens.
The agent holds one unit (a type-family plus its full producer/consumer
census) and one lens (a value-space question) and reasons; it does not
scan for a token.
Three coverage layers, three jobs — never re-report across them:
| anchor | cost | catches | |
@effect/language-service | per-line rule (~60), tsc-time | free, every bun run check | floating Effects, unnecessary Effect.gen, global fetch/Date |
effect-native-audit | a grep: pattern per finder | cheap, frequent | hand-rolled helpers, imperative spines, present-but-anchorable representation smells |
effect-model-audit (this) | a type-family + census per finder, reasoned | expensive, periodic | relational / model-level dishonesty with no lexical anchor |
L4 deliberately borders the sibling's unvalidated-boundary (a missing
decode at a trust edge) and internal-bridge (a decodeUnknownSync bridge that
exists at a seam). L4 is model-level: the type's role demands a strong
parsed representation, independent of whether any decode call exists. If a finding
is really "a decode call is missing here" or "there is a *Sync bridge here",
that is the sibling's territory — file it there, not as L4. The lens carries an
explicit boundary statement (see domains.md) so the axes don't double-count.
How it works
A Workflow script (effect-model-audit.workflow.js) runs a four-phase pipeline.
The lenses are grounded in modelling principles first — the local
strengthening-types skill (~/.claude/skills/strengthening-types), DDD
make-illegal-states-unrepresentable, and Effect's data-types/* + code-style/*
design docs as the vocabulary for the honest representation (not an export
inventory to match against).
- Discover — a cheap pre-pass enumerates the raw export surface
(
grep '^export'overpackages/core/ports.ts) and groups every exported domain type into type-families, each bundled with every construction site (producers) and every read site (consumers) acrosspackages/. The census is the heart of the pass — reasoning about a type without its usage is the isolation that blinds the anchored audit, so it is what makes cross-type role-conflation visible.- HARD INVARIANT — a verified partition. Every exported domain type lands
in exactly one family. This is checked as a coverage equation
(
assertPartition) against the raw export list — missing (an export in no unit), duplicated (in >1), or unexpected (a member that isn't an export) all fail. A deterministic backstop sweeps any un-grouped export into an explicitunassignedunit so nothing escapes audit. Coverage is an equation, not a trusted list — this is how the no-cherry-picking bar is met at the unit level.
- HARD INVARIANT — a verified partition. Every exported domain type lands
in exactly one family. This is checked as a coverage equation
(
- Reason — one bounded agent per unit × lens cell. The agent holds one family + its census and one lens, reads the actual type definitions and census sites, and reasons about the value space. Empty findings is a valid, honest result — a well-modelled family should return none.
- Verify — per-finding, no barrier: each finding flows into one adversarial refuter the moment its finder returns. Verify is default-surfaced and evidence-gated — consistent with a modelling audit, a surfaced false positive is cut cheaply at the gate, but a refuted true smell is invisible. The refuter refutes any finding not grounded in concrete evidence: a named illegal value (L1), a specific missing legal state + the workaround it forces (L2), a specific disagreeing producer/consumer pair (L3), or the specific boundary (L4). "I'd model this differently" with no named illegal value is taste-noise, cut. "Is the rewrite worth the blast radius" is never a refutation ground — it is a gate concern. A verifier error keeps the finding (surfaced, unverified), never silently drops it.
- Synthesise — a synthesis agent writes the report prose, deduping
cross-lens sightings (L1 and L3 overlap by design — the same relational
defect reached by the isolation path and the census path; they dedup by
file:line). The confirmed / low-confidence issue partition is not trusted to that agent — the workflow derives it deterministically from each survivor's own confidence (splitSurvivors: dedup byfile:line, highest confidence wins, then split).
Each surviving finding carries concrete evidence (the illegal value / the disagreeing pair / the boundary), a proposed remodelling, and its blastRadius — every producer + consumer a remodelling ripples to. The census already computed the blast radius; here the ripple is the whole point.
Running it
- Get today's date (
date +%F). Launch the workflow, passing the date so the report file is named:
Workflow({ scriptPath: '.claude/skills/effect-model-audit/effect-model-audit.workflow.js', args: { date: '<YYYY-MM-DD>' }, })It runs in the background (a discovery pass, then one agent per
unit × lenscell — ~10 concurrent — then per-finding verify + synth); you are notified on completion. Watch live with/workflows. To run a single lens (cheaper re-run after editing its prompt, or to resume just one), passargs: { date, lenses: ['L3'] }.- The workflow writes the report to
docs/effect-model-audit-<date>.mdand returns{ confirmed[], lowConfidence[], survivors[], refutedCount, coverage, partitionOk }.
After it returns — gate, then file
The workflow returns findings and does not file them. Model findings are architectural — a sweep can surface a handful of type remodellings, and filing them unreviewed clogs the backlog with work that hasn't been arbitrated.
The report is working scaffolding for the review, not a permanent record. File
what is worth keeping, then delete docs/effect-model-audit-<date>.md.
- Present the
confirmed[]list to the maintainer —file:line, unit, lens, the evidence (the named illegal value / disagreeing producer-consumer pair / boundary), the proposed remodelling, and the blastRadius. Also surface thelowConfidence[]items so any worth keeping can be promoted; un-promoted ones are dropped with the report. - Wait for go-ahead — the gate leans harder on the maintainer here than the sibling's. A model remodelling is a taste-and-cost call, not a mechanical helper swap: the finding says a real illegal state or role-conflation exists, but whether to split a type, brand a scalar, or absorb the blast radius is a judgment about the whole design. The maintainer may cut, downgrade, or reshape a confirmed item, or promote a low-confidence one, before anything is filed. "Is the remodelling worth the ripple" is decided here, deliberately — the verify pass was forbidden from touching it.
File each approved finding as its own issue, grouped under one tracking issue. Each description must be self-contained — it has to stand on its own once the report is gone. Two fields are non-negotiable:
- the evidence — the concrete illegal value, missing state, or disagreeing producer/consumer pair (with both sites) that proves the smell is real, and
- the blastRadius — every producer + consumer the remodelling ripples to. A type change is never local; that ripple is the real cost and the issue is unfileable without it.
Do not point at the report; it won't survive.
Each filed item is its own remodelling; implement them separately with TDD, not as a batch — a type change ripples through its whole census.
Notes
- Modelling principles ARE the ground, not an export inventory. Unlike the
sibling's substitution axis (which needs the complete
Array/Option/… export surface in front of the finder), these lenses are grounded in why a representation is honest: the localstrengthening-typesskill, DDD make-illegal-states-unrepresentable, and Effect'sdata-types/*+code-style/*design docs as the vocabulary for the fix. Seedomains.md. - Local clones, GitHub fallback.
strengthening-typesat~/.claude/skills/strengthening-types/SKILL.md; Effect docs at~/Development/references/effect-website(the URL…/docs/<slug>/maps to…/content/src/content/docs/docs/<slug>.mdx). Both are workstation-local; a cloud/CI agent falls back toWebFetchon effect.website, just slower. - Scope v1 is the port/domain surface.
packages/core/ports.ts(the branded schemas, the address/observation refs, the port interfaces, the tagged errors), with the producer/consumer census spanning every construction + read site acrosspackages/(adapters construct refs; application/mcp read them). Not every exported type in every package — that's where model-honesty defects bite and it keeps v1 bounded. Widen later if it earns its keep.*.test.tsandnode_modulesare out of scope. - The authoritative lens catalogue is the
LENSESarray ineffect-model-audit.workflow.js(the workflow can't read files, so it drives the finders from that array directly).domains.mdmirrors it for humans — keep them in sync. - No auto-fix. Findings become tracked, arbitrated work, not an automated diff.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

