opencode-effect-enforcer 0.2.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +3 -3
  2. package/docs/effect-4.0.0-changelog.md +3213 -0
  3. package/docs/effect-4.0.0.md +110 -0
  4. package/guidance/effect-first-development.md +8 -6
  5. package/guidance/progressive-disclosure-guidance.md +15 -7
  6. package/package.json +2 -2
  7. package/patterns/avoid-any.md +2 -2
  8. package/patterns/avoid-direct-json.md +6 -6
  9. package/patterns/avoid-native-fetch.md +8 -6
  10. package/patterns/avoid-node-imports.md +2 -2
  11. package/patterns/avoid-non-null-assertion.md +2 -2
  12. package/patterns/avoid-object-type.md +2 -2
  13. package/patterns/avoid-platform-coupling.md +1 -1
  14. package/patterns/avoid-process-env.md +3 -4
  15. package/patterns/avoid-ts-ignore.md +1 -1
  16. package/patterns/context-tag-extends.md +11 -8
  17. package/patterns/effect-promise-vs-trypromise.md +6 -7
  18. package/patterns/prefer-arr-sort.md +1 -1
  19. package/patterns/prefer-effect-fn.md +21 -65
  20. package/patterns/prefer-schema-class.md +3 -3
  21. package/patterns/throw-in-effect-gen.md +1 -1
  22. package/patterns/use-clock-service.md +4 -0
  23. package/patterns/use-command-executor-service.md +2 -2
  24. package/patterns/use-http-client-service.md +8 -6
  25. package/patterns/use-random-service.md +6 -7
  26. package/skills/effect-ai-chat/SKILL.md +13 -7
  27. package/skills/effect-ai-language-model/SKILL.md +50 -21
  28. package/skills/effect-ai-prompt/SKILL.md +25 -14
  29. package/skills/effect-ai-provider/SKILL.md +50 -22
  30. package/skills/effect-ai-streaming/SKILL.md +27 -12
  31. package/skills/effect-ai-tool/SKILL.md +37 -28
  32. package/skills/effect-atom-rpc/SKILL.md +57 -36
  33. package/skills/effect-atom-state/SKILL.md +57 -19
  34. package/skills/effect-batching/SKILL.md +5 -3
  35. package/skills/effect-cache/SKILL.md +19 -7
  36. package/skills/effect-cli/SKILL.md +17 -8
  37. package/skills/effect-command-executor/SKILL.md +115 -64
  38. package/skills/effect-concurrency-testing/SKILL.md +26 -6
  39. package/skills/effect-config/SKILL.md +53 -2
  40. package/skills/effect-context-witness/SKILL.md +6 -6
  41. package/skills/effect-domain-modeling/SKILL.md +8 -1
  42. package/skills/effect-error-handling/SKILL.md +15 -2
  43. package/skills/effect-fiber/SKILL.md +20 -25
  44. package/skills/effect-filesystem/SKILL.md +69 -57
  45. package/skills/effect-http-api/SKILL.md +72 -22
  46. package/skills/effect-http-client/SKILL.md +25 -21
  47. package/skills/effect-http-server/SKILL.md +51 -21
  48. package/skills/effect-incremental-migration/SKILL.md +17 -8
  49. package/skills/effect-layer-design/SKILL.md +8 -0
  50. package/skills/effect-managed-runtime/SKILL.md +6 -0
  51. package/skills/effect-mcp-server/SKILL.md +64 -24
  52. package/skills/effect-observability/SKILL.md +61 -15
  53. package/skills/effect-parallelization/SKILL.md +24 -7
  54. package/skills/effect-path/SKILL.md +8 -2
  55. package/skills/effect-platform-abstraction/SKILL.md +88 -66
  56. package/skills/effect-platform-layers/SKILL.md +68 -67
  57. package/skills/effect-pubsub-event-bus/SKILL.md +56 -60
  58. package/skills/effect-react-composition/SKILL.md +19 -6
  59. package/skills/effect-rpc-api/SKILL.md +24 -24
  60. package/skills/effect-rpc-client/SKILL.md +33 -28
  61. package/skills/effect-rpc-cluster/SKILL.md +122 -78
  62. package/skills/effect-rpc-server/SKILL.md +56 -20
  63. package/skills/effect-scheduling/SKILL.md +29 -1
  64. package/skills/effect-schema-composition/SKILL.md +31 -13
  65. package/skills/effect-schema-v4/SKILL.md +94 -10
  66. package/skills/effect-scope/SKILL.md +13 -5
  67. package/skills/effect-service-implementation/SKILL.md +1 -1
  68. package/skills/effect-socket/SKILL.md +52 -8
  69. package/skills/effect-sql/SKILL.md +67 -33
  70. package/skills/effect-stream/SKILL.md +50 -5
  71. package/skills/effect-testing/SKILL.md +91 -2
  72. package/skills/effect-workflow/SKILL.md +76 -39
@@ -0,0 +1,110 @@
1
+ # Effect 4.0.0-rc.116 → 4.0.0
2
+
3
+ ## Provenance and release scope
4
+
5
+ - Previous dependency: `effect@4.0.0-rc.116`.
6
+ - Target: **`effect@4.0.0`, the first stable v4 release** and npm's `latest`
7
+ dist-tag, checked on 2026-10-02. The `rc` tag remains rc.118; it is not the target.
8
+ - npm publication: 2026-10-01 at 03:11:28 UTC. The GitHub release is explicitly
9
+ non-prerelease, published at 01:47:13 UTC. The release commit is dated September 30.
10
+ - Exact source: [`effect@4.0.0`](https://github.com/Effect-TS/effect/tree/effect%404.0.0),
11
+ commit `67ba4e46a11ccda0b6761578bfd22c04ae00167d`.
12
+ - [Official release](https://github.com/Effect-TS/effect/releases/tag/effect%404.0.0).
13
+ - [Complete source comparison](https://github.com/Effect-TS/effect/compare/effect%404.0.0-rc.116...effect%404.0.0).
14
+ - [Full upstream release notes](effect-4.0.0-changelog.md): **93 complete release
15
+ sections across 31 packages**, including dependency-only entries and the stable
16
+ release overview. All rc.117, rc.118, and 4.0.0 sections are included; rc.116 is
17
+ excluded. Only trailing whitespace is normalized.
18
+ - Previous audit: [rc.112 → rc.116](effect-4.0.0-rc.116.md).
19
+
20
+ The moving source reference was already ahead of the release. Contracts were
21
+ checked against the exact tag rather than importing subsequent main-branch fixes.
22
+ The stable release overview describes some features introduced before rc.116;
23
+ the individual entries and source comparison identify this migration's changes.
24
+
25
+ ## Alignment decisions
26
+
27
+ | Area | Alignment |
28
+ | --- | --- |
29
+ | Packaging | Pin the dependency and lockfile to 4.0.0. Remove the `unstable` import segment throughout live examples, module augmentations, source references, and test fixtures. Use `effect/http-api`, root `Arbitrary`, and format-specific binary-text encoding modules. |
30
+ | Stability | Stable package publication does not stabilize every API. `@stability unstable` APIs may break in minor releases; unmarked APIs follow semver. Directly consumed Effect-family packages use the same version. |
31
+ | Schema | Rename range/string checks; document Unicode units and JSON Schema approximations, persisted check IDs, single/type-only brands, representation round trips, and current issue constructors. |
32
+ | Configuration and CLI | Document dependent configuration, whole-group defaults, fallback-result semantics, and negative-number lexing. |
33
+ | Lifetimes and concurrency | Align partition ordering, closeable scopes, cache waiter ownership, zero-TTL preloading, runtime disposal, queue terminal batches, PubSub completion, races, and bounded repetition. |
34
+ | HTTP and RPC | Isolate entrypoint routers, place routes/protocols in the served app, share stateful services at the parent boundary, expose slot-specific parsing, preserve WebSocket exits, and distinguish missed pongs from connection-open retries. |
35
+ | SQL and workflow | Review savepoint release, failed COMMIT behavior, PostgreSQL session retirement and row codecs, workflow execution-ID derivation, reset retries, and conditional reply clearing. |
36
+ | AI and MCP | Align Toolkit service/error requirements, Decision probability options, streaming deltas, provider compatibility, object-root schemas, and version-specific MCP results/errors. |
37
+ | Reactive state and telemetry | Review registry ownership, SWR refresh timing, concurrent atom calls, batching, SSR hydration, and OTLP response draining/flush lifetimes. |
38
+ | Platform examples | Correct stale byte-size, error, scoped-resource, service-wiring, and process-output examples while retaining the platform abstraction boundaries. |
39
+ | Pattern feedback | Update positive guidance and fixtures to current APIs. Correct construction, decoding, randomness, ordering, defect-handling, service-definition, and HTTP retry examples. Keep the existing detector inventory. |
40
+
41
+ ## Complete audit inventory
42
+
43
+ Five independently assigned audits covered all **53 skill directories (54 Markdown
44
+ files)**. The integration review covered all **45 pattern definitions**, all
45
+ **four guidance documents**, the runtime's Effect references, dependency wiring,
46
+ and detector fixtures. The historical essays remain conceptual material; current
47
+ baseline and API policy live in the two active guidance documents.
48
+
49
+ The following table accounts for every skill directory (`effect-` prefix omitted).
50
+ Files with unchanged contracts were reviewed without cosmetic edits.
51
+
52
+ | Skill directories | Review focus |
53
+ | --- | --- |
54
+ | schema-v4, schema-composition, domain-modeling, domain-predicates, pattern-matching, optics, typeclass-design, graph | Schema contracts, brands, predicates, transformations, representations, orders, and unchanged graph/optic APIs. |
55
+ | config, cli, testing | Config source/fallback behavior, argument lexing, native Arbitrary, Vitest fixtures, and effectful TestSchema assertions. |
56
+ | http-api, http-client, http-server, rpc-api, rpc-client, rpc-server, rpc-cluster, sql, workflow, socket | Router ownership, transport/protocol contracts, parsing, network address representation, transaction/session safety, and durable state compatibility. |
57
+ | ai-chat, ai-language-model, ai-prompt, ai-provider, ai-streaming, ai-tool, mcp-server | Model/tool requirements, provider translations, response histories, streaming, and MCP protocol versions. |
58
+ | atom-rpc, atom-state, react-composition, observability, wide-events | Reactive lifecycle, React peers, registry/hydration, exporters, and wide-event guidance including its accompanying article. |
59
+ | cache, batching, layer-design, scope, service-implementation, context-witness, managed-runtime, incremental-migration | Acquisition/interruption, memoization, dependency capture, runtime ownership, and boundary integration. |
60
+ | fiber, parallelization, concurrency-testing, pubsub-event-bus, stream, scheduling, error-handling | Partition order, cancellation/cleanup, queue completion, scheduling, typed failures, and defects. |
61
+ | filesystem, path, platform-abstraction, platform-layers, command-executor | File and process services, portable paths, errors, byte sizes, test doubles, layer precedence, and output draining. |
62
+
63
+ ## Compatibility considerations
64
+
65
+ - This plugin directly depends only on `effect` from the Effect family. Companion
66
+ libraries in examples are reviewed against 4.0.0 source; they are not added as
67
+ plugin runtime dependencies.
68
+ - TypeScript 5.9 or newer is required. The repository already satisfies that
69
+ minimum. `@effect/vitest` and `@effect/doctest` require Vitest 5; this repository
70
+ uses plain Vitest without those adapters, so its runner need not change.
71
+ - `@effect/atom-react` requires React 19. Deno adapters require Deno 2.8.3 or newer.
72
+ - Removed module paths are not aliases. Update module augmentations and tooling
73
+ references along with source imports. HTTP API runtime identities and its
74
+ reserved streaming failure event use `http-api` too.
75
+ - Schema check IDs in persisted representations changed with the check names;
76
+ type-only brands do not survive representation serialization. Reapply nominal
77
+ brands after rebuilding, and migrate stored check IDs where applicable.
78
+ - Workflow execution IDs now hash length-prefixed tags/idempotency keys. Existing
79
+ persisted runs and deferred tokens need an explicit compatibility decision;
80
+ unchanged business idempotency strings do not imply unchanged execution IDs.
81
+ - Hash values can change. Do not treat Effect hash output as a stable persisted
82
+ identifier or wire format.
83
+
84
+ ## Verification scope
85
+
86
+ The documentation test checks core Effect import entrypoints and named exports
87
+ in every TypeScript/TSX fence across skills, patterns, and guidance. It also
88
+ compiles each explicitly marked, independent example with strict TypeScript
89
+ options against the installed stable release. Illustrative fragments may rely
90
+ on surrounding declarations or intentionally show unsupported code; they are
91
+ not all standalone programs.
92
+
93
+ Release-note completeness is checked against every package changelog at the
94
+ target tag. Source-diff and removed-export scans complement the semantic audits.
95
+ The detector tests retain bidirectional pattern inventory and README catalog
96
+ coverage. Companion-package examples are source-reviewed rather than tested
97
+ against live providers, databases, or OS transports.
98
+
99
+ Final integration verification on 2026-10-02:
100
+
101
+ - `bun run check` — passed formatting, lint, and project typechecking.
102
+ - `bun run test` — **912 tests passed across 53 test files**.
103
+ - Documentation compilation — **90 marked examples and 1,096 core Effect import
104
+ declarations** checked against installed `effect@4.0.0`.
105
+ - `git diff --check` — passed.
106
+ - Release-note comparison — all **93 section bodies across 31 packages** match
107
+ the exact tag, after trailing-whitespace normalization.
108
+ - Live-content scans — no removed `effect/unstable/*` or `effect/httpapi` paths,
109
+ rc.116 baseline declarations, or renamed Schema check calls remain. Historical
110
+ audits and verbatim upstream changelogs retain their original references.
@@ -2,9 +2,9 @@
2
2
 
3
3
  This document defines the working model behind Effect-first code using the Effect v4 ecosystem.
4
4
 
5
- Bundled baseline: **Effect 4.0.0-rc.116**. Check the consuming project's installed
5
+ Bundled baseline: **Effect 4.0.0 (stable)**. Check the consuming project's installed
6
6
  version and inspect the matching upstream release tag before using newer APIs.
7
- The release changelog and migration audit are in `docs/effect-4.0.0-rc.116.md`.
7
+ The release changelog and migration audit are in `docs/effect-4.0.0.md`.
8
8
  Source paths below are relative to the Effect source reference; locate symbols
9
9
  by name rather than relying on line numbers or a particular tool name.
10
10
 
@@ -141,12 +141,13 @@ export const decodeCreateTaskInput =
141
141
  - `import * as Eq from "effect/Equal"`
142
142
  - `import * as Bool from "effect/Boolean"`
143
143
  - Reserve root imports from `"effect"` for core combinators/types such as `Effect`, `Match`, `pipe`, and `flow`.
144
- - Keep unstable imports deliberate and local.
144
+ - Import area modules from `effect/<area>` (for example `effect/http-api` and `effect/ai`). Keep APIs marked `@stability unstable` deliberate and local; they may break in minor releases. Use the same release version for `effect` and all directly used `@effect/*` packages.
145
145
 
146
146
  ### EF-5: Effect modules over native collection helpers
147
147
 
148
148
  - Use `Arr`, `R`, `Str`, `Eq`, `HashMap`, `HashSet`, `MutableHashMap`, `MutableHashSet`.
149
149
  - Sort with `Arr.sort(values, order)` and explicit `effect/Order` values (`Order.String`, `Order.Number`, `Order.mapInput`, etc.), never native array `.sort()`.
150
+ - Partition/separate tuples put successes or predicate-passing values first: `[successes, failures]` / `[passes, fails]`. This applies to Array, Chunk, Effect, Record, Option's `partitionMap`, and Stream partitioning; do not assume a failures-first tuple.
150
151
  - Avoid domain usage of native `Object`, `Map`, `Set`, `Date`, and direct native string helpers.
151
152
  - Do not use imperative `for` / `for...of` loops in domain code. Use `Arr.map`, `Arr.filter`, `Arr.filterMap`, or `Arr.reduce` for pure transformations. For effectful iteration, use `Effect.forEach` (which also supports concurrency).
152
153
  - When behavior is unchanged, prefer the tersest helper form: direct helper refs over trivial wrapper lambdas, `flow(...)` over passthrough `pipe(...)` callbacks, and shared thunk helpers when already in scope.
@@ -561,7 +562,7 @@ const withConnection = <A, E, R>(
561
562
  - Keep retry policy close to the failing effect.
562
563
  - Retry only proven-idempotent operations at the narrowest boundary that can classify the failure.
563
564
  - Let exhausted failures remain visible unless the boundary has a truthful fallback.
564
- - Reference: `retry` in `packages/effect/src/Effect.ts` and the Schedule cookbook.
565
+ - Reference: `retry` in `packages/effect/src/Effect.ts` and `ai-docs/src/06_schedule/10_schedules.ts` at the installed release tag.
565
566
 
566
567
  Example:
567
568
 
@@ -772,11 +773,12 @@ export class VersionSyncOptions extends Schema.Class<VersionSyncOptions>(
772
773
 
773
774
  - If a guard validates domain strings/paths/tags, define a branded schema and use `Schema.is(...)`.
774
775
  - If a domain constraint is named, reused, matched on, or structurally validated, model it as a schema first rather than a forest of ad-hoc predicate helpers.
775
- - Prefer built-in schema constructors/checks such as `Schema.NonEmptyString`, `Schema.NonEmptyArray`, `Schema.TupleWithRest`, `Schema.Union`, `Schema.isPattern`, and `Schema.isIncludes` before `Schema.makeFilter`.
776
+ - Prefer built-in schema constructors/checks such as `Schema.NonEmptyString`, `Schema.NonEmptyArray`, `Schema.TupleWithRest`, `Schema.Union`, `Schema.isPattern`, and `Schema.isIncluding` before `Schema.makeFilter`.
776
777
  - Keep guard intent and reusable check intent in schema annotations and check metadata.
777
778
  - For internal literal domains, use `Schema.is(Schema.Literal(...))` for type guards, `Match` for exhaustive matching, and `Schema.Literal(...).annotate({...})` for annotated schema values.
778
779
  - Prefer named intermediate schemas; export and document them when reusable or when they materially clarify the module's domain model, otherwise keep them module-local.
779
780
  - Propagate branded schema types through the persistence layer (e.g., ORM column types: `text().$type<AccessToken>()`) to enforce compile-time safety across the entire stack and prevent parameter-swapping bugs.
781
+ - `Schema.brand` is type-only and takes one concrete identifier; apply it repeatedly to compose brands. Put runtime invariants in checks. Brand identifiers are not stored in AST annotations or preserved by `SchemaRepresentation`; reapply brands after reconstruction when the TypeScript type is needed.
780
782
 
781
783
  Example:
782
784
 
@@ -790,7 +792,7 @@ import * as Str from 'effect/String';
790
792
  type TopicKind = 'plain' | 'scoped';
791
793
 
792
794
  const ContainsScopeSeparator = Schema.String.check(
793
- Schema.isIncludes(':', {
795
+ Schema.isIncluding(':', {
794
796
  identifier: 'ContainsScopeSeparatorCheck',
795
797
  title: 'Contains Scope Separator',
796
798
  description: 'A string that contains `:`.',
@@ -1,9 +1,17 @@
1
1
  # Agent Rules
2
2
 
3
- The bundled guidance targets **Effect 4.0.0-rc.116**. Read the consuming project's
4
- version before applying an API: stable v3, older prereleases, and unreleased main
5
- can have different contracts. Keep directly used Effect-family packages on
6
- compatible release versions.
3
+ The bundled guidance targets **Effect 4.0.0 (stable)**. Read the consuming project's
4
+ version before applying an API: v3, v4 prereleases, and unreleased main can have
5
+ different contracts. Use the same version of `effect` and every directly used
6
+ `@effect/*` package; Effect packages are versioned and released together.
7
+
8
+ Import area modules from `effect/<area>`: for example `effect/http`,
9
+ `effect/http-api`, `effect/rpc`, `effect/ai`, and `effect/process`. Import
10
+ `Arbitrary` from `effect/Arbitrary`, and binary-text codecs from
11
+ `effect/encoding/{Base64,Base64Url,Hex,EncodingError}`. Check source stability
12
+ annotations: `@stability unstable` APIs can break in minor releases even though
13
+ their import paths have no `unstable` segment. APIs without that annotation
14
+ follow semver. TypeScript 5.9 or newer is required.
7
15
 
8
16
  Before planning or writing Effect code, load the skills relevant to the APIs involved.
9
17
  For other tasks, load a skill only when its guidance is needed to answer or complete the task.
@@ -11,8 +19,8 @@ For other tasks, load a skill only when its guidance is needed to answer or comp
11
19
  When skills leave any ambiguity, or when you encounter unfamiliar APIs during implementation, read the OpenCode `effect` reference at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`. Treat this reference as the source of truth over `node_modules`, stale external docs, or memory.
12
20
 
13
21
  Check the reference revision too. For this baseline, inspect the
14
- `effect@4.0.0-rc.116` tag (for example with `git show
15
- effect@4.0.0-rc.116:packages/effect/src/Schema.ts`) when main has moved ahead.
22
+ `effect@4.0.0` tag (for example with `git show
23
+ effect@4.0.0:packages/effect/src/Schema.ts`) when main has moved ahead.
16
24
  Source symbols and signatures at that tag take precedence over stale prose or
17
25
  line-number links. Public exports marked `@internal` in source are not application APIs.
18
26
 
@@ -30,7 +38,7 @@ line-number links. Public exports marked `@internal` in source are not applicati
30
38
  - `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/packages/effect/MCP.md` — MCP server resources, prompts, tools, and transports.
31
39
  - `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/packages/effect/OPTIC.md` — `Optic` guide for lenses, prisms, optionals, traversals, and schema isos.
32
40
  - `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/packages/vitest/README.md` — `@effect/vitest` testing guide.
33
- - `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/cookbooks/schedule.md` — Schedule cookbook; read before designing retries, repeats, polling, backoff, jitter, timeouts, or recurrence limits.
41
+ - `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/ai-docs/src/06_schedule/10_schedules.ts` — runnable Schedule guide at the stable tag; read alongside `Schedule.ts` before designing retries, repeats, polling, backoff, jitter, timeouts, or recurrence limits. The stable tag has no `cookbooks/schedule.md`.
34
42
 
35
43
  Do not use migration notes, Effect-repo contributor patterns, or in-repo specs as general application guidance. Read those only when the task is explicitly about migrating old Effect code or contributing to the Effect repository itself.
36
44
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://www.schemastore.org/package.json",
3
3
  "name": "opencode-effect-enforcer",
4
- "version": "0.2.8",
4
+ "version": "0.3.0",
5
5
  "description": "OpenCode V2 plugin for Effect v4 skills, guidance, and pattern enforcement",
6
6
  "keywords": [
7
7
  "opencode",
@@ -46,7 +46,7 @@
46
46
  "@ast-grep/napi": "^0.42.3",
47
47
  "@opencode/plugin": "2.0.8",
48
48
  "diff": "^9.0.0",
49
- "effect": "4.0.0-rc.116",
49
+ "effect": "4.0.0",
50
50
  "picomatch": "^4.0.3",
51
51
  "yaml": "^2.8.1"
52
52
  },
@@ -30,8 +30,8 @@ generics :: ∀ a. Constraint a ⇒ a → F a
30
30
  bad :: Unknown → T
31
31
  bad x = x `as` T -- trust me bro
32
32
 
33
- good :: Unknown → Either ParseError T
34
- good x = Schema.decode schemaT x -- prove it
33
+ good :: Unknown → Effect T SchemaError
34
+ good x = Schema.decodeUnknownEffect schemaT x -- decode and retain the proof
35
35
  ```
36
36
 
37
37
  Using `as any` bypasses type checking entirely. The `as unknown as T` pattern is equivalent—casting through `unknown` still erases type information. Note: `as const` is acceptable—it narrows to literal types without erasing type safety.
@@ -30,8 +30,8 @@ encodeJson :: Schema a → a → String
30
30
  bad :: String → IO User
31
31
  bad json = JSON.parse json -- returns Any, throws on invalid
32
32
 
33
- good :: String → Either ParseError User
34
- good json = Schema.decodeUnknownSync(Schema.fromJsonString(User)) json
33
+ good :: String → Effect User SchemaError
34
+ good json = Schema.decodeUnknownEffect(Schema.fromJsonString(User)) json
35
35
 
36
36
  unknownJson = Schema.fromJsonString(Schema.Unknown)
37
37
 
@@ -41,11 +41,11 @@ data User = Schema.Class "User"
41
41
  , name :: Schema.String
42
42
  }
43
43
 
44
- decode :: String → Either ParseError User
45
- decode = Schema.decodeUnknownSync(Schema.fromJsonString(User))
44
+ decode :: String → Effect User SchemaError
45
+ decode = Schema.decodeUnknownEffect(Schema.fromJsonString(User))
46
46
 
47
- encode :: User → String
48
- encode = Schema.encodeSync(Schema.fromJsonString(User))
47
+ encode :: User → Effect String SchemaError
48
+ encode = Schema.encodeEffect(Schema.fromJsonString(User))
49
49
  ```
50
50
 
51
51
  `JSON.parse` returns `any` and throws on invalid input. `Schema.fromJsonString(...)` provides typed, validated JSON parsing and encoding. Use `Schema.fromJsonString(Schema.Unknown)` when the JSON shape is intentionally unknown; `Schema.UnknownFromJsonString` is internal in current Effect v4. Direct JSON methods remain reasonable at narrow logging/debugging boundaries.
@@ -33,29 +33,31 @@ good url = pipe(
33
33
 
34
34
  ```haskell
35
35
  -- Composable request building
36
- request :: Effect HttpClientRequest HttpBodyError
36
+ request :: HttpClientRequest
37
37
  request = pipe(
38
- HttpClientRequest.post("/api/users"),
39
- HttpClientRequest.bodyJson({ name: "Alice" })
38
+ HttpClientRequest.get("https://api.example.com/users"),
39
+ HttpClientRequest.setHeader("accept", "application/json")
40
40
  )
41
41
 
42
42
  -- With retry, timeout, tracing
43
43
  resilient :: Effect Response HttpError (HttpClient | Scope)
44
44
  resilient = pipe(
45
- request,
46
- Effect.flatMap(HttpClient.execute),
45
+ HttpClient.execute(request),
47
46
  Effect.retry(Schedule.recurs(3)),
48
47
  Effect.timeout(Duration.seconds(10))
49
48
  )
50
49
 
51
50
  -- Platform layer at entry point
52
51
  main = program.pipe(
53
- Effect.provide(BunHttpClient.layer) -- or NodeHttpClient.layer
52
+ Effect.provide(BunHttpClient.layer) -- or NodeHttpClient.layerUndici
54
53
  )
55
54
  ```
56
55
 
57
56
  Native `fetch` produces untyped Promise rejections. Use `HttpClientRequest`, `HttpClientResponse`, and `HttpClient` from Effect for typed errors, composable request building, and testability via layer substitution.
58
57
 
58
+ Import the HTTP modules from `effect/http`. Retry only proven-idempotent requests;
59
+ do not copy a retry policy onto ordinary non-idempotent POST/PATCH operations.
60
+
59
61
  Exception: an explicit low-level platform implementation that cannot use Effect HTTP. Keep it isolated, lift it with `Effect.tryPromise`, propagate the supplied `AbortSignal`, classify status before decoding, and decode unknown bodies with `Schema`.
60
62
 
61
63
  References: EF-9b, Checklist #42 in effect-first-development.md
@@ -73,12 +73,12 @@ good = do
73
73
  | `node:fs/promises` | `FileSystem.FileSystem` |
74
74
  | `node:path` | `Path.Path` |
75
75
  | `node:os` | `FileSystem.makeTempFileScoped` / `Path.Path` / platform layer |
76
- | `node:child_process` | `ChildProcessSpawner` + `ChildProcess` from `effect/unstable/process` |
76
+ | `node:child_process` | `ChildProcessSpawner` + `ChildProcess` from `effect/process` |
77
77
  | `node:http` | `HttpClient.HttpClient` |
78
78
  | `node:https` | `HttpClient.HttpClient` |
79
79
  | `node:stream` | `Stream` from effect |
80
80
  | `node:readline` | `Terminal.Terminal` |
81
- | `node:crypto` | `Crypto` from `effect/unstable/crypto` or a wrapped Effect service |
81
+ | `node:crypto` | `Crypto.Crypto` from `effect/Crypto` or a wrapped Effect service |
82
82
 
83
83
  **Exceptions:**
84
84
 
@@ -37,8 +37,8 @@ safe :: User → Maybe Email
37
37
  safe user = user?.contact?.email ?? Nothing
38
38
 
39
39
  -- With Schema for external data
40
- validated :: Unknown → Either ParseError User
41
- validated = Schema.decode userSchema
40
+ validated :: Unknown → Effect User SchemaError
41
+ validated = Schema.decodeUnknownEffect User
42
42
  ```
43
43
 
44
44
  The `!` operator is "trust me, this isn't null"—if wrong, runtime crash. Use `?.`, `??`, `Option`, or type guards for safe null handling.
@@ -39,8 +39,8 @@ good :: User → Effect ()
39
39
  good user = doSomething user -- clear structure, IDE support
40
40
 
41
41
  -- For unknown shapes
42
- decode :: Unknown → Either ParseError User
43
- decode = Schema.decode userSchema
42
+ decode :: Unknown → Effect User SchemaError
43
+ decode = Schema.decodeUnknownEffect User
44
44
  ```
45
45
 
46
46
  `Object` and `{}` provide no type safety—they accept any non-null value. Use explicit types, `Record<K,V>`, `unknown`, or Schema for validation.
@@ -38,6 +38,6 @@ good = Layer.provide(platformLayer) -- no platform coupling
38
38
  good = -- runtime provides ChildProcessSpawner, FileSystem, etc.
39
39
  ```
40
40
 
41
- Binding packages wrap external systems (CLIs, APIs, databases) and must be platform-agnostic. They should depend on abstract services from `effect` and its unstable namespaces, such as `FileSystem`, `Path`, `HttpClient`, and `ChildProcessSpawner`, but never on `@effect/platform-bun` or `@effect/platform-node` concrete implementations.
41
+ Binding packages wrap external systems (CLIs, APIs, databases) and must be platform-agnostic. They should depend on abstract services from `effect` and its area entrypoints, such as `FileSystem`, `Path`, `HttpClient` from `effect/http`, and `ChildProcessSpawner` from `effect/process`, rather than hardwiring `@effect/platform-bun` or `@effect/platform-node` concrete implementations.
42
42
 
43
43
  Platform-specific layers (`BunServices.layer`, `NodeServices.layer`) belong in the runtime or CLI entry point, not in bindings. The runtime provides concrete implementations of abstract Effect services there.
@@ -29,13 +29,12 @@ Config.Redacted :: String -> Config Redacted -- for sensitive values
29
29
  bad :: Effect String
30
30
  bad = Effect.sync \_ -> process.env.API_KEY -- raw side effect
31
31
 
32
- good :: Effect String ConfigError
32
+ good :: Effect (Redacted String) ConfigError
33
33
  good = Config.Redacted "API_KEY" -- typed, redacted
34
34
 
35
- better :: Effect String ConfigError
35
+ better :: Effect Int ConfigError
36
36
  better = Config.Int("PORT")
37
- & Config.withDefault "3000"
38
- & Config.map Number.parse -- with transformation
37
+ & Config.withDefault 3000 -- defaults use the decoded type
39
38
  ```
40
39
 
41
40
  `process.env` is a raw side effect with no type safety. Use `Config.*` for validated, composable, testable configuration.
@@ -32,7 +32,7 @@ bad = do
32
32
 
33
33
  good :: Effect a
34
34
  good = do
35
- x ← Schema.decode schema raw -- prove correctness
35
+ x ← Schema.decodeUnknownEffect schema raw -- prove correctness
36
36
  ```
37
37
 
38
38
  Suppressing errors masks bugs that surface at runtime. Fix the underlying type issue instead.
@@ -31,7 +31,7 @@ Use `Context.Service` for service definitions. Replace these legacy spellings:
31
31
  | Legacy API | Era | Replacement |
32
32
  | --------------------------- | --------- | -------------------------- |
33
33
  | `Context.Tag` / `GenericTag` | pre-v4 | `Context.Service` |
34
- | `Effect.Service` | early v4 | `Context.Service` |
34
+ | `Effect.Service` | v3 | `Context.Service` |
35
35
  | `ServiceMap.Service` / `.*` | prerelease | `Context.Service` / `Context.*` |
36
36
 
37
37
  ```haskell
@@ -47,21 +47,24 @@ class ParallelClient extends Effect.Service<ParallelClient>()(...)
47
47
  class MyService extends ServiceMap.Service<MyService>()("@app/MyService", { ... })
48
48
 
49
49
  -- Fix: Context.Service
50
- class ParallelClient extends Context.Service<ParallelClient>()(
50
+ class ParallelClient extends Context.Service<ParallelClient, Interface>()(
51
51
  "@parallel/ParallelClient"
52
52
  )
53
53
  ```
54
54
 
55
+ <!-- typecheck -->
55
56
  ```typescript
56
- // Concrete service (single implementation)
57
- export class ParallelClient extends Context.Service<ParallelClient>()(
57
+ import { Context, Effect, Layer } from 'effect';
58
+
59
+ class ParallelClient extends Context.Service<ParallelClient, {
60
+ readonly ping: Effect.Effect<string>;
61
+ }>()(
58
62
  '@parallel/ParallelClient'
59
63
  ) {}
60
64
 
61
- // Interface-style service (multiple implementations, config, infrastructure)
62
- export class Clipboard extends Context.Service<Clipboard>()(
63
- '@Clipboard/Clipboard'
64
- ) {}
65
+ const layer = Layer.succeed(ParallelClient, {
66
+ ping: Effect.succeed('pong')
67
+ });
65
68
  ```
66
69
 
67
70
  When migrating from `ServiceMap`, also update the corresponding module accessors:
@@ -16,7 +16,7 @@ suggestSkills:
16
16
 
17
17
  ```haskell
18
18
  -- Transformation
19
- promise :: IO (Promise a) → Effect a ∅ -- rejection = defect (uncatchable)
19
+ promise :: IO (Promise a) → Effect a ∅ -- rejection = defect, outside typed E
20
20
  tryPromise :: IO (Promise a) → Effect a E -- rejection = typed error (catchable)
21
21
  ```
22
22
 
@@ -24,7 +24,7 @@ tryPromise :: IO (Promise a) → Effect a E -- rejection = typed error (c
24
24
  -- Pattern
25
25
  bad :: Effect User ∅
26
26
  bad = Effect.promise \_ → fetchUser id
27
- -- rejection becomes Defect: can't catch, crashes fiber
27
+ -- rejection becomes a defect: catchTag does not handle it
28
28
 
29
29
  good :: Effect User FetchError
30
30
  good = Effect.tryPromise
@@ -37,11 +37,10 @@ good = Effect.tryPromise
37
37
  handle :: Effect User FetchError → Effect User ∅
38
38
  handle = catchTag "FetchError" \e → defaultUser
39
39
 
40
- -- Defects bypass all handlers
41
- defect :: Effect a ∅ → Effect a E
42
- defect = id -- can't recover from defects
40
+ -- Defects require explicit cause/defect handling, not ordinary typed recovery
41
+ inspectDefect = Effect.catchDefect
43
42
  ```
44
43
 
45
- `Effect.promise` converts rejections to uncatchable defects. Use `Effect.tryPromise` for typed, recoverable errors in the E channel.
44
+ `Effect.promise` converts rejections to defects outside the typed error channel. `Effect.catchDefect` and cause-level handlers can observe them, but expected rejection belongs in `Effect.tryPromise` with a typed error.
46
45
 
47
- Any reference to `Effect.promise` is flagged — not just `yield* Effect.promise(...)`. Piping, passing, or returning `Effect.promise` propagates the same defect-conversion problem to the consumer and is equally wrong.
46
+ Any reference to `Effect.promise` is flagged — not just `yield* Effect.promise(...)`. Review callbacks, returned helpers, and direct calls alike. An explicit boundary whose rejection genuinely represents a defect may intentionally use `Effect.promise`; explain that contract rather than relabeling expected failures as defects.
@@ -44,7 +44,7 @@ sorted = Arr.sort byNameThenAge
44
44
 
45
45
  -- Reverse
46
46
  descending :: [User] -> [User]
47
- descending = Arr.sort (Order.reverse byName)
47
+ descending = Arr.sort (Order.flip byName)
48
48
  ```
49
49
 
50
50
  Native `.sort()` mutates the array in place and uses an untyped comparator. `Arr.sort` from `effect/Array` returns a new sorted array using a composable, typed `Order`.
@@ -84,74 +84,30 @@ Key details:
84
84
 
85
85
  ## Complete Before/After
86
86
 
87
+ <!-- typecheck -->
87
88
  ```typescript
88
- // BEFORE — plain arrow functions, no tracing
89
- export class UserRepository extends Context.Service<UserRepository>()(
90
- '@services/UserRepository',
91
- {
92
- make: Effect.gen(function* () {
93
- const db = yield* DatabaseClient;
89
+ import { Context, Effect, Layer } from 'effect';
90
+ import * as Schema from 'effect/Schema';
94
91
 
95
- return {
96
- findById: (id: string): Effect.Effect<User, UserNotFound> =>
97
- Effect.gen(function* () {
98
- const row = yield* db.query(
99
- 'SELECT * FROM users WHERE id = ?',
100
- id
101
- );
102
- if (!row)
103
- return yield* new UserNotFound({
104
- id,
105
- message: `Not found: ${id}`
106
- });
107
- return row as User;
108
- }),
109
-
110
- create: (
111
- data: CreateUserData
112
- ): Effect.Effect<User, DuplicateUser> =>
113
- Effect.gen(function* () {
114
- return yield* db.insert('users', data);
115
- })
116
- };
117
- })
118
- }
119
- ) {}
120
-
121
- // AFTER — Effect.fn, every method gets a traced span
122
- export class UserRepository extends Context.Service<UserRepository>()(
123
- '@services/UserRepository',
124
- {
125
- make: Effect.gen(function* () {
126
- const db = yield* DatabaseClient;
127
-
128
- const findById = Effect.fn('UserRepository.findById')(
129
- (id: string): Effect.Effect<User, UserNotFound> =>
130
- Effect.gen(function* () {
131
- const row = yield* db.query(
132
- 'SELECT * FROM users WHERE id = ?',
133
- id
134
- );
135
- if (!row)
136
- return yield* new UserNotFound({
137
- id,
138
- message: `Not found: ${id}`
139
- });
140
- return row as User;
141
- })
142
- );
143
-
144
- const create = Effect.fn('UserRepository.create')(
145
- (data: CreateUserData): Effect.Effect<User, DuplicateUser> =>
146
- Effect.gen(function* () {
147
- return yield* db.insert('users', data);
148
- })
149
- );
150
-
151
- return { findById, create };
152
- })
153
- }
92
+ class User extends Schema.Class<User>('User')({ id: Schema.String }) {}
93
+ class UserNotFound extends Schema.TaggedError<UserNotFound>()(
94
+ 'UserNotFound', { id: Schema.String }
154
95
  ) {}
96
+ class Database extends Context.Service<Database, {
97
+ readonly findUser: (id: string) => Effect.Effect<User, UserNotFound>;
98
+ }>()('app/Database') {}
99
+ class UserRepository extends Context.Service<UserRepository, {
100
+ readonly findById: (id: string) => Effect.Effect<User, UserNotFound>;
101
+ }>()('app/UserRepository') {}
102
+
103
+ const layer = Layer.effect(UserRepository, Effect.gen(function* () {
104
+ const db = yield* Database;
105
+ // Before: (id: string) => Effect.gen(function* () { ... })
106
+ const findById = Effect.fn('UserRepository.findById')(function* (id: string) {
107
+ return yield* db.findUser(id);
108
+ });
109
+ return UserRepository.of({ findById });
110
+ }));
155
111
  ```
156
112
 
157
113
  ## When NOT to use Effect.fn
@@ -16,7 +16,7 @@ suggestSkills:
16
16
 
17
17
  ```haskell
18
18
  -- Transformation
19
- Schema.Struct :: { fields } -> Schema { fields } -- anonymous, no constructor
19
+ Schema.Struct :: { fields } -> Schema { fields } -- structural value with make helpers
20
20
  Schema.Class :: String -> { fields } -> Class -- named, constructable, extensible
21
21
 
22
22
  -- Pattern
@@ -25,7 +25,7 @@ bad = Schema.Struct({
25
25
  id: Schema.String,
26
26
  name: Schema.String
27
27
  })
28
- -- anonymous type, no constructor, no instanceof
28
+ -- structural type, make helpers, no class identity / instanceof
29
29
 
30
30
  good :: Schema
31
31
  good = class User extends Schema.Class<User>("User")({
@@ -49,6 +49,6 @@ extend = class Admin extends User.extend<Admin>("Admin")({
49
49
  }) {}
50
50
  ```
51
51
 
52
- `Schema.Struct` produces an anonymous schema without a constructor or `instanceof` support. `Schema.Class` provides a named type, constructor, extensibility, and optional annotation support when docs or introspection benefit from it. Prefer `Schema.Class` for decoded domain/API shapes, union members, and values that need identity. `Schema.Struct` remains appropriate for local structural composition, configuration internals, and schemas where class identity adds no value, so review this informational finding in context.
52
+ Every schema, including `Schema.Struct`, has `make`, `makeOption`, and `makeEffect` construction helpers. `Schema.Class` additionally provides a named class, `new` / `instanceof` identity, and class extension. Prefer it for decoded domain/API shapes, union members, and values that need identity. `Schema.Struct` remains appropriate for local structural composition, configuration internals, and schemas where class identity adds no value, so review this informational finding in context.
53
53
 
54
54
  References: EF-3 in effect-first-development.md
@@ -30,7 +30,7 @@ suggestSkills:
30
30
 
31
31
  ```haskell
32
32
  -- Transformation
33
- throw :: Error -> ⊥ -- untyped, uncatchable by Effect
33
+ throw :: Error -> ⊥ -- defect, outside the typed error channel
34
34
  yield* Effect.fail :: TaggedError -> E ⊥ E -- typed, catchable via catchTag
35
35
  ```
36
36
 
@@ -43,3 +43,7 @@ test = do
43
43
  ```
44
44
 
45
45
  Direct `Date` usage is non-deterministic. Use `DateTime.now` or `Clock.currentTimeMillis` for testable time operations via `TestClock`.
46
+
47
+ Use wall-clock time for timestamps and `Clock.monotonicTimeNanos` for elapsed
48
+ durations; wall-clock adjustments must not
49
+ change a measured interval.