effect-deep-audit
Skill8 starsChanged 9 months agoMITView on GitHub
Deep audit of an Effect-TS codebase against actual Effect core team patterns. Produces a tiered finding list + executable DAG plan, then systematically rewrites non-idiomatic code. USE THIS SKILL WHEN: 'audit this effect code', 'is this idiomatic effect', 'deep audit', starting a new Effect project and want to verify patterns, refactoring an Effect codebase toward library-grade quality, reviewing code before it ships to production. NOT FOR: Greenfield Effect projects (use effectts skill).
- Reads credentials
In this repository3 other files it gives its agents.
- effect-deslopify-idiomaticSkill
- effecttsSkill
- sddSkill
The file
We split this skill into the rules it gives an agent
10 rules. About 0 are common, 3 are distinctive, and 7 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 43
If you have effect-mcp configured, also use effect_docs_search({ query: "..." }) for API concepts.
- Distinctive: line 45
Check what your framework already provides: Before writing custom code, grep the framework source.
- Very specific: line 119
Keep tests green: Run tsc --noEmit + vitest run after EVERY structural change.
- Very specific: line 123
Delete aggressively: No backwards compat shims.
- Distinctive: line 130
Every API response schema derives from a Drizzle table:
- Very specific: line 143
Critical: Use Schema.DateTimeUtc (Encoded=string), NOT Schema.DateTimeUtcFromDate (Encoded=Date).
- Distinctive: line 159
Not two middlewares where the second re-resolves what the first already resolved.
- Very specific: line 176
Use Effect.fn("Service.method") for service method definitions (preferred — combines function definition + automatic span)
- Very specific: line 177
Use Effect.withSpan only when wrapping existing effects in pipe chains
- Very specific: line 178
Add Effect.annotateLogs("service", "ServiceName") to every service module
≈ “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 Deep Audit
Systematic methodology for bringing an Effect codebase to library-grade quality, battle-tested across full-stack audits covering auth, middleware, schemas, repos, event handlers, API groups, and tests.
Prerequisites (Optional)
- Effect source (recommended):
git clone https://github.com/Effect-TS/effectfor pattern verification - Effect MCP: https://github.com/tim-smart/effect-mcp for API lookups (fallback: https://effect.website/docs)
The Process
Phase 1: Reconnaissance (DO THIS FIRST)
Verify against Effect source, not docs. Docs lag. Source is truth.
# Clone Effect source for pattern verification (if not already done)
# git clone https://github.com/Effect-TS/effect <effect-repo>
# Service patterns
rg "class .* extends Context\.Tag" <effect-repo>/packages/platform/src
rg "Effect\.Service" <effect-repo>/packages/cluster/src
# Error patterns
rg "Schema\.TaggedError" <effect-repo>/packages/cluster/src
# Layer composition
rg "Layer\.provideMerge" <effect-repo>/packages/platform/src
# HttpApi patterns
rg "HttpApiBuilder\.group" <effect-repo>/packages/platform/src
rg "HttpApiBuilder\.middleware" <effect-repo>/packages/platform/srcIf you have effect-mcp configured, also use effect_docs_search({ query: "..." }) for API concepts.
Check what your framework already provides. Before writing custom code, grep the framework source. This is consistently the highest-leverage finding in audits — frameworks often provide operations that hundreds of lines of custom code reimplement.
# Example: check what capabilities your framework's plugins provide
rg "api\." <framework-source>/src/plugins/<plugin-name>/Phase 2: Tiered Finding Classification
Audit every file in the target. Classify each finding:
| Tier | Tag | What | Priority |
| 0 | BUG | Code that doesn't execute or produces wrong results | Fix immediately |
| 1 | PERF | Unnecessary work (double resolution, N+1, redundant queries) | Fix in same pass |
| 2 | SCHEMA | Manual mappers, hand-written schemas that mirror tables, as casts | High — schema drift is a bug factory |
| 3 | ARCH | Monolith files, duplicated helpers, wrong abstraction boundaries | Medium — affects velocity |
| 4 | IDIOM | Non-idiomatic but correct (DateTime.unsafeNow, missing spans, process.env) | Low — clean up after structural work |
| 5 | FINE | Confirmed correct. Document why to prevent future "fixes" | N/A |
Specific patterns to grep for:
# Tier 0: Bugs
rg "Effect\.log(Error|Warning|Info)\(" --type ts # statement expressions (Effect not chained)
rg "as any" --type ts # type escapes hiding real errors
# Tier 2: Schema anti-patterns
rg "toModel|toXxx|toPublic" --type ts # manual row mappers
rg "S\.Struct\({" --type ts # hand-written schemas (should be createSelectSchema)
rg "satisfies.*Schema" --type ts # schema-adjacent type assertions
# Tier 2: Error modeling
rg "Schema\.TaggedError" --type ts # check error patterns are correct
# Tier 3: Architecture
wc -l src/**/*.ts | sort -n | tail -10 # monolith files (>200 LOC)
rg "catchTag|catchTags|mapError" --type ts -c # duplicated error handling
# Tier 4: Idioms
rg "DateTime\.unsafeNow" --type ts # should be DateTime.now (effectful)
rg "process\.env" --type ts # should use Config/Layer
rg "Effect\.sync.*unsafe" --type ts # unnecessary sync wrapper
rg "withSpan" --type ts -c # span coverage (should be everywhere)Phase 3: Plan as DAG
Structure the plan as a dependency graph. Key insight: type errors cascade. Changing a schema breaks every consumer. Changing a service interface breaks every implementation. Work bottom-up:
Layer 0: Foundation (schemas, table defs, branded types)
Layer 1: Service interfaces + middleware
Layer 2: Implementations (repos, event handlers)
Layer 3: API handlers (group files)
Layer 4: Tests
Layer 5: Cleanup (spans, idioms, dead code)Each layer gates on: 0 type errors + all tests passing. Do NOT proceed to the next layer with errors. The type checker is your friend — it tells you every callsite that needs updating.
Phase 4: Execution
Parallelize mechanical work. Schema renames, import fixes, field renames across 20 files — these are perfect for parallel agents. Save judgment-heavy work for files that require it (handler rewrites, middleware logic, layer composition).
Keep tests green. Run tsc --noEmit + vitest run after EVERY structural
change. If you have 14 errors, fix all 14 before moving on. Compounding errors
across layers is how 3-hour debugging sessions happen.
Delete aggressively. No backwards compat shims. No renamed _unused vars. No
// removed comments. If code is dead, rm it. Git has history.
Key Patterns Discovered
createSelectSchema = sole schema source of truth
Every API response schema derives from a Drizzle table:
import { createSelectSchema } from 'drizzle-orm/effect-schema'
const OwnedAssetSelect = createSelectSchema(ownedAssets, {
id: OwnedAssetId, // branded type override
ownerUserId: UserId, // branded type override
kind: OwnedAssetKind, // literal union override
createdAt: Schema.DateTimeUtc, // NOT DateTimeUtcFromDate (breaks HTTP JSON)
}).omit('metadata', 'sourceEventId')Critical: Use Schema.DateTimeUtc (Encoded=string), NOT Schema.DateTimeUtcFromDate
(Encoded=Date). DateTimeUtcFromDate breaks HTTP JSON round-trips because the encoded
form is a Date object, not a string. This bug was found in multiple domain packages
during audits.
Nullable columns with overrides need S.NullOr() wrapping — the override replaces
the full column schema including auto-nullability.
Handlers decode inline — no mapper functions:
return yield* S.decodeUnknown(MySchema)(row).pipe(Effect.orDie)Single middleware pattern
One middleware that: resolves session, checks authorization, provides context. Not two middlewares where the second re-resolves what the first already resolved.
Effect.logError is an Effect, not a statement
// BUG: creates Effect value, discards it
Effect.logError('something broke', { error })
return Effect.succeed(fallbackResponse)
// FIX: chain it
Effect.logError('something broke', { error }).pipe(
Effect.as(fallbackResponse)
)Tracing
- Use
Effect.fn("Service.method")for service method definitions (preferred — combines function definition + automatic span) - Use
Effect.withSpanonly when wrapping existing effects in pipe chains - Add
Effect.annotateLogs("service", "ServiceName")to every service module
// Preferred: Effect.fn for service methods
findById: Effect.fn("UserRepository.findById")(function*(userId) {
const rows = yield* sql`SELECT * FROM users WHERE id = ${userId}`
if (rows.length === 0) return yield* new UserNotFound({ userId })
return yield* Schema.decodeUnknown(User)(rows[0])
}),
// Fallback: withSpan for pipe chains
listByOwner: (userId, limit = 100) =>
db.select().from(table).where(...).pipe(
Effect.mapError(toRepositoryQueryError(REPO, 'listByOwner')),
Effect.flatMap(Effect.forEach((row) => decode(row).pipe(Effect.orDie))),
Effect.withSpan('OwnedAssetRepository.listByOwner'),
),Monolith handler files -> one file per API group
A 500-line handler file with all routes is unnavigable. Split into:
groups/HealthGroupLive.tsgroups/AdminGroupLive.tsgroups/UserManagementGroupLive.ts- etc.
The main handler file becomes pure layer wiring (~30-50 lines).
Shared error helpers go in a dedicated module, not duplicated across group files.
DateTime.now vs DateTime.unsafeNow
DateTime.unsafeNow() is sync. DateTime.now is effectful. In Effect.gen
contexts, always use yield* DateTime.now. The "unsafe" prefix exists for
a reason — it bypasses the Effect runtime's clock, which matters for testing
and time-travel debugging.
Checklist (run after every audit)
[ ] Every API response schema uses createSelectSchema, not manual S.Struct
[ ] Every DateTimeUtc column uses S.DateTimeUtc (not DateTimeUtcFromDate)
[ ] Every nullable column with a branded override uses S.NullOr()
[ ] No toXxx mapper functions — handlers decode inline with S.decodeUnknown
[ ] No as any / as unknown type escapes in non-test code
[ ] No Effect.logError/logWarning as statement expressions
[ ] No process.env — all config through Effect Config/Layer
[ ] No DateTime.unsafeNow in Effect.gen contexts
[ ] Every service method uses Effect.fn (or Effect.withSpan for pipe chains)
[ ] Every service module has Effect.annotateLogs
[ ] No handler files > 200 LOC — split into per-group files
[ ] No duplicated error mapping helpers — extract to shared module
[ ] Tests split into per-group files with shared test infrastructure
[ ] Framework capabilities audited before writing custom code
[ ] Dead code deleted (not commented out, not renamed with _prefix)References
| Reference | Content |
effectts skill | Idiomatic Effect patterns (services, layers, errors) |
| Effect source | Grep here for real patterns, not docs — git clone https://github.com/Effect-TS/effect |
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.

