modules/programs/agents/shared/skills/effect-deep-audit/SKILL.md
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 effect-best-practices skill).
npx skillsauth add MichaelVessia/nixos-config effect-deep-auditInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
git clone https://github.com/Effect-TS/effect for pattern verificationVerify 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/src
If 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>/
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 1: Sync I/O violations (breaks testability, blocks event loop)
rg "from 'node:fs'" --type ts # sync fs imports (should use FileSystem.FileSystem)
rg "from 'node:child_process'" --type ts # sync exec imports (should use shell service or Command)
rg "existsSync|readFileSync|writeFileSync|mkdirSync|readdirSync|statSync|symlinkSync" --type ts
rg "execSync" --type ts # blocks event loop, not mockable
# 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
rg "catchAll\(\(\) =>" --type ts # blanket error suppression (should be catchTag or selective)
rg "Effect\.catch\(\(\)" --type ts # same pattern, different spelling
# 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)
rg "const [A-Z_]+ = ['\"]" src/ --type ts # hardcoded string constants (candidates for Config)
rg "const [A-Z_]+ = \d+" src/ --type ts # hardcoded number constants (candidates for Config)
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.
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.
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)
One middleware that: resolves session, checks authorization, provides context. Not two middlewares where the second re-resolves what the first already resolved.
// 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)
)
Effect.fn("Service.method") for service method definitions (preferred — combines function definition + automatic span)Effect.withSpan only when wrapping existing effects in pipe chainsEffect.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'),
),
A 500-line handler file with all routes is unnavigable. Split into:
groups/HealthGroupLive.tsgroups/AdminGroupLive.tsgroups/UserManagementGroupLive.tsThe main handler file becomes pure layer wiring (~30-50 lines).
Shared error helpers go in a dedicated module, not duplicated across group files.
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.
[ ] 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)
[ ] No sync Node.js I/O — use FileSystem.FileSystem, not readFileSync/existsSync/etc.
[ ] No execSync — use Effect-tracked shell service or platform Command
[ ] No blanket catchAll error suppression — use catchTag or inspect error content
[ ] Tunable constants use Config with withDefault, not module-level consts
[ ] Test layers use FileSystem.layerNoop for I/O mocking, not real filesystem
| Reference | Content |
|-----------|---------|
| effect-best-practices skill | Idiomatic Effect patterns (services, layers, errors) |
| Effect source | Grep here for real patterns, not docs — git clone https://github.com/Effect-TS/effect |
development
Generate self-contained HTML visualizations with Plannotator theming. Use for implementation plans, PR explainers, architecture diagrams, data tables, slide decks, and any visual explanation of technical concepts. Plans and PR explainers follow Plannotator's prescriptive approach; all other visual content delegates to nicobailon/visual-explainer.
development
Turn an idea or objective into a goal package for /goal. Interviews the user, builds a reviewed fact sheet via Plannotator, then explores the codebase to produce an execution plan.
development
Open Plannotator's browser-based code review UI for the current worktree or a pull request URL, then act on the feedback that comes back.
testing
Open Plannotator on the latest rendered assistant message and use the returned annotations to revise that message or continue.