opencode-effect-enforcer 0.2.5 → 0.2.6

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 (49) hide show
  1. package/README.md +42 -140
  2. package/docs/effect-4.0.0-rc.116-changelog.md +2654 -0
  3. package/docs/effect-4.0.0-rc.116.md +102 -0
  4. package/guidance/effect-first-development.md +23 -14
  5. package/guidance/progressive-disclosure-guidance.md +3 -3
  6. package/package.json +2 -2
  7. package/patterns/avoid-direct-tag-checks.md +1 -1
  8. package/patterns/avoid-process-env.md +4 -4
  9. package/patterns/context-tag-extends.md +4 -4
  10. package/patterns/prefer-redacted-config.md +10 -10
  11. package/patterns/require-effect-concurrency.md +1 -1
  12. package/skills/effect-ai-chat/SKILL.md +2 -2
  13. package/skills/effect-ai-language-model/SKILL.md +36 -4
  14. package/skills/effect-ai-prompt/SKILL.md +1 -1
  15. package/skills/effect-ai-provider/SKILL.md +23 -12
  16. package/skills/effect-ai-tool/SKILL.md +13 -0
  17. package/skills/effect-atom-rpc/SKILL.md +7 -1
  18. package/skills/effect-atom-state/SKILL.md +8 -2
  19. package/skills/effect-cache/SKILL.md +10 -1
  20. package/skills/effect-cli/SKILL.md +105 -94
  21. package/skills/effect-command-executor/SKILL.md +7 -1
  22. package/skills/effect-config/SKILL.md +67 -44
  23. package/skills/effect-domain-modeling/SKILL.md +3 -3
  24. package/skills/effect-error-handling/SKILL.md +2 -2
  25. package/skills/effect-fiber/SKILL.md +2 -2
  26. package/skills/effect-filesystem/SKILL.md +34 -4
  27. package/skills/effect-http-api/SKILL.md +17 -2
  28. package/skills/effect-http-client/SKILL.md +11 -2
  29. package/skills/effect-http-server/SKILL.md +28 -11
  30. package/skills/effect-layer-design/SKILL.md +6 -2
  31. package/skills/effect-mcp-server/SKILL.md +21 -4
  32. package/skills/effect-observability/SKILL.md +2 -2
  33. package/skills/effect-optics/SKILL.md +1 -1
  34. package/skills/effect-parallelization/SKILL.md +2 -2
  35. package/skills/effect-pattern-matching/SKILL.md +1 -1
  36. package/skills/effect-platform-abstraction/SKILL.md +3 -3
  37. package/skills/effect-rpc-api/SKILL.md +3 -3
  38. package/skills/effect-rpc-client/SKILL.md +14 -13
  39. package/skills/effect-rpc-cluster/SKILL.md +15 -12
  40. package/skills/effect-rpc-server/SKILL.md +11 -13
  41. package/skills/effect-scheduling/SKILL.md +7 -0
  42. package/skills/effect-schema-composition/SKILL.md +12 -4
  43. package/skills/effect-schema-v4/SKILL.md +75 -20
  44. package/skills/effect-scope/SKILL.md +4 -4
  45. package/skills/effect-socket/SKILL.md +161 -658
  46. package/skills/effect-sql/SKILL.md +50 -12
  47. package/skills/effect-stream/SKILL.md +19 -24
  48. package/skills/effect-testing/SKILL.md +35 -22
  49. package/skills/effect-workflow/SKILL.md +13 -2
@@ -0,0 +1,102 @@
1
+ # Effect 4.0.0-rc.112 → 4.0.0-rc.116
2
+
3
+ ## Provenance and release scope
4
+
5
+ - Previous dependency: `effect@4.0.0-rc.112`.
6
+ - Target: `effect@4.0.0-rc.116`, npm's `rc` dist-tag checked on 2026-09-18.
7
+ - npm's `latest` tag is the separate v3 line (`3.22.2`).
8
+ - Exact source: [`effect@4.0.0-rc.116`](https://github.com/Effect-TS/effect/tree/effect%404.0.0-rc.116), commit `d62dd0d65252e5d3635538f0e41adc7c08aa9beb`.
9
+ - [Complete source comparison](https://github.com/Effect-TS/effect/compare/effect%404.0.0-rc.112...effect%404.0.0-rc.116).
10
+ - [Full upstream release notes](effect-4.0.0-rc.116-changelog.md): 121 complete release sections across 31 packages, including dependency-only entries.
11
+
12
+ The interval includes rc.113, rc.114, rc.115, and rc.116. The changelog collection
13
+ was checked section-by-section against every package `CHANGELOG.md` at the target
14
+ tag. No upstream entry in that interval was omitted. Later releases supersede
15
+ earlier changes; notably PostgreSQL timestamp decoding and unknown-OID behavior
16
+ are documented using the final rc.116 contract.
17
+
18
+ ## Changes applied
19
+
20
+ | Area | Alignment |
21
+ | --- | --- |
22
+ | Dependency and baseline | Pin Effect and lockfile to rc.116; update README and privileged baseline references. |
23
+ | Config and CLI | PascalCase scalar constructors, explicit Boolean defaults, typed arrays/records and ByteSize configuration. Update secret-config detector and fixtures. |
24
+ | Schema | Effectful transformations, standalone Getter combinators, native Arbitrary, parse options, class identity, revivers, AST contracts, template literals, JSON Schema and compilation. |
25
+ | Socket | Scoped pull readers, batched values, Writer objects, typed closure, backpressure, TLS/STARTTLS, addresses, framing, and reconnect ownership. |
26
+ | RPC and cluster | Replace MessagePack recommendations with SchemaBinary; preserve transport framing rules and codec selection; use cluster option `serialization: 'binary'`. |
27
+ | Filesystem and HTTP | ByteSize counts versus numeric buffer lengths and bigint seek offsets; HTTP schemas in Schema; NetAddress, eager web-handler construction, file/range responses, upgrades, parsing and SSE options. |
28
+ | PostgreSQL | Native PgConnection/PgPool, explicit JSON binding, preparation, registry codecs, final row representations, scoped notification queues, startup and TLS behavior. |
29
+ | AI and MCP | Branded service interfaces, opaque/encoded parameter modes, failure-result codecs/origins, DecisionModel, protocol adapters, strict tools, instructions, prompt titles, and stderr logging. |
30
+ | Streams and atoms | Lazy scan seeds, partition ordering/capacity, mapBoth names, targeted error handling, middleware errors, zero TTL, reactivity branding and dehydration. |
31
+ | Lifetimes and scheduling | Cache TTL by Exit, LayerMap acquisition errors, observer error types, process cleanup, timeout fallback lifetime, repeat metadata, rate-limiter store contract, durable queue policy. |
32
+ | Testing | Native Arbitrary sampling/property checks; adapter peer/runtime requirements; compiler-checked migration examples. |
33
+ | Writing | Remove release-qualified behavior from live skills, patterns, and guidance; retain version history here and in upstream release notes. |
34
+
35
+ ## Content audit inventory
36
+
37
+ The audit covered all 53 bundled skill directories (54 Markdown files), all 45
38
+ pattern definitions, all four guidance documents, and the runtime source.
39
+ Removed public exports were compared between the exact old and new tags and
40
+ searched across the complete live content tree. Targeted scans also covered
41
+ lowercase Config/CLI constructors, callback sockets, MessagePack, fast-check,
42
+ service interface names, Stream scans, and filesystem sizes.
43
+
44
+ The following grouping records every skill directory; unchanged skills were
45
+ included in the compatibility scans rather than edited merely to mark an audit.
46
+
47
+ | Skill directories (`effect-` prefix omitted) | Review focus |
48
+ | --- | --- |
49
+ | schema-v4, schema-composition, domain-modeling, domain-predicates, pattern-matching, optics | Schema syntax, transformations, predicates, tagged unions and issue contracts. |
50
+ | config, cli | Constructor renames, defaults, prompts, collection configuration and secrets. |
51
+ | socket, filesystem, path, platform-abstraction, platform-layers, command-executor | Platform boundaries, scoped I/O, byte counts, address types and process lifetime. |
52
+ | http-api, http-client, http-server | HTTP schemas, parsing, server construction, transport errors, files, SSE and WebSocket upgrades. |
53
+ | rpc-api, rpc-client, rpc-server, rpc-cluster | Schema-aware serialization, framing, cluster defaults, storage and peer compatibility. |
54
+ | ai-chat, ai-language-model, ai-prompt, ai-provider, ai-streaming, ai-tool, mcp-server | Service types, provider responses, tool failures, decisions and MCP contracts. |
55
+ | atom-rpc, atom-state, react-composition | RPC/HTTP error channels, TTL, stream scan, React peer and reactive service contracts. |
56
+ | cache, batching, layer-design, scope, service-implementation, context-witness, managed-runtime, incremental-migration | Cached acquisition, service/layer patterns, resource ownership and runtime boundaries. |
57
+ | fiber, parallelization, concurrency-testing, pubsub-event-bus, stream, scheduling | Structured concurrency, stream operators, timing, retry and test synchronization. |
58
+ | testing, error-handling, observability, wide-events | Native property generation, error surfaces, logger configuration and instrumentation. |
59
+ | sql, workflow, graph, typeclass-design | Native database adapters, durable processing, graph and dual-helper compatibility. |
60
+
61
+ Patterns requiring content changes were `avoid-process-env`,
62
+ `prefer-redacted-config`, `avoid-direct-tag-checks`, `context-tag-extends`, and
63
+ `require-effect-concurrency`. The secret-config detector recognizes
64
+ `Config.String` and `Config.NonEmptyString`; its examples and regression cases
65
+ use the supported constructors. Bidirectional pattern/test inventory and README
66
+ catalog coverage remain required checks.
67
+
68
+ The active guidance baseline and examples were updated. Historical essays and
69
+ the earlier rc.112 audit retain their historical context.
70
+
71
+ ## Compatibility decisions
72
+
73
+ - Keep directly consumed Effect-family packages on compatible release versions.
74
+ This package directly depends only on `effect`; companion adapters are teaching
75
+ material, not additional runtime dependencies.
76
+ - `@effect/vitest` requires Vitest `>=5.0.0 <6.0.0` and Node
77
+ `^22.12.0 || ^24.0.0 || >=26.0.0`. This repository uses plain Vitest 3 and does
78
+ not install that adapter; its own runner therefore remains unchanged.
79
+ - SchemaBinary is a wire/persistence format change, not a transparent replacement
80
+ for existing MessagePack bytes. Coordinate peers and stored-data migration.
81
+ - Native PostgreSQL is a driver change. Validate row decoding, custom OIDs,
82
+ preparation/pooler settings and notification recovery at consuming boundaries.
83
+ - The source reference's moving main branch is not the baseline. The exact tag
84
+ takes precedence when source and prose disagree.
85
+
86
+ ## Verification
87
+
88
+ - Changelog completeness: all 121 selected upstream sections matched the collected
89
+ text across 31 package changelogs.
90
+ - Removed-export scan: no old-only qualified public Effect symbols remain in
91
+ skills, patterns, guidance or runtime source.
92
+ - `bun run check`: passed formatting, lint (zero warnings/errors) and TypeScript
93
+ compilation.
94
+ - `bun run test`: passed all 912 tests in 53 files, including detector cases,
95
+ inventory/catalog coverage, runtime tests and marked Markdown example
96
+ compilation against installed rc.116.
97
+ - `git diff --check`: passed.
98
+
99
+ The documentation compiler checks explicitly marked, independent TypeScript
100
+ fences. It does not execute every illustrative fragment or verify integrations
101
+ against live databases/providers. Companion-package examples were reviewed
102
+ against tagged source rather than adding all companion dependencies here.
@@ -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.112**. Check the consuming project's installed
5
+ Bundled baseline: **Effect 4.0.0-rc.116**. 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.112.md`.
7
+ The release changelog and migration audit are in `docs/effect-4.0.0-rc.116.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
 
@@ -296,7 +296,7 @@ export type Tenant = typeof Tenant.Type;
296
296
  - Prefer `Schema.Class` for tagged union member schemas.
297
297
  - Use `Schema.TaggedUnion` only for canonical `_tag` object-union construction.
298
298
  - Reference: `packages/effect/SCHEMA.md`, sections `TaggedUnion` and `toTaggedUnion`.
299
- - In rc.112, both union forms offer `matchOrElse` for partial matching with a typed fallback. Prefer `match` when every variant must be handled separately. `toTaggedUnion` narrows fallback input to unmatched variants; direct `Schema.TaggedUnion` types it as the full union.
299
+ - Both union forms offer `matchOrElse` for partial matching with a typed fallback. Prefer `match` when every variant must be handled separately. `toTaggedUnion` narrows fallback input to unmatched variants; direct `Schema.TaggedUnion` types it as the full union.
300
300
 
301
301
  Example:
302
302
 
@@ -420,6 +420,12 @@ const pollInterval = Duration.millis(250);
420
420
  const program = Effect.sleep(pollInterval).pipe(Effect.timeout(timeout));
421
421
  ```
422
422
 
423
+ ### EF-16b: Byte counts use `ByteSize`
424
+
425
+ Use `ByteSize` and `ByteSize.Input` for byte counts and storage/network limits.
426
+ Keep signed seek offsets as `bigint`. Parse external size strings before passing
427
+ them to APIs, and preserve exact counts rather than coercing them to numbers.
428
+
423
429
  ### EF-17: Nullable/nullish schema fields should decode to `Option`
424
430
 
425
431
  - Use dedicated schema helpers for optional/null conversions:
@@ -467,7 +473,7 @@ const b = pipe('value', addPrefix('p:'));
467
473
 
468
474
  ### EF-19: JSON parse/stringify must use Schema
469
475
 
470
- - Use `Schema.fromJsonString(Schema.Unknown)` for unknown JSON payloads. `Schema.UnknownFromJsonString` is internal as of beta.103.
476
+ - Use `Schema.fromJsonString(Schema.Unknown)` for unknown JSON payloads.
471
477
  - Use `Schema.fromJsonString(MySchema)` for typed JSON string boundaries.
472
478
  - Avoid direct `JSON.parse` / `JSON.stringify` in Effect-first code.
473
479
  - Reference: [`fromJsonString`](packages/effect/src/Schema.ts) in the Effect v4 source.
@@ -540,7 +546,7 @@ const readSdkValue = (client: ExternalSdk) =>
540
546
  - Prefer `Effect.scoped` for helper composition that allocates resources.
541
547
  - Do not manually open resources without an explicit finalization strategy.
542
548
  - Reference: `acquireUseRelease` and `scoped` in `packages/effect/src/Effect.ts`.
543
- - For pool checkouts in rc.112 use `Pool.use(pool, callback)` to return the item on every exit without adding a caller `Scope` requirement. Do not return an item from `Effect.scoped(Pool.get(pool))` and then use it after release.
549
+ - For pool checkouts use `Pool.use(pool, callback)` to return the item on every exit without adding a caller `Scope` requirement. Do not return an item from `Effect.scoped(Pool.get(pool))` and then use it after release.
544
550
 
545
551
  Example:
546
552
 
@@ -615,7 +621,7 @@ const runWithHeartbeat = Effect.fn('Worker.run')(function* () {
615
621
  - For non-trivial fan-out, set concurrency in `Effect.forEach`, `Effect.all`, or `Effect.validate`.
616
622
  - Avoid implicit unbounded parallelism on large collections.
617
623
  - Concurrency should be part of API intent for throughput-sensitive paths.
618
- - Use an explicit number or `"unbounded"`. The `"inherit"` option and `Effect.withConcurrency` were removed in beta.102.
624
+ - Use an explicit number or `"unbounded"`; pass concurrency policy directly to each combinator.
619
625
  - Reference: `Effect.forEach`, `Effect.all`, and `Types.Concurrency` in the Effect v4 source.
620
626
 
621
627
  Example:
@@ -640,16 +646,16 @@ Example:
640
646
  import { Config, Effect } from 'effect';
641
647
 
642
648
  const loadPort = Effect.fn('Config.loadPort')(function* () {
643
- return yield* Config.int('PORT');
649
+ return yield* Config.Int('PORT');
644
650
  });
645
651
  ```
646
652
 
647
653
  ### EF-29: Secrets must stay redacted
648
654
 
649
- - Use `Config.redacted` for secret config values.
655
+ - Use `Config.Redacted` for secret config values.
650
656
  - Use `Redacted.make` for sensitive values coming from non-config sources.
651
657
  - Never log secret values after unwrapping.
652
- - Reference: `Config.redacted` in `packages/effect/src/Config.ts` and `packages/effect/src/Redacted.ts`.
658
+ - Reference: `Config.Redacted` in `packages/effect/src/Config.ts` and `packages/effect/src/Redacted.ts`.
653
659
 
654
660
  Example:
655
661
 
@@ -657,7 +663,7 @@ Example:
657
663
  import { Config, Effect } from 'effect';
658
664
 
659
665
  const loadApiKey = Effect.fn('Config.loadApiKey')(function* () {
660
- const apiKey = yield* Config.redacted('API_KEY');
666
+ const apiKey = yield* Config.Redacted('API_KEY');
661
667
  yield* Effect.logDebug(`apiKey=${String(apiKey)}`);
662
668
  return apiKey;
663
669
  });
@@ -764,6 +770,8 @@ export class CreateOrderInput extends Schema.Class<CreateOrderInput>(
764
770
  - Use `Schema.withDecodingDefault` / `Schema.withDecodingDefaultKey` for decode-time defaults.
765
771
  - Constructor defaults may fail with `SchemaIssue.Issue`. Likewise, `MySchema.makeEffect(fields)` returns validation failures directly as `SchemaIssue.Issue`, not wrapped in `Schema.SchemaError`.
766
772
  - Constructor defaults (including `Schema.tag`) do not automatically apply during boundary decoding. Unknown input uses `Schema.decodeUnknownEffect`; `decodeSync` is not a constructor replacement.
773
+ - Class `make`, `makeOption`, and `makeEffect` preserve an existing instance. Use `new` when a distinct instance is intended.
774
+ - Pass parsing options to the decoder/encoder/constructor adapter. Model retained extra properties with `Record` or `StructWithRest`; use `onExcessProperty: "error"` for closed boundaries.
767
775
 
768
776
  Example:
769
777
 
@@ -925,6 +933,7 @@ const arraysEqual = (
925
933
 
926
934
  - If conversion is deterministic and type-shaping (path normalization, filename conversion, tagged-string normalization), model it with `Schema.decodeTo(..., SchemaTransformation.transform(...))`.
927
935
  - Prefer schema transformation helpers over ad-hoc conversion functions.
936
+ - Use `SchemaGetter.transformEffect` / `SchemaTransformation.transformEffect` for effectful conversion. Compose getters with standalone `SchemaGetter.compose`, and transformation pairs with `SchemaTransformation.composeTransformation`.
928
937
 
929
938
  Example:
930
939
 
@@ -994,7 +1003,7 @@ const UnknownToString = Schema.Unknown.pipe(
994
1003
  - For invalidatable caches, use `Effect.cachedInvalidateWithTTL(effect, Duration.infinity)` which returns a `[cachedEffect, invalidate]` tuple. Call `yield* invalidate` to force re-computation on next access.
995
1004
  - For time-based caches, use `Effect.cachedWithTTL(effect, duration)`.
996
1005
  - Prefer `Effect.cachedInvalidateWithTTL` with `Duration.infinity` over mutable `let` rebinding of cached effects.
997
- - For keyed scoped resources, rc.112 adds `RcMap.getOption` and `LayerMap.contextEffectOption`. They atomically retain already-cached entries and propagate acquisition failures; `None` means missing/closed, not failed. A `has` check followed by `get` is not equivalent.
1006
+ - For keyed scoped resources, use `RcMap.getOption` and `LayerMap.contextEffectOption` to atomically retain already-cached entries and propagate acquisition failures; `None` means missing/closed, not failed. A `has` check followed by `get` is not equivalent.
998
1007
 
999
1008
  Example:
1000
1009
 
@@ -1191,8 +1200,8 @@ export const resilientTask = task.pipe(
1191
1200
  import { Config, Effect } from 'effect';
1192
1201
 
1193
1202
  export const loadConfig = Effect.fn('Config.load')(function* () {
1194
- const port = yield* Config.int('PORT');
1195
- const apiKey = yield* Config.redacted('API_KEY');
1203
+ const port = yield* Config.Int('PORT');
1204
+ const apiKey = yield* Config.Redacted('API_KEY');
1196
1205
  return { port, apiKey };
1197
1206
  });
1198
1207
  ```
@@ -1237,7 +1246,7 @@ Use this before submitting code:
1237
1246
  24. Forking intent is explicit (`forkChild` default; `forkDetach` justified).
1238
1247
  25. Large fan-out operations specify concurrency deliberately.
1239
1248
  26. Config values come from `Config` / `ConfigProvider`, not direct `process.env` in domain logic.
1240
- 27. Secrets are `Redacted` (`Config.redacted` / `Redacted.make`) and not logged raw.
1249
+ 27. Secrets are `Redacted` (`Config.Redacted` / `Redacted.make`) and not logged raw.
1241
1250
  28. Recovery uses `catchTag` / `catchFilter` for targeted cases.
1242
1251
  29. Expected failures use `Effect.fail`; defects are reserved for invariants and discarding irrelevant upstream error types via `orDie`.
1243
1252
  30. Isolation-sensitive layer provisioning uses `{ local: true }` or `Layer.fresh`.
@@ -1,6 +1,6 @@
1
1
  # Agent Rules
2
2
 
3
- The bundled guidance targets **Effect 4.0.0-rc.112**. Read the consuming project's
3
+ The bundled guidance targets **Effect 4.0.0-rc.116**. Read the consuming project's
4
4
  version before applying an API: stable v3, older prereleases, and unreleased main
5
5
  can have different contracts. Keep directly used Effect-family packages on
6
6
  compatible release versions.
@@ -10,8 +10,8 @@ Load all relevant skills before writing or planning any code. Effect is a massiv
10
10
  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.
11
11
 
12
12
  Check the reference revision too. For this baseline, inspect the
13
- `effect@4.0.0-rc.112` tag (for example with `git show
14
- effect@4.0.0-rc.112:packages/effect/src/Schema.ts`) when main has moved ahead.
13
+ `effect@4.0.0-rc.116` tag (for example with `git show
14
+ effect@4.0.0-rc.116:packages/effect/src/Schema.ts`) when main has moved ahead.
15
15
  Source symbols and signatures at that tag take precedence over stale prose or
16
16
  line-number links. Public exports marked `@internal` in source are not application APIs.
17
17
 
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.5",
4
+ "version": "0.2.6",
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.112",
49
+ "effect": "4.0.0-rc.116",
50
50
  "picomatch": "^4.0.3",
51
51
  "yaml": "^2.8.1"
52
52
  },
@@ -55,6 +55,6 @@ TypeScript correctly narrows literal `_tag` checks. Prefer exported guards and
55
55
  matching helpers for consistent semantics and exhaustiveness as variants evolve.
56
56
  For schema-first models use union `.guards`, `.match`, or `Schema.is`; class
57
57
  variants can also use `instanceof`. `Schema.toTaggedUnion` supports discriminator
58
- keys beyond `_tag`. In rc.112, `.matchOrElse` adds partial matching with a typed
58
+ keys beyond `_tag`. `.matchOrElse` provides partial matching with a typed
59
59
  fallback. For trusted `Data.taggedEnum` values use `$is` / `$match`; `$is` checks
60
60
  only the tag and is not structural validation of unknown input.
@@ -19,9 +19,9 @@ suggestSkills:
19
19
  processEnv :: String -> IO (Maybe String) -- side effect, untyped, untestable
20
20
 
21
21
  -- Instead
22
- Config.string :: String -> Config String -- typed, composable, testable
22
+ Config.String :: String -> Config String -- typed, composable, testable
23
23
  Config.withDefault :: a -> Config a -> Config a
24
- Config.redacted :: String -> Config Redacted -- for sensitive values
24
+ Config.Redacted :: String -> Config Redacted -- for sensitive values
25
25
  ```
26
26
 
27
27
  ```haskell
@@ -30,10 +30,10 @@ bad :: Effect String
30
30
  bad = Effect.sync \_ -> process.env.API_KEY -- raw side effect
31
31
 
32
32
  good :: Effect String ConfigError
33
- good = Config.string "API_KEY" -- typed, validated
33
+ good = Config.Redacted "API_KEY" -- typed, redacted
34
34
 
35
35
  better :: Effect String ConfigError
36
- better = Config.string("PORT")
36
+ better = Config.Int("PORT")
37
37
  & Config.withDefault "3000"
38
38
  & Config.map Number.parse -- with transformation
39
39
  ```
@@ -26,13 +26,13 @@ suggestSkills:
26
26
 
27
27
  # Use `Context.Service` for All Service Definitions
28
28
 
29
- `Context.Service` is the single canonical service-definition API in Effect v4 (beta.46+). Three legacy spellings exist and must all be replaced:
29
+ Use `Context.Service` for service definitions. Replace these legacy spellings:
30
30
 
31
31
  | Legacy API | Era | Replacement |
32
32
  | --------------------------- | --------- | -------------------------- |
33
33
  | `Context.Tag` / `GenericTag` | pre-v4 | `Context.Service` |
34
34
  | `Effect.Service` | early v4 | `Context.Service` |
35
- | `ServiceMap.Service` / `.*` | beta.43 | `Context.Service` / `Context.*` |
35
+ | `ServiceMap.Service` / `.*` | prerelease | `Context.Service` / `Context.*` |
36
36
 
37
37
  ```haskell
38
38
  -- Anti-pattern: *Tag suffix + Context.Tag — removed in v4
@@ -43,10 +43,10 @@ data ParallelClientService = ...
43
43
  -- Anti-pattern: Effect.Service — also removed in v4
44
44
  class ParallelClient extends Effect.Service<ParallelClient>()(...)
45
45
 
46
- -- Anti-pattern: ServiceMap.* — removed before v4 beta.46 stabilized
46
+ -- Anti-pattern: ServiceMap.*
47
47
  class MyService extends ServiceMap.Service<MyService>()("@app/MyService", { ... })
48
48
 
49
- -- Fix: Context.Service (beta.46 API)
49
+ -- Fix: Context.Service
50
50
  class ParallelClient extends Context.Service<ParallelClient>()(
51
51
  "@parallel/ParallelClient"
52
52
  )
@@ -3,13 +3,13 @@ action: context
3
3
  tool: (edit|write)
4
4
  event: after
5
5
  name: prefer-redacted-config
6
- description: Use Config.redacted or Schema.Redacted for secret-like configuration values
6
+ description: Use Config.Redacted or Schema.Redacted for secret-like configuration values
7
7
  glob: '**/*.{ts,tsx}'
8
8
  detector: ast
9
9
  rule:
10
10
  any:
11
- - pattern: Config.string($KEY)
12
- - pattern: Config.nonEmptyString($KEY)
11
+ - pattern: Config.String($KEY)
12
+ - pattern: Config.NonEmptyString($KEY)
13
13
  - all:
14
14
  - kind: pair
15
15
  - has:
@@ -33,18 +33,18 @@ suggestSkills:
33
33
 
34
34
  ```haskell
35
35
  -- Transformation
36
- Config.string secretKey :: String -- easy to log accidentally
37
- Config.redacted secretKey :: Redacted String -- hidden from logs/toString
36
+ Config.String secretKey :: String -- easy to log accidentally
37
+ Config.Redacted secretKey :: Redacted String -- hidden from logs/toString
38
38
  ```
39
39
 
40
40
  ```typescript
41
41
  // Bad
42
- const apiKey = Config.string('API_KEY');
43
- const token = Config.nonEmptyString('GITHUB_TOKEN');
42
+ const apiKey = Config.String('API_KEY');
43
+ const token = Config.NonEmptyString('GITHUB_TOKEN');
44
44
 
45
45
  // Good
46
- const apiKey = Config.redacted('API_KEY');
47
- const token = Config.redacted('GITHUB_TOKEN');
46
+ const apiKey = Config.Redacted('API_KEY');
47
+ const token = Config.Redacted('GITHUB_TOKEN');
48
48
  ```
49
49
 
50
50
  For structured config schemas, wrap secret-like string fields in `Schema.Redacted`:
@@ -67,4 +67,4 @@ const AppConfig = Config.schema(
67
67
  );
68
68
  ```
69
69
 
70
- Secrets should remain redacted from the moment they enter the program. Use `Config.redacted` for primitive config values and `Schema.Redacted(Schema.String)` for schema-based config fields.
70
+ Secrets should remain redacted from the moment they enter the program. Use `Config.Redacted` for primitive config values and `Schema.Redacted(Schema.String)` for schema-based config fields.
@@ -79,7 +79,7 @@ Effect.forEach xs f opts -- concurrency intent explicit
79
79
  Effect.forEach(items, processItem);
80
80
  Effect.all(tasks);
81
81
  Effect.validate(inputs, validateInput, { discard: true });
82
- Effect.all(tasks, { concurrency: 'inherit' }); // removed in beta.102
82
+ Effect.all(tasks, { concurrency: 'inherit' }); // unsupported ambient policy
83
83
 
84
84
  // Good
85
85
  Effect.forEach(items, processItem, { concurrency: 1 });
@@ -231,7 +231,7 @@ const program = Effect.gen(function* () {
231
231
  timeToLive: '1 hour' // optional TTL
232
232
  });
233
233
 
234
- // chat is a `Chat.Persisted` — same API as Chat.Service but auto-saves
234
+ // chat is a `Chat.Persisted` — same API as Chat.Chat but auto-saves
235
235
  const response = yield* chat
236
236
  .generateText({
237
237
  prompt: 'Hello!'
@@ -245,7 +245,7 @@ const program = Effect.gen(function* () {
245
245
  });
246
246
  ```
247
247
 
248
- The `Persisted` interface extends `Chat.Service` with:
248
+ The `Persisted` interface extends `Chat.Chat` with:
249
249
 
250
250
  - `id: string` — the chat identifier in the store
251
251
  - `save: Effect<void, AiError | PersistenceError>` — manual save trigger
@@ -94,7 +94,13 @@ const manualTools = LanguageModel.generateText({
94
94
  });
95
95
  ```
96
96
 
97
- When `disableToolCallResolution: true`, tool-call `params` are preserved in the schema's **encoded** representation instead of being decoded and then returned. The response type reflects this as `GenerateTextResponse<Tools, true>` and `Response.ToolCallParts<Tools, true>`; streaming uses `Response.StreamPart<Tools, true>`. This matters for transformations such as `Schema.NumberFromString`: manual calls contain the wire value `{ count: '3' }`, not `{ count: 3 }`.
97
+ With `disableToolCallResolution: true`, tool-call `params` retain the schema's
98
+ **encoded** representation. Response generics use the mode `"encoded"`, for
99
+ example `GenerateTextResponse<Tools, "encoded">` and
100
+ `Response.StreamPart<Tools, "encoded">`. Automatic resolution uses `"opaque"`
101
+ parameters (`unknown`), because invalid calls can also appear in responses.
102
+ Only successfully decoded handler inputs have the decoded parameter type.
103
+ For `Schema.NumberFromString`, manual calls contain `{ count: '3' }`.
98
104
 
99
105
  Pass those encoded params directly to `toolkit.handle(name, params, toolCallId)` when resolving manually. `Toolkit.handle` now accepts `Tool.ParametersEncoded<T>` and performs the decode before the handler receives `Tool.Parameters<T>`.
100
106
 
@@ -104,7 +110,7 @@ Pass those encoded params directly to `toolkit.handle(name, params, toolCallId)`
104
110
  const response = yield* LanguageModel.generateText({ prompt: '...' });
105
111
 
106
112
  response.text; // string - concatenated text content
107
- response.toolCalls; // decoded params normally; encoded params when resolution is disabled
113
+ response.toolCalls; // opaque params normally; encoded params when resolution is disabled
108
114
  response.toolResults; // Array<ToolResultParts> - tool outputs
109
115
  response.finishReason; // "stop" | "length" | "content-filter" | "tool-calls" | "error" | "pause" | "unknown" | "other"
110
116
  response.usage; // Usage object with nested structure, e.g. response.usage.outputTokens.total
@@ -297,6 +303,32 @@ const robust = LanguageModel.generateText({
297
303
  );
298
304
  ```
299
305
 
306
+ ## Batched classification, rating, and probability
307
+
308
+ Use `DecisionModel` for structured judgments over one schema-encoded input.
309
+ Define named decisions once and send them in one call; input encoding services
310
+ remain explicit and provider/validation failures use `AiError`. Classification
311
+ requires at least two labels, rating requires at least two distinct ordered
312
+ levels, and a definition requires at least one decision.
313
+
314
+ <!-- typecheck -->
315
+ ```ts
316
+ import * as Schema from 'effect/Schema';
317
+ import { Decision, DecisionModel } from 'effect/unstable/ai';
318
+
319
+ class Ticket extends Schema.Class<Ticket>('Ticket')({ body: Schema.String }) {}
320
+ const triage = Decision.make({
321
+ input: Ticket,
322
+ decisions: {
323
+ team: Decision.classify({
324
+ instructions: 'Choose the team responsible for this request',
325
+ criteria: { billing: 'Payments and invoices', support: 'Product support' }
326
+ })
327
+ }
328
+ });
329
+ const result = DecisionModel.decide(triage, { input: new Ticket({ body: 'Invoice question' }) });
330
+ ```
331
+
300
332
  ## Type Extraction Utilities
301
333
 
302
334
  ```typescript
@@ -308,8 +340,8 @@ type MyError = LanguageModel.ExtractError<typeof options>;
308
340
  // Extract service requirements from options
309
341
  type MyRequirements = LanguageModel.ExtractServices<typeof options>;
310
342
 
311
- // true only when options has literal disableToolCallResolution: true
312
- type EncodedParams = LanguageModel.ExtractEncodedToolParameters<typeof options>;
343
+ // "encoded" for literal disableToolCallResolution: true; otherwise "opaque"
344
+ type ParametersMode = LanguageModel.ExtractToolParametersMode<typeof options>;
313
345
 
314
346
  // Inferred based on:
315
347
  // - toolkit: Toolkit.WithHandler<Tools> → Tool.HandlerError<Tools> ∈ E
@@ -516,7 +516,7 @@ const program = Effect.gen(function* () {
516
516
 
517
517
  ## Provider-Specific Options
518
518
 
519
- ### OpenAI Responses explicit cache breakpoints (rc.112)
519
+ ### OpenAI Responses explicit cache breakpoints
520
520
 
521
521
  With `@effect/ai-openai` loaded, system-message and text-part options accept
522
522
  `openai.promptCacheBreakpoint`. This requires GPT-5.6 or later; earlier models
@@ -100,7 +100,7 @@ import { FetchHttpClient } from 'effect/unstable/http';
100
100
 
101
101
  // Client layer (reusable across models)
102
102
  const AnthropicClientLayer = AnthropicClient.layerConfig({
103
- apiKey: Config.redacted('ANTHROPIC_API_KEY')
103
+ apiKey: Config.Redacted('ANTHROPIC_API_KEY')
104
104
  }).pipe(Layer.provide(FetchHttpClient.layer));
105
105
 
106
106
  // Option A: model() — returns Model.Model (preferred)
@@ -130,7 +130,7 @@ import { Config, Layer } from 'effect';
130
130
  import { FetchHttpClient } from 'effect/unstable/http';
131
131
 
132
132
  const OpenAiClientLayer = OpenAiClient.layerConfig({
133
- apiKey: Config.redacted('OPENAI_API_KEY')
133
+ apiKey: Config.Redacted('OPENAI_API_KEY')
134
134
  }).pipe(Layer.provide(FetchHttpClient.layer));
135
135
 
136
136
  // model() constructor (preferred)
@@ -186,7 +186,7 @@ import { Config, Layer } from 'effect';
186
186
  import { FetchHttpClient, HttpClient, HttpClientRequest } from 'effect/unstable/http';
187
187
 
188
188
  const CompatibleClientLayer = OpenAiClient.layerConfig({
189
- apiKey: Config.redacted('OPENAI_COMPAT_API_KEY'),
189
+ apiKey: Config.Redacted('OPENAI_COMPAT_API_KEY'),
190
190
  apiUrl: Config.succeed('https://my-provider.example.com/v1')
191
191
  }).pipe(Layer.provide(FetchHttpClient.layer));
192
192
 
@@ -211,7 +211,7 @@ import { Config, Layer } from 'effect';
211
211
  import { FetchHttpClient } from 'effect/unstable/http';
212
212
 
213
213
  const OpenRouterClientLayer = OpenRouterClient.layerConfig({
214
- apiKey: Config.redacted('OPENROUTER_API_KEY')
214
+ apiKey: Config.Redacted('OPENROUTER_API_KEY')
215
215
  }).pipe(Layer.provide(FetchHttpClient.layer));
216
216
 
217
217
  // model() constructor — use provider-prefixed model IDs
@@ -420,7 +420,7 @@ export class AiWriter extends Context.Service<
420
420
 
421
421
  ## Custom Error Wrapping
422
422
 
423
- In rc.112, `AiError.AuthenticationError` accepts an optional `description` and
423
+ `AiError.AuthenticationError` accepts an optional `description` and
424
424
  appends it after the kind-based remediation message. Anthropic, OpenAI,
425
425
  OpenAI-compatible, and OpenRouter adapters propagate provider error text from
426
426
  401/403 responses. Preserve this reason rather than replacing it with a generic
@@ -496,11 +496,11 @@ import { FetchHttpClient } from 'effect/unstable/http';
496
496
  // ---------------------------------------------------------------------------
497
497
 
498
498
  const AnthropicClientLayer = AnthropicClient.layerConfig({
499
- apiKey: Config.redacted('ANTHROPIC_API_KEY')
499
+ apiKey: Config.Redacted('ANTHROPIC_API_KEY')
500
500
  }).pipe(Layer.provide(FetchHttpClient.layer));
501
501
 
502
502
  const OpenAiClientLayer = OpenAiClient.layerConfig({
503
- apiKey: Config.redacted('OPENAI_API_KEY')
503
+ apiKey: Config.Redacted('OPENAI_API_KEY')
504
504
  }).pipe(Layer.provide(FetchHttpClient.layer));
505
505
 
506
506
  // ---------------------------------------------------------------------------
@@ -619,15 +619,15 @@ Effect.runPromise(program.pipe(Effect.provide(AiWriter.layer)));
619
619
  // WRONG: Hardcoded API keys
620
620
  AnthropicClient.layerConfig({ apiKey: 'sk-...' });
621
621
 
622
- // RIGHT: Config.redacted for secrets
623
- AnthropicClient.layerConfig({ apiKey: Config.redacted('ANTHROPIC_API_KEY') });
622
+ // RIGHT: Config.Redacted for secrets
623
+ AnthropicClient.layerConfig({ apiKey: Config.Redacted('ANTHROPIC_API_KEY') });
624
624
 
625
625
  // WRONG: Missing FetchHttpClient layer
626
- AnthropicClient.layerConfig({ apiKey: Config.redacted('KEY') });
626
+ AnthropicClient.layerConfig({ apiKey: Config.Redacted('KEY') });
627
627
  // Will fail at runtime — providers require an HttpClient
628
628
 
629
629
  // RIGHT: Always provide an HTTP client layer
630
- AnthropicClient.layerConfig({ apiKey: Config.redacted('KEY') }).pipe(
630
+ AnthropicClient.layerConfig({ apiKey: Config.Redacted('KEY') }).pipe(
631
631
  Layer.provide(FetchHttpClient.layer)
632
632
  );
633
633
 
@@ -653,7 +653,7 @@ import { BedrockClient } from '@effect/ai-amazon-bedrock'; // Does NOT exist
653
653
 
654
654
  ## Quality Checklist
655
655
 
656
- - [ ] Use `Config.redacted` for API keys (never hardcode)
656
+ - [ ] Use `Config.Redacted` for API keys (never hardcode)
657
657
  - [ ] Provide `FetchHttpClient.layer` to all client layers
658
658
  - [ ] Use `.model()` constructor for `ExecutionPlan` and `Effect.provide`
659
659
  - [ ] Use `ExecutionPlan` for multi-provider fallback with retry
@@ -674,6 +674,17 @@ import { BedrockClient } from '@effect/ai-amazon-bedrock'; // Does NOT exist
674
674
 
675
675
  ## References
676
676
 
677
+ Provider-neutral structured decisions use `Decision` / `DecisionModel` from
678
+ `effect/unstable/ai`. `OpenRouterDecisionModel` targets OpenRouter's alpha Decisions
679
+ API; `@effect/ai-typesafe` supplies a provider for TypeSafe System One. Custom
680
+ OpenRouter client implementations include `createDecisions`.
681
+
682
+ Use each branded service's same-name type (`LanguageModel.LanguageModel`,
683
+ `EmbeddingModel.EmbeddingModel`, `Chat.Chat`) and its exported TypeId for custom
684
+ implementations. Prefer provided constructors; do not hard-code marker strings.
685
+ OpenAI web-search sources are a discriminated union: narrow `type` before reading
686
+ `url` or `name`. Provider-executed tool failures remain failure results.
687
+
677
688
  - `packages/ai/anthropic/src/AnthropicLanguageModel.ts`
678
689
  - `packages/ai/openai/src/OpenAiLanguageModel.ts`
679
690
  - `packages/ai/openrouter/src/OpenRouterLanguageModel.ts`
@@ -208,6 +208,19 @@ type Error = Tool.Failure<typeof FindUser>;
208
208
 
209
209
  **Key Pattern: failureMode**
210
210
 
211
+ Parameter validation follows the tool's `failureMode`. Failure results include
212
+ `Tool.ExecutionFailure` for framework failures (AI errors, denial, interruption)
213
+ as well as declared user errors. Select result codecs using `isFailure` and
214
+ `Tool.failureResultSchema(tool)`; preserve `encodedResult` when storing or
215
+ replaying response parts. A failure with `Schema.NumberFromString` is stored as
216
+ a string, even when the success schema stores a number. Validation errors do not
217
+ expose a `toolParams` field.
218
+
219
+ `Toolkit.handle` accepts parse options for parameter decoding. Returned failures
220
+ carry `failureOrigin`, also available through the `Toolkit.FailureOrigin` cause
221
+ annotation and `Tool.FailureOrigin` type. Keep validation, declared handler, and
222
+ internal failures distinct when presenting or reporting them.
223
+
211
224
  - `"error"` (default): Failures go to Effect error channel
212
225
  - `"return"`: Failures returned as tool result (captured, not thrown)
213
226
 
@@ -20,7 +20,7 @@ Use this skill when building React (or Atom-based) frontends that consume an exi
20
20
  For the underlying RPC definitions, see the `effect-rpc-cluster` skill.
21
21
  For Atom fundamentals (`Atom.make`, `family`, `keepAlive`, `AsyncResult`, hydration), see the `effect-atom-state` skill.
22
22
 
23
- The underlying RPC protocols require `codecFor` in rc.112. Built-in protocol
23
+ The underlying RPC protocols require `codecFor`. Built-in protocol
24
24
  layers supply it; forward it when implementing a custom transport. You can pair
25
25
  `RpcSerialization.layerSchemaBinary` on client/server without changing query or
26
26
  mutation call sites; see `effect-rpc-client` for frame limits and compatibility.
@@ -29,6 +29,12 @@ type is `string`. This is distinct from the client's `{ discard: true }` option.
29
29
 
30
30
  ## Effect Source Reference
31
31
 
32
+ Query and mutation errors include client middleware errors. AtomHttpApi stream
33
+ successes retain transport, decoding, and SSE failures in the stream error channel;
34
+ handle them when consuming the stream. An explicit zero `timeToLive` disables
35
+ default idle retention, so unmount/remount may dispose and refetch. Omission uses
36
+ the registry default. HttpApi calls accept per-call `sseOptions`.
37
+
32
38
  - `packages/effect/src/unstable/reactivity/AtomRpc.ts` — the whole API (~270 lines)
33
39
  - `packages/effect/test/reactivity/AtomRpc.test.ts` — minimal usage + serialization test
34
40
  - `packages/effect/src/unstable/reactivity/AtomHttpApi.ts` — the cousin pattern for `HttpApi` (same idea, same options)