Effect

Repositories built on the effect library — typed error handling, tagged constructors, Layers.

What this is

Repositories built on the effect library — typed error handling, tagged constructors, Layers.

Extends TypeScript — those rules apply too; this file only adds what's specific to Effect.

How to onboard it

A repository picks up this stack by onboarding on a profile that includes it. redline init detects the profile from what is in the repository, so in most cases this is automatic:

terminal
$ npx redlinegate init  # detects the profile
$ npx redlinegate init --profile typescript-effect  # or name one

One profile pulls these rules in: typescript-effect. A profile that includes a stack extending this one gets these rules too.

Once onboarded, your files match this stack when they fit any of these globs:

  • **/*.ts
  • **/*.tsx
  • **/*.mts
  • **/*.cts

How to use it — 5 rules

Nothing to run. Once your profile includes Effect, redline init renders these rules into your repository's AI tooling and the reviewer applies them on every pull request. When a review comment cites one of these ids, this table is where to look up what it catches and why.

Rule idSeverityCatches
effect/manual-error-tag-in-catchHIGHBranching on `error._tag` (or `switch (error._tag)`) inside a broad `Effect.catch`/`catchAll`/`catchIf` handler. The error channel is not narrowed, so the handled error stays in the effect's type and every caller still has to handle it. Use `Effect.catchTag`/`catchTags` (or `catchReason`/`catchReasons` for a tagged `reason`).
effect/manual-tagged-constructionHIGHAn object literal with a literal `_tag` (`{ _tag: "NotFound", id }`) where a tagged constructor exists. It skips the Schema's validation and the class's identity — `instanceof`, `Equal` and the error's stack trace — while looking identical to the type checker. Use the Schema `.make`, the tagged class or error constructor, or the `Data.taggedEnum` variant constructor. Not a pattern passed to `Match.when`.
effect/service-constructor-importHIGHRuntime code importing a service's `make…` constructor from a relative path (`import { makeIssueService } from "./issue-service"`). The service's own requirements stop propagating to the composition root, and a test can no longer provide a different implementation. Import the owning `Layer` and yield the service from context. Test files are exempt.
effect/manual-tag-comparisonSUGGESTIONComparing `value._tag` to a string literal, or switching on it, outside a catch handler. `Match.tag`/`Match.tags` with `Match.exhaustive` makes a variant added later a compile error instead of a silent fall-through; `Predicate.isTagged` for a single reusable check.
effect/prefer-matchSUGGESTIONA chained ternary comparing one value against two or more literals (`kind === "a" ? x : kind === "b" ? y : z`). Use `Match.value(kind).pipe(Match.when(…), …)`.

Expected output

A finding from this file, and every finding Redline produces, opens with a machine-readable first line — severity, then the rule id in brackets, then the problem in one line:

a finding from this file
Redline/HIGH [effect/manual-error-tag-in-catch]: <one-line problem>

Ids are aggregated per rule, which is how the organisation finds out which rules earn their place and which only generate noise — so a finding without a valid id cannot be measured and counts as untagged. Of the 5 rules here, 3 HIGH and 2 SUGGESTION. Only a BLOCKER must not merge; a SUGGESTION may be dismissed without justification, and is never upgraded to get attention.

If a rule here fires constantly on code your team has deliberately decided to allow, that is the signal to raise with the standards owner — the rule id is what makes that conversation measurable — not to argue it away comment by comment.

How to edit it

Rules are edited in standards/stacks/effect.md and nowhere else. The rendered copies in AGENTS.md, .github/copilot-instructions.md and .github/instructions/ are generated and are overwritten by the next render. A change here propagates to every onboarded repository as a pull request, so treat it as a production change.

  1. Edit the markdownstandards/ is the only place a human edits a rule. Everything under AGENTS.md, .github/copilot-instructions.md and .github/instructions/ is rendered from it and is overwritten by the next render.
  2. node scripts/assign-rule-ids.mjsAssigns a permanent <stack>/<slug> id to any new rule bullet and rewrites the file in place. Do not invent an id by hand. CI runs the same script with --check and fails if a rule is missing one.
  3. node scripts/render-self.mjsRe-renders this repository's own artifacts from the edited source. CI runs it with --check, so stale checked-in output fails the build.
  4. Bump standards/manifest.json → versionRequired in the same pull request as the rule change. Sync pull requests quote the version, so a repository's rendered artifacts always name where they came from.
  5. Add a CHANGELOG.md entryAlso in the same pull request. A standards change with no measurement is an opinion — record the seed score alongside it.
  6. node scripts/validate.mjsThe bundle self-check CI runs: manifest integrity, well-formed rule ids, the severity output contract surviving your edit, glob portability.

Rule ids are permanent. Rewording a rule is fine and keeps its id; renaming or removing an id orphans every historical telemetry record that cited it.

The full file

standards/stacks/effect.md · 30 lines · 2.1 KB
# Effect Review Rules

**Scope:** TypeScript in a repository built on the `effect` library. These rules extend the
TypeScript rules; they exist because Effect already ships the typed operator for each of
these jobs, and hand-rolling it discards what the type system was tracking. Do not apply
them to a repository that does not depend on `effect`.

## HIGH

- `effect/manual-error-tag-in-catch` — Branching on `error._tag` (or `switch (error._tag)`) inside a broad
  `Effect.catch`/`catchAll`/`catchIf` handler. The error channel is not narrowed, so the handled error stays in the
  effect's type and every caller still has to handle it. Use `Effect.catchTag`/`catchTags` (or
  `catchReason`/`catchReasons` for a tagged `reason`).
- `effect/manual-tagged-construction` — An object literal with a literal `_tag` (`{ _tag: "NotFound", id }`) where a
  tagged constructor exists. It skips the Schema's validation and the class's identity — `instanceof`, `Equal` and
  the error's stack trace — while looking identical to the type checker. Use the Schema `.make`, the tagged
  class or error constructor, or the `Data.taggedEnum` variant constructor. Not a pattern passed to `Match.when`.
- `effect/service-constructor-import` — Runtime code importing a service's `make…` constructor from a relative
  path (`import { makeIssueService } from "./issue-service"`). The service's own requirements stop propagating to
  the composition root, and a test can no longer provide a different implementation. Import the owning `Layer`
  and yield the service from context. Test files are exempt.

## SUGGESTION

- `effect/manual-tag-comparison` — Comparing `value._tag` to a string literal, or switching on it, outside a catch
  handler. `Match.tag`/`Match.tags` with `Match.exhaustive` makes a variant added later a compile error instead of a
  silent fall-through; `Predicate.isTagged` for a single reusable check.
- `effect/prefer-match` — A chained ternary comparing one value against two or more literals
  (`kind === "a" ? x : kind === "b" ? y : z`). Use `Match.value(kind).pipe(Match.when(…), …)`.