agentleFS
kriegcloudkriegcloud/beep-effect/.claude/skills/schema-first-development/SKILL.md

schema-first-development

Skill78 starsChanged 6 days agoApache-2.0View on GitHub

Focused schema-first guidance for this repo's effect/Schema and @beep/schema patterns. Use when adding or refactoring schemas, replacing exported interface/type data models, fixing schema-first lint violations, modeling literal domains or tagged unions, applying schema defaults or transformations, decoding external input, or reviewing schema code for repo-law compliance.

The file

Show its rules and text

We split this skill into the rules it gives an agent

28 rules. About 0 are common, 4 are distinctive, and 24 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 24

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use this skill for schema-heavy work in this repository.

    line 13
  2. Distinctive:

    Treat schema-first rules as enforced repository law, not style preference.

    line 15
  3. Very specific:

    Keep Schema as the source of truth for pure data models.

    line 22
  4. Very specific:

    Check local Effect v4 source for nontrivial Schema API choices.

    line 39
  5. Distinctive:

    Prefer exact upstream or repo-local helpers over memory and ad-hoc plumbing.

    line 46
  6. Very specific:

    Check upstream effect/Schema first, then reuse @beep/schema and existing local schemas before inventing new checks.

    line 55
  7. Very specific:

    Prefer Effect schema codecs.

    line 69
  8. Very specific:

    No exported pure-data interface or type literal remains.

    line 73
  9. Very specific:

    No schema value ends with Schema.

    line 74
  10. Very specific:

    Prefer import * as S from "effect/Schema" and canonical Effect aliases.

    line 83
  11. Very specific:

    Create file-local identity composers with @beep/identity/packages.

    line 84
  12. Distinctive:

    Use named intermediate schemas for reused concepts.

    line 89
  13. Very specific:

    Prefer LiteralKit for named literal domains: its keyed API (.Enum, .is, match, .toTaggedUnion(...)) plus the S.Literals members it inherits …

    line 90
  14. Very specific:

    Prefer MappedLiteralKit when a protocol/code mapping needs both encoded and decoded literal helper surfaces.

    line 94
  15. Very specific:

    Do not add as const to inline array literals passed directly to LiteralKit(...).

    line 96
  16. Very specific:

    Use S.toTaggedUnion("<field>") for discriminators such as kind, status, type, subtype, profile, or family.

    line 103
  17. Very specific:

    Use S.TaggedUnion(...) only for canonical _tag object unions.

    line 105
  18. Very specific:

    Prefer schema-derived tagged-union helpers (.cases, .guards, .isAnyOf, .match) when constructing, guarding, or branching on a schema tagged …

    line 109
  19. Very specific:

    Use S.OptionFromNullOr, S.OptionFromNullishOr and S.OptionFromOptionalKey for absence at the boundary.

    line 111
  20. Very specific:

    Use S.OptionFrom* when the wire/schema field is optional or nullish.

    line 115
  21. Very specific:

    Use O.all({...}) for all-or-nothing fixed-shape composition.

    line 120
  22. Very specific:

    Prefer built-in schema constructors and checks before S.makeFilter(...).

    line 125
  23. Very specific:

    Treat broad exported/domain/boundary primitives such as S.String, S.Number, and unbounded arrays as prompts to ask whether the schema …

    line 126
  24. Very specific:

    If you need a custom reusable check, include identifier, title, and description.

    line 129
  25. Very specific:

    Use S.is(schema) for guards, S.toEquivalence(schema) for comparisons, and Arbitrary.schema(schema) (effect/Arbitrary) for schema-modeled …

    line 131
  26. Very specific:

    Use S.fromJsonString(S.Unknown) or S.fromJsonString(schema) for JSON string boundaries.

    line 133
  27. Very specific:

    Use S.decodeUnknownEffect / S.decodeEffect and S.encodeUnknownEffect / S.encodeEffect by default.

    line 135
  28. Very specific:

    Use effect-first-development when the task is broader than schema work, including service/layer wiring and typed error flow outside schema …

    line 145

≈ “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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Schema-First Development

Use this skill for schema-heavy work in this repository.

Treat schema-first rules as enforced repository law, not style preference. The primary sources are:

  • standards/effect-first-development.md
  • packages/tooling/tool/cli/src/commands/Lint/SchemaFirst.ts
  • standards/schema-first.inventory.jsonc

Keep Schema as the source of truth for pure data models.

Workflow

  1. Classify the task.
  • New schema or domain model
  • Refactor from exported interface / type literal
  • Boundary decode, transform, or defaulting work
  • Literal domain or tagged union modeling
  • Review or lint-fix
  1. Load only the reference file you need.
  • Repository laws: references/repo-laws.md
  • Local shared schema primitives: references/local-primitives.md
  • Pattern selection and anti-patterns: references/pattern-catalog.md
  • Real repository examples: references/examples.md
  1. Check local Effect v4 source for nontrivial Schema API choices. .repos/effect points to $HOME/YeeBois/references/effect/effect, the Effect child of the reference workspace; Effect main is v4.
  • Start with .repos/effect/packages/effect/SCHEMA.md.
  • Confirm behavior in .repos/effect/packages/effect/src/Schema.ts or the specialized module (SchemaAST, SchemaGetter, SchemaIssue, SchemaRepresentation, SchemaTransformation) when using advanced APIs.
  • Prefer exact upstream or repo-local helpers over memory and ad-hoc plumbing.
  1. Apply the laws in this order.
  • Model pure data with Schema first.
  • Model plain object data as a branded S.Opaque over an annotated S.Struct (S.TaggedStruct for _tag data). Keep S.Class / S.TaggedClass / S.Error / S.TaggedError for errors, persisted entities, schemas with instance members, and .extend chains. Every schema class and Opaque declaration carries { readonly brand: unique symbol }.
  • Check upstream effect/Schema first, then reuse @beep/schema and existing local schemas before inventing new checks. Where upstream covers a foundation/modeling concept'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 in standards/architecture/DECISIONS.md "Upstream-First Foundation/Modeling").
  • Move normalization, defaults, nullable handling, and JSON parsing into the schema.
  • Annotate reusable non-class schemas with .pipe($I.annoteSchema(...)) (LiteralKit included); classes take $I.annote(...) as their trailing argument.
  • Derive guards, equivalence, arbitraries, codecs, tagged-union helpers, and literal helpers from the schema instead of writing parallel helpers.
  • Prefer Effect schema codecs; map schema errors with Effect.mapError(...) when the error leaves the local helper/module boundary.
  1. Verify before finishing.
  • No exported pure-data interface or type literal remains.
  • No schema value ends with Schema.
  • Non-class schemas export same-name runtime type aliases.
  • Tagged unions use the repo-preferred construction.
  • Finite variants are modeled as discriminated unions instead of optional payload bags.
  • JSON boundaries use schema codecs, not native JSON helpers.

Fast Rules

  • Prefer import * as S from "effect/Schema" and canonical Effect aliases.
  • Create file-local identity composers with @beep/identity/packages.
  • Plain data is class X extends S.Opaque<X, { readonly brand: unique symbol }>()(S.Struct({...}).pipe($I.annoteSchema("X", {...}))) {}. Construct with X.make(...); there is no new X(...) and no .extend. Annotate the Struct inside the pipe, because X.annotate(...) drops the statics. See references/repo-laws.md section 2.
  • Use named intermediate schemas for reused concepts.
  • Prefer LiteralKit for named literal domains: its keyed API (.Enum, .is, match, .toTaggedUnion(...)) plus the S.Literals members it inherits (.literals, .pick(...), .mapMembers(...)). Use S.Literals for anonymous inline unions never referenced by name.
  • Prefer MappedLiteralKit when a protocol/code mapping needs both encoded and decoded literal helper surfaces.
  • Do not add as const to inline array literals passed directly to LiteralKit(...); its const type parameters preserve the literal tuple.
  • Model finite variants, lifecycle states, status/result cases, and case-specific payloads as discriminated unions, not one optional/nullish payload bag.
  • Decode external optional/nullish case bags into internal tagged models when compatibility requires the wire shape.
  • Use S.toTaggedUnion("<field>") for discriminators such as kind, status, type, subtype, profile, or family.
  • Use S.TaggedUnion(...) only for canonical _tag object unions.
  • Fix a tag with S.TaggedStruct / S.TaggedClass / S.TaggedError, and any other required single-literal field with S.tag(v), never S.Literal(v); bun run beep laws schema-tag-idioms --check enforces it.
  • Prefer schema-derived tagged-union helpers (.cases, .guards, .isAnyOf, .match) when constructing, guarding, or branching on a schema tagged union.
  • Use S.OptionFromNullOr, S.OptionFromNullishOr and S.OptionFromOptionalKey for absence at the boundary. Optional fields use exact keys (EF-40): S.optionalKey, S.withDecodingDefaultKey, S.OptionFromOptionalKey, never their undefined-admitting siblings.
  • Use S.OptionFrom* when the wire/schema field is optional or nullish. If runtime Option values are already being shaped into an object, prefer O.getSomesStruct({...}) (@beep/utils) for heterogeneous struct spreads with known keys — runtime-identical to R.getSomes but type-preserving per key; reserve R.getSomes({...}) for homogeneous dynamic-key dictionaries. Use O.all({...}) for all-or-nothing fixed-shape composition. (Amended 2026-07-05 — see standards/architecture/DECISIONS.md.)
  • Use S.withConstructorDefault(...), S.withDecodingDefaultKey(...), and S.decodeTo(...) with SchemaTransformation for normalization and fallback behavior.
  • Prefer built-in schema constructors and checks before S.makeFilter(...).
  • Treat broad exported/domain/boundary primitives such as S.String, S.Number, and unbounded arrays as prompts to ask whether the schema should be more precise.
  • If you need a custom reusable check, include identifier, title, and description.
  • Use S.is(schema) for guards, S.toEquivalence(schema) for comparisons, and Arbitrary.schema(schema) (effect/Arbitrary) for schema-modeled property tests.
  • Use S.fromJsonString(S.Unknown) or S.fromJsonString(schema) for JSON string boundaries.
  • Use S.decodeUnknownEffect / S.decodeEffect and S.encodeUnknownEffect / S.encodeEffect by default. Reach for S.decodeUnknownResult, S.decodeResult, or S.decodeUnknownOption only for deliberate non-throwing synchronous helpers; do not teach or add S.decodeSync, S.decodeUnknownSync, S.encodeSync, or S.encodeUnknownSync. If a legacy synchronous wrapper must still throw, convert Result failures with Result.getOrThrowWith(...) so raw schema issues do not escape the boundary.

Escalation

  • Use effect-first-development when the task is broader than schema work, including service/layer wiring and typed error flow outside schema modeling.

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.