opencode-effect-enforcer 0.2.5 → 0.2.8

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 (52) 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 +28 -311
  5. package/guidance/progressive-disclosure-guidance.md +5 -19
  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-arr-sort.md +1 -1
  11. package/patterns/prefer-redacted-config.md +10 -10
  12. package/patterns/prefer-schema-class.md +1 -1
  13. package/patterns/require-effect-concurrency.md +1 -1
  14. package/skills/effect-ai-chat/SKILL.md +2 -2
  15. package/skills/effect-ai-language-model/SKILL.md +36 -4
  16. package/skills/effect-ai-prompt/SKILL.md +1 -1
  17. package/skills/effect-ai-provider/SKILL.md +23 -12
  18. package/skills/effect-ai-tool/SKILL.md +13 -0
  19. package/skills/effect-atom-rpc/SKILL.md +7 -1
  20. package/skills/effect-atom-state/SKILL.md +8 -2
  21. package/skills/effect-cache/SKILL.md +10 -1
  22. package/skills/effect-cli/SKILL.md +105 -94
  23. package/skills/effect-command-executor/SKILL.md +7 -1
  24. package/skills/effect-config/SKILL.md +67 -44
  25. package/skills/effect-domain-modeling/SKILL.md +3 -3
  26. package/skills/effect-error-handling/SKILL.md +2 -2
  27. package/skills/effect-fiber/SKILL.md +2 -2
  28. package/skills/effect-filesystem/SKILL.md +34 -4
  29. package/skills/effect-http-api/SKILL.md +17 -2
  30. package/skills/effect-http-client/SKILL.md +11 -2
  31. package/skills/effect-http-server/SKILL.md +28 -11
  32. package/skills/effect-layer-design/SKILL.md +6 -2
  33. package/skills/effect-mcp-server/SKILL.md +21 -4
  34. package/skills/effect-observability/SKILL.md +2 -2
  35. package/skills/effect-optics/SKILL.md +1 -1
  36. package/skills/effect-parallelization/SKILL.md +2 -2
  37. package/skills/effect-pattern-matching/SKILL.md +1 -1
  38. package/skills/effect-platform-abstraction/SKILL.md +3 -3
  39. package/skills/effect-rpc-api/SKILL.md +3 -3
  40. package/skills/effect-rpc-client/SKILL.md +14 -13
  41. package/skills/effect-rpc-cluster/SKILL.md +15 -12
  42. package/skills/effect-rpc-server/SKILL.md +11 -13
  43. package/skills/effect-scheduling/SKILL.md +7 -0
  44. package/skills/effect-schema-composition/SKILL.md +12 -4
  45. package/skills/effect-schema-v4/SKILL.md +75 -20
  46. package/skills/effect-scope/SKILL.md +4 -4
  47. package/skills/effect-socket/SKILL.md +161 -658
  48. package/skills/effect-sql/SKILL.md +50 -12
  49. package/skills/effect-stream/SKILL.md +19 -24
  50. package/skills/effect-testing/SKILL.md +35 -22
  51. package/skills/effect-workflow/SKILL.md +13 -2
  52. package/src/guidance.ts +0 -1
@@ -560,7 +560,7 @@ Effect SQL uses driver-specific packages that provide `SqlClient` layers.
560
560
 
561
561
  | Package | Database |
562
562
  | ------------------------- | ------------------------------------ |
563
- | `@effect/sql-pg` | PostgreSQL (via `pg`) |
563
+ | `@effect/sql-pg` | PostgreSQL (native Effect protocol client) |
564
564
  | `@effect/sql-pglite` | Embedded PostgreSQL/PGlite |
565
565
  | `@effect/sql-mysql2` | MySQL (via `mysql2`) |
566
566
  | `@effect/sql-sqlite-node` | SQLite (via `better-sqlite3`) |
@@ -591,14 +591,14 @@ const DatabaseLayer = PgClient.layer({
591
591
 
592
592
  // From Config (reads from environment/config provider)
593
593
  const DatabaseLayerConfig = PgClient.layerConfig({
594
- url: Config.redacted('DATABASE_URL')
594
+ url: Config.Redacted('DATABASE_URL')
595
595
  });
596
596
 
597
597
  // The layer provides both PgClient and SqlClient services
598
598
  const program = Effect.gen(function* () {
599
599
  const sql = yield* SqlClient; // generic interface
600
600
  // or
601
- const pg = yield* PgClient; // pg-specific (has .json(), .listen(), .notify())
601
+ const pg = yield* PgClient.PgClient; // PostgreSQL-specific service
602
602
  });
603
603
 
604
604
  const main = program.pipe(Effect.provide(DatabaseLayer));
@@ -606,10 +606,35 @@ const main = program.pipe(Effect.provide(DatabaseLayer));
606
606
 
607
607
  ### PgClient-Specific Features
608
608
 
609
- ### Low-level PostgreSQL codecs (rc.112)
610
-
611
- `@effect/sql-pg` adds public `PgProtocol`, `PgTypes`, and `PgAuth` modules.
612
- These are building blocks for protocol adapters; `PgClient` still uses `pg`.
609
+ ### Native PostgreSQL client and codecs
610
+
611
+ `PgClient` uses `PgConnection` and `PgPool` for native binary queries, prepared
612
+ statements, pipelining, streaming, cancellation, and notifications. Use `make`
613
+ for a pool or `makeClient` for one connection. `types` accepts a `PgTypes.Registry`.
614
+ Bind JSON explicitly with `sql.json`, and send one statement per query string.
615
+ Named preparation is enabled by default; set `prepare: false` for incompatible
616
+ poolers, or use statement-level `unprepared` / `valuesUnprepared`.
617
+
618
+ Decode rows according to the actual driver boundary: `int8` is `bigint`, `date`
619
+ is a string, timestamps are `Date` (millisecond precision), and `bytea` is
620
+ `Uint8Array`. Infinite/out-of-range timestamps decode to invalid Dates; validate
621
+ before constructing domain instants. Timestamp encoders accept Date or epoch
622
+ milliseconds; invalid Date encoding fails. A Date binds as `timestamptz`, so use
623
+ UTC session time or `PgTypes.timestamp(value)` for UTC fields in a timestamp column.
624
+ `executeRaw` returns `PgConnection.Result`.
625
+
626
+ Unregistered OIDs decode as UTF-8 text, suitable for scalar enum labels but not
627
+ arbitrary binary types. Register custom scalar/array codecs through
628
+ `PgTypes.makeRegistry().register(elementOid, codec, { arrayOid })` and pass the
629
+ registry as `types`. Decode failures close the connection and fail pending work.
630
+ `inet` uses `IpInterface`; `cidr` uses `IpNetwork` and rejects host bits.
631
+
632
+ Use structured `startupParameters` and opaque `startupOptions` for per-connection
633
+ session defaults. Passwords may be infallible, service-free Effects reevaluated
634
+ per physical connection. `sslmode=prefer` and `allow` try TLS first and fall back
635
+ only when the server declines SSLRequest; handshake/certificate failures remain
636
+ fatal. Explicit SSL options take precedence. `maxMessageSize` defaults to 16 MiB;
637
+ when multiplexing is enabled, `multiplexConcurrency` defaults to 32.
613
638
 
614
639
  - `PgProtocol`: PostgreSQL 3.0 frontend encoding and incremental backend-frame
615
640
  parsing. Stateful parser failures are terminal and synchronous; lift a parser
@@ -629,16 +654,29 @@ keep the Effect-family package versions aligned when upgrading those adapters.
629
654
  ### PgClient JSON and Notifications
630
655
 
631
656
  ```ts
632
- const pg = yield* PgClient;
657
+ const pg = yield* PgClient.PgClient;
633
658
 
634
659
  // JSON parameter helper
635
660
  sql`INSERT INTO data ${sql.insert({ metadata: pg.json({ key: 'value' }) })}`;
636
661
 
637
662
  // LISTEN/NOTIFY
638
- const notifications = pg.listen('my_channel'); // Stream<string, SqlError>
663
+ const notifications = yield* pg.listen('my_channel'); // scoped Dequeue<string, SqlError>
639
664
  yield* pg.notify('my_channel', 'hello');
665
+ const messages = Stream.fromQueue(notifications);
640
666
  ```
641
667
 
668
+ Listener acquisition completes after registration, giving an explicit readiness
669
+ boundary. PostgreSQL listener queues carry `SqlError` when the connection fails;
670
+ retry a scoped effect that reacquires the listener, not the failed queue. Intentional
671
+ scope closure interrupts consumers. PGlite also returns a scoped notification dequeue.
672
+
673
+ `SqlModel.makeResolvers().insert` requires both input encoding and row decoding
674
+ services; `insertVoid` needs only input encoding services. D1 statement `.raw`
675
+ returns the complete native result; read `.results` for rows. Durable Object SQL
676
+ transactions support nested child rollback but no explicit async operations inside
677
+ the transaction. Use `Statement.SpanPropagationEnabled` to opt into driver span
678
+ parenting under `sql.execute`.
679
+
642
680
  ### PGlite Setup
643
681
 
644
682
  Use `@effect/sql-pglite` for embedded PostgreSQL-compatible databases backed by `@electric-sql/pglite`. Its layer provides both the PGlite-specific service and the generic `SqlClient` service.
@@ -654,7 +692,7 @@ const PgliteLayer = PgliteClient.layer({
654
692
  });
655
693
 
656
694
  const PgliteLayerConfig = PgliteClient.layerConfig({
657
- dataDir: Config.string('PGLITE_DATA_DIR')
695
+ dataDir: Config.String('PGLITE_DATA_DIR')
658
696
  });
659
697
 
660
698
  const program = Effect.gen(function* () {
@@ -662,7 +700,7 @@ const program = Effect.gen(function* () {
662
700
  const pglite = yield* PgliteClient.PgliteClient;
663
701
 
664
702
  yield* sql`INSERT INTO data ${sql.insert({ metadata: pglite.json({ key: 'value' }) })}`;
665
- const notifications = pglite.listen('my_channel');
703
+ const notifications = yield* pglite.listen('my_channel');
666
704
  yield* pglite.notify('my_channel', 'hello');
667
705
  yield* pglite.refreshArrayTypes;
668
706
  const snapshot = yield* pglite.dumpDataDir('gzip');
@@ -704,7 +742,7 @@ yield*
704
742
  Stream.runCollect
705
743
  );
706
744
 
707
- // Chunked streaming (driver-dependent, e.g. pg uses cursor with 128-row chunks)
745
+ // Streaming uses the driver's bounded result batching.
708
746
  ```
709
747
 
710
748
  ## Error Handling
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: effect-stream
3
- description: Build effectful pull-based streaming pipelines with Effect Stream — creation, transformation, consumption, NDJSON/Msgpack encoding, concurrency, resource safety. Use when working with values produced over time, paginated APIs, event listeners, or streaming I/O.
3
+ description: Build effectful pull-based streaming pipelines with Effect Stream — creation, transformation, consumption, NDJSON/SchemaBinary encoding, concurrency, resource safety. Use when working with values produced over time, paginated APIs, event listeners, or streaming I/O.
4
4
  ---
5
5
 
6
6
  You are an Effect TypeScript expert specializing in pull-based streaming with `Stream`, `Sink`, and `Channel`.
@@ -15,7 +15,7 @@ Reference this for:
15
15
  - Stream constructors and combinators (`packages/effect/src/Stream.ts`)
16
16
  - Creating streams from various sources (`ai-docs/src/02_stream/10_creating-streams.ts`)
17
17
  - Consuming and transforming streams (`ai-docs/src/02_stream/20_consuming-streams.ts`)
18
- - Encoding/decoding with NDJSON and Msgpack (`ai-docs/src/02_stream/30_encoding.ts`)
18
+ - Encoding/decoding with NDJSON and SchemaBinary (`packages/effect/src/unstable/encoding/`)
19
19
 
20
20
  ## Core Model
21
21
 
@@ -25,7 +25,7 @@ Use a stream when values are naturally many-valued and ordered over time. For on
25
25
 
26
26
  ```ts
27
27
  import { Effect, Schedule, Schema, Sink, Stream } from 'effect';
28
- import { Ndjson, Msgpack } from 'effect/unstable/encoding';
28
+ import { Ndjson, SchemaBinary } from 'effect/unstable/encoding';
29
29
  ```
30
30
 
31
31
  For Node.js readable streams:
@@ -249,11 +249,11 @@ Stream.make('US', 'CA', 'NZ').pipe(
249
249
  ```ts
250
250
  // Running accumulator — emits initial state plus each accumulated state
251
251
  // Output: [0, 1, 3, 6]
252
- Stream.make(1, 2, 3).pipe(Stream.scan(0, (acc, n) => acc + n));
252
+ Stream.make(1, 2, 3).pipe(Stream.scan(() => 0, (acc, n) => acc + n));
253
253
 
254
254
  // Effectful variant
255
255
  Stream.make(1, 2, 3).pipe(
256
- Stream.scanEffect(0, (acc, n) => Effect.succeed(acc + n))
256
+ Stream.scanEffect(() => 0, (acc, n) => Effect.succeed(acc + n))
257
257
  );
258
258
  ```
259
259
 
@@ -355,15 +355,15 @@ stream.pipe(
355
355
 
356
356
  ---
357
357
 
358
- ## 4. Encoding & Decoding (NDJSON / Msgpack / SchemaBinary)
358
+ ## 4. Encoding & Decoding (NDJSON / SchemaBinary)
359
359
 
360
360
  Use `Stream.pipeThroughChannel` with codec channels from `effect/unstable/encoding`.
361
361
 
362
362
  ```ts
363
- import { Ndjson, Msgpack } from 'effect/unstable/encoding';
363
+ import { Ndjson, SchemaBinary } from 'effect/unstable/encoding';
364
364
  ```
365
365
 
366
- ### Schema-derived binary frames (rc.112)
366
+ ### Schema-derived binary frames
367
367
 
368
368
  <!-- typecheck -->
369
369
  ```ts
@@ -446,20 +446,8 @@ objectStream.pipe(Stream.pipeThroughChannel(Ndjson.encode())); // objects → Ui
446
446
  Stream.pipeThroughChannel(Ndjson.decodeString({ ignoreEmptyLines: true }));
447
447
  ```
448
448
 
449
- ### Msgpack
450
-
451
- Same API shape — replace `Ndjson` with `Msgpack`. Note that `Msgpack.decodeSchema(schema)` is curried: it returns a factory you must invoke (`()`) to get the `Channel` value passed to `Stream.pipeThroughChannel`, exactly like the NDJSON schema helpers.
452
-
453
- ```ts
454
- const decoder = Msgpack.decodeSchema(
455
- Schema.Struct({
456
- id: Schema.Number,
457
- name: Schema.String
458
- })
459
- )();
460
-
461
- binaryStream.pipe(Stream.pipeThroughChannel(decoder), Stream.runCollect);
462
- ```
449
+ Use SchemaBinary for a schema-derived binary contract and NDJSON for JSON
450
+ interoperability. Both peers must agree on the wire format and schema.
463
451
 
464
452
  ### Realistic pipeline: decode → transform → re-encode
465
453
 
@@ -545,6 +533,13 @@ stream.pipe(Stream.orElseSucceed((error) => defaultValue));
545
533
 
546
534
  Prefer natural backpressure. Add `Stream.buffer` only to deliberately decouple producer and consumer: `"suspend"` backpressures when full, `"dropping"` drops new values, and `"sliding"` drops old values to retain the latest. Avoid `capacity: "unbounded"` unless growth is bounded elsewhere and documented.
547
535
 
536
+ `Stream.partition` returns `[passes, fails]` and accepts `{ capacity }` (default
537
+ 16). Consume both sides concurrently to avoid blocking a full partition.
538
+ `Stream.mapBoth` takes `onElement` / `onError`. `catchTags` rejects unknown tag
539
+ keys; `catchDefect` handles defects separately from typed failures/interruption.
540
+ `tapDefect`, `tapErrorTag`, and `unwrapReason` provide targeted observation/recovery.
541
+ Use `Channel.runDrain` to consume channel output and return its completion value.
542
+
548
543
  ### merge
549
544
 
550
545
  Interleave elements from two streams concurrently in arrival order.
@@ -625,7 +620,7 @@ Because the producer starts immediately, subscribers that attach after the sourc
625
620
 
626
621
  ### broadcastN
627
622
 
628
- Fixed-fanout multicast (added in beta.68). Produces a tuple of `n` streams; the source starts only **after all `n` downstream streams have been subscribed**, so every consumer sees the full sequence without needing `replay`. If a downstream stream is interrupted, it unsubscribes and no longer contributes backpressure.
623
+ Fixed-fanout multicast produces a tuple of `n` streams; the source starts only **after all `n` downstream streams have been subscribed**, so every consumer sees the full sequence without needing `replay`. If a downstream stream is interrupted, it unsubscribes and no longer contributes backpressure.
629
624
 
630
625
  ```ts
631
626
  Effect.scoped(
@@ -679,7 +674,7 @@ const safeStream = Stream.scoped(
679
674
  // Stream<string, never, never> — Scope is eliminated
680
675
  ```
681
676
 
682
- As of beta.69, `Stream.scoped` provides its managed scope to the **pull effects** as well — including effects created by `Stream.fromEffect` and by sequential `Stream.mapEffect`. So `Effect.acquireRelease` finalizers used inside those pulls run when the stream completes, not leaked until the outer program ends.
677
+ `Stream.scoped` provides its managed scope to **pull effects**, including effects created by `Stream.fromEffect` and sequential `Stream.mapEffect`. `Effect.acquireRelease` finalizers inside those pulls run when the stream completes.
683
678
 
684
679
  ### unwrap
685
680
 
@@ -21,11 +21,16 @@ Reference this for:
21
21
 
22
22
  ## Framework Selection
23
23
 
24
- Keep `@effect/vitest` aligned with the Effect release. The rc.112 adapter's
25
- Vitest peer range is `>=4.1.0 <5.0.0`; do not install it into a Vitest 3 project
24
+ Keep `@effect/vitest` aligned with the Effect release. Its
25
+ Vitest peer range is `>=5.0.0 <6.0.0`; do not install it into a Vitest 3 or 4 project
26
26
  without migrating that project's test framework. Plain compiler/inventory tests
27
27
  can keep using their existing Vitest runner without the adapter.
28
28
 
29
+ The adapter requires Node.js `^22.12.0 || ^24.0.0 || >=26.0.0`. Use
30
+ `{ concurrent: false }` for sequential suites, including named `layer` / `it.layer`
31
+ suites. Await asynchronous assertions and finalizers. Define custom matchers
32
+ through `vitest.Matchers` and import reporter types from `vitest/node`.
33
+
29
34
  **CRITICAL**: Choose the correct testing framework based on the code being tested.
30
35
 
31
36
  ### Use @effect/vitest for Effect Code
@@ -367,7 +372,7 @@ layer(DatabaseLayer)((it) => {
367
372
  });
368
373
  ```
369
374
 
370
- A nested `it.layer` suite **reuses** the parent suite's memoized layer allocations rather than rebuilding them. As of beta.67, each nested suite also **forks its own memo map**, so layers allocated locally inside one nested suite are isolated from sibling nested suites and are released independently when that suite finishes. The practical effect: shared parent layers (e.g. `DatabaseLayer`) are built once and reused, while sibling-local allocations do not leak across siblings even in concurrent suites.
375
+ A nested `it.layer` suite **reuses** the parent's memoized allocations and **forks its own memo map**. Parent layers are built once; local allocations are isolated from sibling suites and released when their suite finishes, including concurrent suites.
371
376
 
372
377
  ### Excluding Test Services
373
378
 
@@ -604,19 +609,21 @@ it.effect('should fail with specific error', () =>
604
609
  ### Using it.prop for Pure Properties
605
610
 
606
611
  ```typescript
607
- import { FastCheck } from 'effect/testing';
612
+ import * as Schema from 'effect/Schema';
608
613
  import { it } from '@effect/vitest';
609
614
 
610
615
  it.prop(
611
616
  'addition is commutative',
612
- [FastCheck.integer(), FastCheck.integer()],
617
+ [Schema.Int, Schema.Int],
613
618
  ([a, b]) => a + b === b + a
614
619
  );
615
620
 
616
621
  // With object syntax
617
622
  it.prop(
618
623
  'multiplication distributes',
619
- { a: FastCheck.integer(), b: FastCheck.integer(), c: FastCheck.integer() },
624
+ { a: Schema.Int.check(Schema.isBetween({ minimum: -100, maximum: 100 })),
625
+ b: Schema.Int.check(Schema.isBetween({ minimum: -100, maximum: 100 })),
626
+ c: Schema.Int.check(Schema.isBetween({ minimum: -100, maximum: 100 })) },
620
627
  ({ a, b, c }) => a * (b + c) === a * b + a * c
621
628
  );
622
629
  ```
@@ -626,7 +633,7 @@ it.prop(
626
633
  ```typescript
627
634
  import { it } from '@effect/vitest';
628
635
  import { Effect, Context } from 'effect';
629
- import { FastCheck } from 'effect/testing';
636
+ import * as Schema from 'effect/Schema';
630
637
 
631
638
  class Database extends Context.Service<
632
639
  Database,
@@ -638,7 +645,7 @@ class Database extends Context.Service<
638
645
 
639
646
  it.effect.prop(
640
647
  'database operations are idempotent',
641
- [FastCheck.string(), FastCheck.integer()],
648
+ [Schema.String, Schema.Int],
642
649
  ([key, value]) =>
643
650
  Effect.gen(function* () {
644
651
  const db = yield* Database;
@@ -674,35 +681,41 @@ it.effect.prop('user validation works', { user: User }, ({ user }) =>
674
681
  );
675
682
  ```
676
683
 
677
- `it.prop` and `it.effect.prop` accept schemas directly and derive their arbitraries internally. For manual use, beta.106 consolidated derivation into `Schema.toArbitrary(schema)`, which returns a factory that must receive the fast-check module:
684
+ `it.prop` and `it.effect.prop` accept schemas and native Arbitraries, composed
685
+ with `Arbitrary.all`. For manual sampling use the interruptible native runner:
678
686
 
679
687
  ```typescript
680
- import { Schema } from 'effect';
681
- import { FastCheck } from 'effect/testing';
688
+ import { Arbitrary } from 'effect/unstable/arbitrary';
682
689
 
683
- const UserArbitrary = Schema.toArbitrary(User)(FastCheck);
684
- const samples = FastCheck.sample(UserArbitrary, 10);
690
+ const userArbitrary = Arbitrary.schema(User);
691
+ const samples = Arbitrary.sampleEffect(userArbitrary, { count: 10, seed: 'users' });
685
692
  ```
686
693
 
687
- `Schema.toArbitraryLazy` and arbitrary derivation reports no longer exist.
694
+ Generation is from the decoded schema Type. Compose with `map`, `flatMap`,
695
+ `filter`, `filterMap`, `all`, and `array(item, { minLength, maxLength })`.
696
+ Prefer constructive schemas over filters that exhaust the discard budget.
697
+ `checkEffect` returns `Passed`, `Falsified`, `Exhausted`, or an invalid-replay
698
+ result; inspect the outcome rather than treating completion as success. Preserve
699
+ seeds, replay tokens, and important failing inputs. Replay paths can change when
700
+ the generator/shrinker changes. In the Vitest adapter, thrown exceptions, typed
701
+ failures, and defects are shrinkable falsifications; interruption stays interruption.
688
702
 
689
- ### Configuring FastCheck
703
+ ### Configuring native property checks
690
704
 
691
705
  ```typescript
692
706
  import { it } from '@effect/vitest';
693
707
  import { Effect } from 'effect';
694
- import { FastCheck } from 'effect/testing';
708
+ import * as Schema from 'effect/Schema';
695
709
 
696
710
  it.effect.prop(
697
711
  'property test',
698
- [FastCheck.integer()],
712
+ [Schema.Int],
699
713
  ([n]) => Effect.succeed(n >= 0 || n < 0),
700
714
  {
701
715
  timeout: 10000,
702
- fastCheck: {
703
- numRuns: 1000,
704
- seed: 42,
705
- verbose: true
716
+ arbitrary: {
717
+ runs: 1000,
718
+ seed: 42
706
719
  }
707
720
  }
708
721
  );
@@ -1155,7 +1168,7 @@ Key differences from `@effect/vitest`:
1155
1168
  - Layer composition uses `Layer.provideMerge` here because the harness intentionally exposes both application and test services; do not use it blindly
1156
1169
  - Keep `effect` as the default; use `live` only when real time or live runtime services are under test
1157
1170
 
1158
- **`TestClock` without an ambient `Scope` (beta.70):** earlier betas required a surrounding `Scope` for `TestClock.adjust` to advance time. As of beta.70, `TestClock.layer()` works when provided directly to a program run with `Effect.runPromise` (no ambient `Scope`) — which is exactly what the harness above relies on. `testEffect(...).effect(...)` merges `TestClock.layer()` into the layer stack and runs with `Effect.runPromise`, and `TestClock.adjust` still drives time correctly.
1171
+ `TestClock.layer()` works when provided directly to a program executed with `Effect.runPromise`, without an ambient Scope. The harness above merges that layer into the test stack so `TestClock.adjust` controls time.
1159
1172
 
1160
1173
  ## Testing Checklist
1161
1174
 
@@ -125,7 +125,7 @@ yield* SendEmail.resume(executionId);
125
125
 
126
126
  ### Deterministic Execution ID
127
127
 
128
- As of rc.112, generated `WorkflowProxy.toRpcGroup` `<Name>Discard` RPCs and
128
+ Generated `WorkflowProxy.toRpcGroup` `<Name>Discard` RPCs and
129
129
  `WorkflowProxy.toHttpApiGroup` discard HTTP endpoints also return the execution
130
130
  ID (`Schema.String`). Consumers can persist that ID to poll/resume the workflow.
131
131
  Update generated client response types and tests that expected `void`; ordinary
@@ -192,7 +192,7 @@ const sendWithRetry = Activity.make({
192
192
 
193
193
  `Activity.retry` accepts the same options as `Effect.retry` *minus* `schedule` — the activity owns the attempt counter, so retries are attempt-based (`times`, `until`, `while`, `catch`, etc.) rather than schedule-based.
194
194
 
195
- Because an `Activity` *is* an `Effect`, pipe it directly through compensation/retry combinators — there is no `.asEffect()` method (`Effect.Yieldable` was removed in beta.66):
195
+ An `Activity` *is* an `Effect`; pipe it directly through compensation/retry combinators:
196
196
 
197
197
  ```ts
198
198
  yield* SomeActivity.pipe(
@@ -460,6 +460,17 @@ const AppLayer = Layer.mergeAll(MyWorkflowLayer, ApiWorker).pipe(
460
460
 
461
461
  Delivery is **at least once** per the backing `PersistedQueue`, so worker handlers must be **idempotent** and tolerate retries, duplicate observations, and worker restarts.
462
462
 
463
+ Persisted queue processing policy belongs on `PersistedQueue.make`: `maxAttempts`
464
+ defaults to 10 and `retrySchedule` determines redelivery delay. Attempts count on
465
+ claim; exhausted and undecodable items are dead-lettered. Distinguish this policy
466
+ from retrying a transient failure to offer an item.
467
+
468
+ `DurableDeferred.into` requires the schema encoding services needed to persist the
469
+ exit. Cluster handoff interrupts an abandoned workflow run attempt without
470
+ persisting a business failure or running compensations; replay continues under
471
+ the next owner. Keep durable completion and activity state under the workflow
472
+ engine rather than adding local retry/wakeup bookkeeping.
473
+
463
474
  ## Compensation (Saga Pattern)
464
475
 
465
476
  `Workflow.withCompensation` registers rollback logic that runs if the **entire workflow** fails. This enables the saga pattern for distributed transactions.
package/src/guidance.ts CHANGED
@@ -60,7 +60,6 @@ export const withPluginPolicy = (guidance: string): string =>
60
60
  [
61
61
  guidance,
62
62
  "OpenCode Effect Enforcer policy:",
63
- "- The bundled effect-* skills are available through OpenCode's skill tool. Load at least four relevant skills before planning or writing Effect code.",
64
63
  "- Treat post-write pattern feedback as an immediate review request. Fix valid findings before continuing.",
65
64
  "- Pattern findings are advisory: explain intentional exceptions or false positives instead of changing correct code."
66
65
  ].join("\n\n---\n\n")