effect-first-development
Skill78 starsChanged 6 days agoApache-2.0View on GitHub
Canonical Effect-first development guide for this repo. Trigger on: new features, refactors, bug fixes, API boundaries, typed errors, Option usage, schema decoding, service wiring, and test authoring.
- Reads credentials
In this repository38 other files it gives its agents.
- AGENTS.mdAGENTS.md
- explorations/AGENTS.mdAGENTS.md
- adhdSkill
- atom-reactivity-specialistSkill
- browser-qa-loopSkill
- closeoutSkill
- codex-findingsSkill
- crispenSkill
- exploreSkill
- grillingSkill
- grill-meSkill
- grill-with-docsSkill
- jsdoc-annotation-specialistSkill
- motion-evidence-reviewSkill
- onepassword-secret-refsSkill
- ontology-architectSkill
- ontology-conceptualizerSkill
- ontology-curatorSkill
- ontology-foundational-auditorSkill
- ontology-mapperSkill
- ontology-requirementsSkill
- ontology-scoutSkill
- ontology-validatorSkill
- operator-handoffSkill
- orchestrateSkill
- plasma-sensitive-entrySkill
- portlessSkill
- qa-session-opsSkill
- quality-review-fix-loopSkill
- reflectSkill
- remote-development-agentsSkill
- schema-first-developmentSkill
- shadcnSkill
- sparql-expertSkill
- teachSkill
- turborepoSkill
- unslopSkill
- yeetSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
28 rules. About 0 are common, 3 are distinctive, and 25 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.
25
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 14
Use this skill when implementing or reviewing production code in this repository.
- Distinctive: line 15
If there is any conflict, repository laws win.
- Distinctive: line 19
Before writing code, walk the Non-Negotiable Laws below as a question checklist.
- Very specific: line 44
Import every Effect module from its dedicated path (effect/Effect, effect/Match, effect/String as Str, etc.).
- Very specific: line 46
No plain throw, new Error, or untyped error channels in production logic.
- Very specific: line 47
No nullish leak in domain logic.
- Very specific: line 48
No direct typeof checks when effect/Predicate covers the case.
- Very specific: line 49
No native Object/Map/Set/Date/String helpers in domain logic.
- Very specific: line 52
Do not finish with failing check, lint, test, or docgen.
- Very specific: line 53
Do not suffix schema constants with Schema.
- Very specific: line 55
Do not use native switch; use Match.
- Very specific: line 56
All new schemas must be meaningfully annotated: .pipe($I.annoteSchema("Name", { description })) for non-class schemas (LiteralKit …
- Very specific: line 58
If a schema has properties that are a union of literal strings, it should be a tagged union composed via LiteralKit, .mapMembers, and …
- Very specific: line 64
Never use JSON.parse / JSON.stringify in Effect-first code, tests, or fixtures.
- Very specific: line 67
Only runtime boundaries (app entrypoints/tests) may call Effect.runSync / Effect.runPromise / Effect.runFork.
- Very specific: line 78
Use Effect.fail for expected business errors and reserve Effect.die / Effect.orDie for invariants and impossible states.
- Very specific: line 79
When layer memoization sharing is unsafe, force isolation with Effect.provide(..., { local: true }) or Layer.fresh.
- Very specific: line 82
Prefer schema-level defaults (S.withConstructorDefault, S.withDecodingDefaultKey) instead of ad-hoc runtime fallback object literals.
- Very specific: line 86
Never use native Array.prototype.sort.
- Very specific: line 87
Avoid ad-hoc String(...) coercion in domain logic.
- Very specific: line 88
When branching on boolean values, prefer the flattest equivalent form first.
- Very specific: line 89
Before keeping O.match(...), check whether O.map(...), O.flatMap(...), O.liftPredicate(...), and O.getOrElse(...) express the same control …
- Very specific: line 91
Do not import node:path in production/tooling source.
- Very specific: line 92
Do not use native fetch in production/tooling source.
- Very specific: line 95
Use LiteralKit for named internal literal domains, especially when .Enum, .is, .match, .matchOrElse, .toTaggedUnion, or annotation-bearing …
- Very specific: line 96
Prefer P.isTagged("Tag") over manual _tag guard helpers built from P.hasProperty, P.isObject, or inline _tag string checks.
- Very specific: line 97
When a matcher is the function body or a reusable helper, prefer Match.type<T>().pipe(...) / Match.tags(...) over Match.value(...).
- Very specific: line 99
Prefer the tersest equivalent helper form when behavior is unchanged: direct helper refs over trivial wrapper lambdas, flow(...) for …
≈ “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-First Development (Canonical)
Use this skill when implementing or reviewing production code in this repository. If there is any conflict, repository laws win.
Zero-Fail Memory Rule
Before writing code, walk the Non-Negotiable Laws below as a question checklist. The fast triage questions:
- Can it fail? Typed tagged error in
Effect. Can it be missing?Option. - Is input external? Decode with
Schemaat the boundary; schema is the source of truth for data shapes. - Touching arrays/objects/strings/booleans or branching on shape? Effect modules (
A/R/Str/Bool),Predicate,Match,A.match— never native helpers orswitch. Prefer the flattest equivalent form first. Before I keep anO.match(...), canO.map/O.flatMap/O.liftPredicate/O.getOrElseexpress it more flatly? - Defining services, schemas, unions, or Effect-returning functions? Identity composer +
Context.Service/Layer, brandedS.Opaquefor data,LiteralKit/S.toTaggedUnion, namedEffect.fn. - At a runtime boundary?
Effect.run*only at entrypoints/tests,runMainowns process exit,Effect.tryPromisefor promises,Effect.scoped/acquireUseReleasefor resources. - Resilience/observability?
Effect.retry+Schedule,timeoutOption/timeoutOrElse, explicit concurrency, spans + structured logs from the start,Config/Redactedfor env + secrets. - Recovery? Precise
catchTag/catchFilterin domain,catchCause/matchCauseEffectat boundaries,Cause.prettyfor rendering,failfor expected vsdiefor invariants.
Additional checks with no law counterpart below:
- Designing a service or test helper? Keep
FileSystem,Path, andSqlClientinside the layer/service unless they are the explicit domain boundary. - Testing platform/runtime semantics? Prefer
@effect/vitestfor supporting tests, but spawn the real runtime when the assertion is about platform lifecycle behavior. - Testing assertions? Use public
@effect/vitest/utilshelpers for Option (assertSome/assertNone), Result (assertSuccess/assertFailure), and Exit (assertExitSuccess/assertExitFailure). Payload-checking helpers require the expected value;assertExitFailurerequires an expectedCause(for exampleCause.fail(expectedError)). Plain-valueexpectandassertremain legal insideit.effect; do not impose a blanket replacement. See testing patterns for complete examples and narrowing behavior.
Non-Negotiable Laws
- Canonical aliases are mandatory:
effect/ArrayasAeffect/OptionasOeffect/PredicateasPeffect/RecordasReffect/SchemaasS
- Import every Effect module from its dedicated path (
effect/Effect,effect/Match,effect/StringasStr, etc.). Importpipe,flow, andidentityfromeffect/Function. The rooteffectbarrel is not used, including in JSDoc and Markdown examples. - No
any, type assertions,@ts-ignore, or non-null assertions. - No plain
throw,new Error, or untyped error channels in production logic. - No nullish leak in domain logic; convert nullish to
Optionat boundaries. - No direct
typeofchecks wheneffect/Predicatecovers the case. - No native
Object/Map/Set/Date/Stringhelpers in domain logic. - For typed errors, extend
S.TaggedErrorfromeffect/Schemadirectly. Use a package$Icomposer for a distinct namespaced identifier; otherwise omit the identifier, and never pass a bare identifier equal to the tag. Cause-carrying errors declarecause: S.Defect({ includeStack: true })explicitly. - Exported APIs need JSDoc and docgen-clean examples.
- Do not finish with failing
check,lint,test, ordocgen. - Do not suffix schema constants with
Schema; use the domain name. - For non-class schemas, export runtime type aliases with the same name:
export type X = typeof X.Type. - Do not use native
switch; useMatch. For empty/non-empty array branching, preferA.matchover manual length checks. - All new schemas must be meaningfully annotated:
.pipe($I.annoteSchema("Name", { description }))for non-class schemas (LiteralKitincluded),$I.annote("Name", { description })as a class's trailing argument. Never.annotate($I.annote(...)). - Service identifiers must use package composer
.create(...)and$I\MyService``. - If a schema has properties that are a union of literal strings, it should be a tagged union composed via
LiteralKit,.mapMembers, andTuple.evolve, then finalized withS.toTaggedUnion. UseS.TaggedUniononly for canonical_tagobject-union construction. - Reusable functions returning
Effectshould use namedEffect.fn("Namespace.name")(orEffect.fnUntracedfor hot/internal paths). Zero-arg effect values may stayEffect.gen(...).pipe(Effect.withSpan("Name"))when there is no exported/reused function to expose. - Effect workflows should be observable with spans and structured logs from the start; add metrics (
effect/Metric+Effect.track*) where the path is important enough to measure. - Durations and time windows should use
effect/Duration, not ad-hoc number literals. - For nullable/nullish/optional schema-to-
Optionconversions, useS.OptionFromNullOr,S.OptionFromNullishOr, orS.OptionFromOptionalKey(neverS.OptionFromOptional; optional fields use exact keys, EF-40). For runtimeOptionobject fields, preferO.getSomesStruct({...})(@beep/utils) for heterogeneous struct spreads with known keys — it is runtime-identical toR.getSomesbut preserves literal keys and per-key value types; reserveR.getSomes({...})for homogeneous dynamic-key dictionaries. UseO.all({...})when the whole object is all-or-nothing. (Amended 2026-07-05 — seestandards/architecture/DECISIONS.md.) - Exported helper utilities should expose dual data-first/data-last forms via
dualfromeffect/Function. - Never use
JSON.parse/JSON.stringifyin Effect-first code, tests, or fixtures; useS.fromJsonString(S.Unknown)/S.fromJsonString+S.decodeUnknownEffect/S.encodeUnknownEffector explicit Result/Option codecs for non-throwing sync helpers. - Do not use
S.decodeSync,S.decodeUnknownSync,S.encodeSync, orS.encodeUnknownSyncby default. Prefer Effect codecs, and map schema errors withEffect.mapError(...)before returning them across module, service, CLI, HTTP, or test-helper boundaries. If a legacy sync API must remain throwing, use Result codecs plusResult.getOrThrowWith(...)to preserve anErroror typed boundary error. - Plain data schemas are
class X extends S.Opaque<X, { readonly brand: unique symbol }>()(S.Struct({...}).pipe($I.annoteSchema("X", {...}))) {}(S.TaggedStructfor_tagdata); construct withX.make(...). KeepS.Class/S.TaggedClass/S.Error/S.TaggedErrorfor errors, persisted entities, schemas with instance members, and.extendchains. Every schema class and Opaque carries{ readonly brand: unique symbol }; a{}brand needs a schema-first inventory exception.S.Classchecksinstanceof, so plain objects failS.isand encoding against it. - Only runtime boundaries (app entrypoints/tests) may call
Effect.runSync/Effect.runPromise/Effect.runFork; libraries returnEffect. - Process entrypoints must not recover the root cause merely to set
process.exitCodeand printCause.pretty(...). Pass the failing root effect directly toBunRuntime.runMain/NodeRuntime.runMain; userunMain(..., { teardown })plusRuntime.defaultTeardownfor custom terminal rendering. - Promise-returning APIs must be lifted with
Effect.tryPromiseat boundaries. - Resource lifetimes must be explicit with
Effect.acquireUseReleaseorEffect.scoped. - Retries must be expressed via
Effect.retryandSchedule, not manual retry loops. - Timeout behavior should be modeled with
Effect.timeoutOption/Effect.timeoutOrElseinstead of ad-hoc timers. - Forking defaults to
Effect.forkChild;Effect.forkDetachrequires explicit daemon intent. - Parallel fan-out should set explicit concurrency for
Effect.forEach/Effect.all/Effect.validatewhen load is non-trivial. - Config should be modeled via
ConfigandConfigProvider, not directprocess.envaccess in domain services. - Secrets must be represented as
Redactedvalues (Config.Redacted/Redacted.make) and never logged raw. - Error recovery should be precise (
catchTag/catchFilter) instead of blanket recovery that hides unrelated failures; at outer HTTP/worker boundaries preferEffect.catchCause/Effect.matchCauseEffect. At process entrypoints, prefer platformrunMainteardown instead. - Use
Effect.failfor expected business errors and reserveEffect.die/Effect.orDiefor invariants and impossible states. - When layer memoization sharing is unsafe, force isolation with
Effect.provide(..., { local: true })orLayer.fresh. - Schema-first development: if a data model can be represented as
Schema, define theSchemafirst and derive runtime types from it; avoid plaintype/interfacefor domain data shapes. - Service contracts may stay interfaces, but row shapes, wire payloads, and persisted models should not.
- Prefer schema-level defaults (
S.withConstructorDefault,S.withDecodingDefaultKey) instead of ad-hoc runtime fallback object literals. - Guard predicates for domain strings/paths/tags should come from branded schemas via
S.is(...), not ad-hocregex.test(...)helpers. - For schema-modeled domain comparisons, prefer
S.toEquivalence(schema)over manual===/!==checks. - For deterministic format conversions, prefer schema transformations (
S.decodeTo+SchemaTransformation.transform) over ad-hoc string conversion helpers. - Never use native
Array.prototype.sort; useA.sort(values, order)with explicitOrderinstances. - Avoid ad-hoc
String(...)coercion in domain logic; model unknown-to-string normalization with schema transformations and compare via schema equivalence. - When branching on boolean values, prefer the flattest equivalent form first; use
Bool.matchwhen both branches do real work or when it is materially clearer than direct boolean selection. - Before keeping
O.match(...), check whetherO.map(...),O.flatMap(...),O.liftPredicate(...), andO.getOrElse(...)express the same control flow more flatly. AvoidonNone: () => ({})object compaction; useO.map(...)plusO.getOrElse(() => ({})),O.getSomesStruct({...})for heterogeneous struct spreads (R.getSomes({...})only for homogeneous dynamic-key dictionaries), orS.OptionFrom*according to boundary semantics. (Amended 2026-07-05 — seestandards/architecture/DECISIONS.md.) - In callback-only contexts where
yield*is unavailable (for exampleSchemaTransformation.transform*), consume services withContext.Service.use(...). - Do not import
node:pathin production/tooling source. UsePath.Pathservice (yield* Path.Path) forjoin,resolve,relative,basename, etc. - Do not use native
fetchin production/tooling source. UseHttpClientfromeffect/http/HttpClientand provide platform client layers (Bun:BunHttpClient.layer). - Named or reused domain constraints must be modeled as schemas first; prefer built-in schema constructors/checks before
S.makeFilter, then derive guards withS.is(...). - Reusable
S.makeFilter,S.makeFilterGroup, and reusable built-in check blocks must includeidentifier,title, anddescription;messagestays user-facing. - Use
LiteralKitfor named internal literal domains, especially when.Enum,.is,.match,.matchOrElse,.toTaggedUnion, or annotation-bearing schema values are part of the design; useS.Literalsfor anonymous inline unions never referenced by name. - Prefer
P.isTagged("Tag")over manual_tagguard helpers built fromP.hasProperty,P.isObject, or inline_tagstring checks. - When a matcher is the function body or a reusable helper, prefer
Match.type<T>().pipe(...)/Match.tags(...)overMatch.value(...). - At logging/recovery boundaries, render causes with
Cause.pretty(...)orCause.prettyErrors(...)instead of ad-hocString(error)fallback chains. - Prefer the tersest equivalent helper form when behavior is unchanged: direct helper refs over trivial wrapper lambdas,
flow(...)for passthroughpipe(...)callbacks, and shared thunk helpers when already in scope. - Where upstream covers a
foundation/modelingconcept's intent, its covered facets retire in the same PR that migrates their consumers, with no alias; the whole concept retires unless the lines reading its uncovered members outnumber the lines using its covered facets (ADAPT, per the facet census). Do not add a concept upstream already covers (standards/architecture/DECISIONS.md"Upstream-First Foundation/Modeling"; effect-laws-v1 law 24).
Always / Never Examples (load on demand)
- references/always-never-core.md — core modeling: 1 absence handling · 2 typed error boundary · 3 schema naming + annotation · 4 type checks · 4b schema-backed guards + internal modeling · 5 Match over switch · 6 tagged unions + exhaustive branching · 7 service identity via package composer · 8 discriminated union schemas · 9 Effect-returning functions
- references/observability-runtime.md — observability + runtime boundaries:
10 observability + metrics · 10b boundary logging with Cause · 10c process entrypoint teardown ·
11 Duration values · 12 nullish schemas to Option · 13 dual helper APIs ·
14 JSON via Schema codecs · 15 runtime boundary for running effects ·
16
Effect.tryPromiseboundaries · 17 scoped resource safety - references/resilience-recovery.md — resilience + recovery: 18 retry + timeout modeling · 19 structured concurrency + bounded parallelism · 20 config + secret redaction · 21 precise recovery by tag · 22 expected failures vs defects · 23 layer isolation
Source of Truth References
.repos/effect points to the Effect child at
$HOME/YeeBois/references/effect/effect (Effect main is v4). The workspace also
contains effect-tsgo; bun run beep refs provision provisions it from
packages/tooling/policy-pack/repo-configs/data/references.json, with BEEP_REFERENCES_ROOT overriding the
$HOME/YeeBois/references/effect root.
- Effect LLMS guide
- Effect ai-docs index
- Effect migration notes
- Effect Schema docs
- Effect core API source
- Effect Config source
- Effect Fiber source
Verify
rg -n " as |@ts-ignore|!\\.|\\bany\\b" apps packages toolingrg -n "new Error\\(|throw " apps packages toolingrg -n "typeof " apps packages toolingrg -n "\\bswitch\\s*\\(" apps packages toolingrg -n "export const [A-Za-z0-9_]+Schema\\b" apps packages toolingrg -n ":\\s*Effect\\.Effect<|=>\\s*Effect\\.Effect<|=>\\s*Effect\\.[A-Za-z]+" apps packages toolingrg -n "Date\\.now\\(|Math\\.random\\(|setTimeout\\(|setInterval\\(" apps packages toolingrg -n "\\.split\\(|\\.trim\\(|\\.toLowerCase\\(|\\.toUpperCase\\(|\\.replace\\(" apps packages toolingrg -n "S\\.Struct\\(" apps packages toolingrg -n "^export interface [A-Za-z0-9_]+" apps packages toolingrg -n "^export type [A-Za-z0-9_]+\\s*=\\s*\\{" apps packages toolingrg -n "JSON\\.parse\\(|JSON\\.stringify\\(" apps packages toolingrg -n "Effect\\.run(Sync|Promise|Fork)\\(" apps packages tooling infrarg -n "process\\.env" apps packages tooling infrarg -n "forkDetach\\(" apps packages toolingrg -n "catchAll\\(|Effect\\.catch\\(" apps packages toolingrg -n "Redacted\\.value\\(" apps packages toolingrg -n "Effect\\.die\\(|Effect\\.orDie\\(" apps packages toolingrg -n "Effect\\.provide\\(.*local:\\s*true|Layer\\.fresh\\(" apps packages toolingrg -n "const hasTag|P\\.hasProperty\\(.*_tag|P\\.isObject\\(.*_tag|Match\\.value\\(" apps packages toolingrg -n "Effect\\.fn\\(function\\*|=\\s*Effect\\.gen\\(function\\*" apps packages toolingrg -n "Cause\\.pretty\\(|Cause\\.prettyErrors\\(|Effect\\.catchCause\\(|Effect\\.matchCauseEffect\\(" apps packages toolingbun run checkbun run lintbun run testbun run docgen
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.

