opencode-effect-enforcer 0.2.8 → 0.4.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 (74) hide show
  1. package/README.md +6 -5
  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 +6 -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-decision-model/SKILL.md +301 -0
  28. package/skills/effect-ai-decision-model/openrouter.md +70 -0
  29. package/skills/effect-ai-language-model/SKILL.md +53 -21
  30. package/skills/effect-ai-prompt/SKILL.md +25 -14
  31. package/skills/effect-ai-provider/SKILL.md +53 -22
  32. package/skills/effect-ai-streaming/SKILL.md +27 -12
  33. package/skills/effect-ai-tool/SKILL.md +37 -28
  34. package/skills/effect-atom-rpc/SKILL.md +57 -36
  35. package/skills/effect-atom-state/SKILL.md +57 -19
  36. package/skills/effect-batching/SKILL.md +5 -3
  37. package/skills/effect-cache/SKILL.md +19 -7
  38. package/skills/effect-cli/SKILL.md +17 -8
  39. package/skills/effect-command-executor/SKILL.md +115 -64
  40. package/skills/effect-concurrency-testing/SKILL.md +26 -6
  41. package/skills/effect-config/SKILL.md +53 -2
  42. package/skills/effect-context-witness/SKILL.md +6 -6
  43. package/skills/effect-domain-modeling/SKILL.md +8 -1
  44. package/skills/effect-error-handling/SKILL.md +15 -2
  45. package/skills/effect-fiber/SKILL.md +20 -25
  46. package/skills/effect-filesystem/SKILL.md +69 -57
  47. package/skills/effect-http-api/SKILL.md +72 -22
  48. package/skills/effect-http-client/SKILL.md +25 -21
  49. package/skills/effect-http-server/SKILL.md +51 -21
  50. package/skills/effect-incremental-migration/SKILL.md +17 -8
  51. package/skills/effect-layer-design/SKILL.md +8 -0
  52. package/skills/effect-managed-runtime/SKILL.md +6 -0
  53. package/skills/effect-mcp-server/SKILL.md +64 -24
  54. package/skills/effect-observability/SKILL.md +61 -15
  55. package/skills/effect-parallelization/SKILL.md +24 -7
  56. package/skills/effect-path/SKILL.md +8 -2
  57. package/skills/effect-platform-abstraction/SKILL.md +88 -66
  58. package/skills/effect-platform-layers/SKILL.md +68 -67
  59. package/skills/effect-pubsub-event-bus/SKILL.md +56 -60
  60. package/skills/effect-react-composition/SKILL.md +19 -6
  61. package/skills/effect-rpc-api/SKILL.md +24 -24
  62. package/skills/effect-rpc-client/SKILL.md +33 -28
  63. package/skills/effect-rpc-cluster/SKILL.md +122 -78
  64. package/skills/effect-rpc-server/SKILL.md +56 -20
  65. package/skills/effect-scheduling/SKILL.md +29 -1
  66. package/skills/effect-schema-composition/SKILL.md +31 -13
  67. package/skills/effect-schema-v4/SKILL.md +94 -10
  68. package/skills/effect-scope/SKILL.md +13 -5
  69. package/skills/effect-service-implementation/SKILL.md +1 -1
  70. package/skills/effect-socket/SKILL.md +52 -8
  71. package/skills/effect-sql/SKILL.md +67 -33
  72. package/skills/effect-stream/SKILL.md +50 -5
  73. package/skills/effect-testing/SKILL.md +91 -2
  74. package/skills/effect-workflow/SKILL.md +76 -39
@@ -3,25 +3,27 @@ name: effect-workflow
3
3
  description: Build durable workflows with Effect using Workflow, Activity, DurableClock, DurableDeferred, and DurableQueue for execution that survives restarts, supports compensation (saga pattern), and integrates with Effect Cluster for distribution.
4
4
  ---
5
5
 
6
- You are an Effect TypeScript expert specializing in durable workflow execution using the `effect/unstable/workflow` module.
6
+ You are an Effect TypeScript expert specializing in durable workflow execution using the `effect/workflow` module.
7
7
 
8
8
  ## Effect Source Reference
9
9
 
10
10
  The Effect v4 source is available at `~/.local/share/opencode/repos/github.com/Effect-TS/effect@main/`.
11
- Browse and read files there directly to look up APIs, types, and implementations.
11
+ Inspect the `effect@4.0.0` tag for this skill; main may be newer. Keep `effect`
12
+ and all companion packages on the same release.
12
13
 
13
14
  Key source files:
14
15
 
15
- - `packages/effect/src/unstable/workflow/Workflow.ts` — Workflow definition, compensation, annotations
16
- - `packages/effect/src/unstable/workflow/Activity.ts` — Activity definition, retry, idempotency
17
- - `packages/effect/src/unstable/workflow/WorkflowEngine.ts` — Engine service, in-memory layer, encoded interface
18
- - `packages/effect/src/unstable/workflow/DurableClock.ts` — Durable sleep/timers
19
- - `packages/effect/src/unstable/workflow/DurableDeferred.ts` — Durable signal/wait, tokens, done/succeed/fail
20
- - `packages/effect/src/unstable/workflow/DurableQueue.ts` — Durable queue handing work to persisted background workers
16
+ - `packages/effect/src/workflow/Workflow.ts` — Workflow definition, compensation, annotations
17
+ - `packages/effect/src/workflow/Activity.ts` — Activity definition, retry, idempotency
18
+ - `packages/effect/src/workflow/WorkflowEngine.ts` — Engine service, in-memory layer, encoded interface
19
+ - `packages/effect/src/workflow/DurableClock.ts` — Durable sleep/timers
20
+ - `packages/effect/src/workflow/DurableDeferred.ts` — Durable signal/wait, tokens, done/succeed/fail
21
+ - `packages/effect/src/workflow/DurableQueue.ts` — Durable queue handing work to persisted background workers
21
22
 
22
23
  ## IMPORTANT: Unstable API
23
24
 
24
- The workflow module lives under `effect/unstable/workflow`. APIs may change between versions. All imports use this path:
25
+ The workflow module lives under `effect/workflow` and remains `@stability unstable`:
26
+ its APIs may break in minor releases. All imports use this path:
25
27
 
26
28
  ```ts
27
29
  import {
@@ -31,7 +33,7 @@ import {
31
33
  DurableClock,
32
34
  DurableDeferred,
33
35
  DurableQueue
34
- } from 'effect/unstable/workflow';
36
+ } from 'effect/workflow';
35
37
  ```
36
38
 
37
39
  ## Core Concepts
@@ -51,19 +53,18 @@ DurableQueue → hand work to a persisted background worker and await its resu
51
53
 
52
54
  ## Workflow Definition
53
55
 
54
- Use `Workflow.make` to define a workflow. Every workflow has:
56
+ Use `Workflow.make(tag, options)` to define a workflow. Every workflow has:
55
57
 
56
- - A unique `name`
58
+ - A unique tag (the first argument, exposed as `_tag`)
57
59
  - A `payload` schema (struct fields or Schema)
58
60
  - An `idempotencyKey` function that produces a deterministic execution ID from the payload
59
61
  - Optional `success` and `error` schemas (default `Schema.Void` / `Schema.Never`)
60
62
 
61
63
  ```ts
62
- import { Workflow } from 'effect/unstable/workflow';
64
+ import { Workflow } from 'effect/workflow';
63
65
  import { Schema } from 'effect';
64
66
 
65
- const SendEmail = Workflow.make({
66
- name: 'SendEmail',
67
+ const SendEmail = Workflow.make('SendEmail', {
67
68
  payload: {
68
69
  to: Schema.String,
69
70
  subject: Schema.String,
@@ -77,6 +78,21 @@ const SendEmail = Workflow.make({
77
78
 
78
79
  ### Registering a Workflow Handler
79
80
 
81
+ Class syntax is also supported for shared workflow definitions:
82
+
83
+ <!-- typecheck -->
84
+ ```ts
85
+ import * as Schema from 'effect/Schema';
86
+ import { Workflow } from 'effect/workflow';
87
+
88
+ class SendReceipt extends Workflow.make('SendReceipt', {
89
+ payload: { orderId: Schema.String },
90
+ idempotencyKey: ({ orderId }) => orderId
91
+ }) {}
92
+
93
+ const receiptExecutionId = SendReceipt.executionId({ orderId: 'order-1' });
94
+ ```
95
+
80
96
  Use `workflow.toLayer(handler)` to register the execution logic. The handler receives the decoded payload and execution ID:
81
97
 
82
98
  ```ts
@@ -131,7 +147,17 @@ ID (`Schema.String`). Consumers can persist that ID to poll/resume the workflow.
131
147
  Update generated client response types and tests that expected `void`; ordinary
132
148
  RPC `discard: true` call options still discard the response and cannot return it.
133
149
 
134
- The execution ID is computed as a hash of `"${name}-${idempotencyKey(payload)}"`. This means executing the same workflow with the same payload is idempotent — it returns the existing execution rather than starting a new one.
150
+ The execution ID hashes the length-prefixed input
151
+ `"${name.length}:${name}:${idempotencyKey(payload)}"`. This avoids ambiguous
152
+ tag/key concatenations. The same workflow tag and idempotency key identify the
153
+ same execution, even if other payload fields differ; choose the key accordingly.
154
+ Use `workflow.executionId(payload)` instead of duplicating the hash algorithm.
155
+
156
+ This differs from rc.116's `"${name}-${key}"` hash: recomputing from an old payload
157
+ produces a different ID. Preserve stored execution IDs for polling, resuming,
158
+ interrupting, and deferred tokens for existing executions. Coordinate the
159
+ identity transition before resubmitting old payloads, which would otherwise
160
+ start distinct executions.
135
161
 
136
162
  ```ts
137
163
  const id =
@@ -148,15 +174,15 @@ const id =
148
174
  Activities are the **atomic units of work** inside a workflow. Their results are persisted by the engine, so on replay they return the cached result without re-executing.
149
175
 
150
176
  ```ts
151
- import { Activity } from 'effect/unstable/workflow';
177
+ import { Activity } from 'effect/workflow';
152
178
 
153
179
  const validateRecipient = Activity.make({
154
180
  name: 'ValidateRecipient',
155
181
  success: Schema.Struct({ valid: Schema.Boolean }),
156
182
  error: Schema.String,
157
183
  execute: Effect.gen(function* () {
158
- // This code runs at most once per workflow execution
159
- // (unless the activity itself fails and is retried)
184
+ // A persisted result is reused on replay. Interrupted/crashed work
185
+ // can rerun before its result is recorded: external writes need idempotency.
160
186
  const result = yield* checkEmailService(payload.to);
161
187
  return { valid: result.isValid };
162
188
  })
@@ -180,7 +206,7 @@ const handler = SendEmail.toLayer((payload, executionId) =>
180
206
  Use `Activity.retry` to retry an effect within an activity. The engine tracks the attempt count automatically and exposes it via `Activity.CurrentAttempt`:
181
207
 
182
208
  ```ts
183
- import { Activity } from 'effect/unstable/workflow';
209
+ import { Activity } from 'effect/workflow';
184
210
 
185
211
  const sendWithRetry = Activity.make({
186
212
  name: 'SendWithRetry',
@@ -263,7 +289,7 @@ const result =
263
289
  `DurableClock.sleep` creates a timer that survives process restarts. Short sleeps (<=60s by default) run in-memory as regular activities. Longer sleeps are scheduled through the engine.
264
290
 
265
291
  ```ts
266
- import { DurableClock } from 'effect/unstable/workflow';
292
+ import { DurableClock } from 'effect/workflow';
267
293
 
268
294
  // Inside a workflow handler:
269
295
  yield*
@@ -290,7 +316,7 @@ Under the hood, a DurableClock creates a `DurableDeferred` and the engine schedu
290
316
  ### Creating and Awaiting
291
317
 
292
318
  ```ts
293
- import { DurableDeferred } from 'effect/unstable/workflow';
319
+ import { DurableDeferred } from 'effect/workflow';
294
320
  import { Schema, Exit } from 'effect';
295
321
 
296
322
  // Define the deferred with typed schemas
@@ -305,6 +331,15 @@ const confirmation = yield* DurableDeferred.await(PaymentConfirmation);
305
331
 
306
332
  The engine registers the awaited deferred before reading it. Completing that deferred while the workflow run is still active preempts a run parked on it, retains the pending result, and replays so the completion is observed. The in-memory engine follows the same behavior as `ClusterWorkflowEngine`; this closes the race where a live completion could otherwise be missed until a later retry.
307
333
 
334
+ The cluster engine retries failed run resets before acknowledging deferred
335
+ completion. It resets the suspended run conditionally against the reply it read,
336
+ so a stale concurrent resume cannot erase a newer completed reply. Custom
337
+ `MessageStorage.clearReplies(requestId, { expectedReplyId })` implementations
338
+ must atomically compare the latest reply ID before clearing; mismatch is a
339
+ successful no-op. `Sharding.reset` returning `false` means reset failed, not that
340
+ the expected-reply comparison mismatched. See `effect-rpc-cluster` for storage
341
+ claim-reset semantics.
342
+
308
343
  ### Completing from Outside
309
344
 
310
345
  External code (e.g., a webhook handler) completes the deferred using a **token**:
@@ -395,8 +430,8 @@ const result =
395
430
  `DurableQueue` lets a workflow delegate a unit of work to a **persisted background worker** and suspend until the worker records a result. The workflow calls `process` to enqueue an item and wait; a separate worker created with `worker` / `makeWorker` takes the item, runs the handler, and completes the waiting workflow through a `DurableDeferred` token.
396
431
 
397
432
  ```ts
398
- import { DurableQueue, Workflow, WorkflowEngine } from 'effect/unstable/workflow';
399
- import { PersistedQueue } from 'effect/unstable/persistence';
433
+ import { DurableQueue, Workflow, WorkflowEngine } from 'effect/workflow';
434
+ import { PersistedQueue } from 'effect/persistence';
400
435
  import { Effect, Layer, Schema } from 'effect';
401
436
  ```
402
437
 
@@ -514,7 +549,7 @@ The compensation function receives:
514
549
  Controls whether defects (unexpected errors) are captured in the workflow result. Default: `true`.
515
550
 
516
551
  ```ts
517
- const MyWorkflow = Workflow.make({ ... }).annotate(Workflow.CaptureDefects, false)
552
+ const MyWorkflow = Workflow.make('MyWorkflow', { ... }).annotate(Workflow.CaptureDefects, false)
518
553
  ```
519
554
 
520
555
  ### SuspendOnFailure
@@ -522,7 +557,7 @@ const MyWorkflow = Workflow.make({ ... }).annotate(Workflow.CaptureDefects, fals
522
557
  When `true`, the workflow suspends on any error instead of failing. You can then manually resume it:
523
558
 
524
559
  ```ts
525
- const MyWorkflow = Workflow.make({ ... }).annotate(Workflow.SuspendOnFailure, true)
560
+ const MyWorkflow = Workflow.make('MyWorkflow', { ... }).annotate(Workflow.SuspendOnFailure, true)
526
561
 
527
562
  // Later, after fixing the issue:
528
563
  yield* MyWorkflow.resume(executionId)
@@ -557,7 +592,7 @@ The `WorkflowEngine` is a service that orchestrates workflow execution. It handl
557
592
  For testing and local development, use the in-memory engine:
558
593
 
559
594
  ```ts
560
- import { WorkflowEngine } from 'effect/unstable/workflow';
595
+ import { WorkflowEngine } from 'effect/workflow';
561
596
 
562
597
  const TestLayer = Layer.mergeAll(
563
598
  SendEmailLive
@@ -569,11 +604,11 @@ const TestLayer = Layer.mergeAll(
569
604
 
570
605
  ### Production Engine — `ClusterWorkflowEngine.layer`
571
606
 
572
- For production, use `ClusterWorkflowEngine.layer` from `effect/unstable/cluster`. It wires the workflow engine into `Sharding` + `MessageStorage` so executions, activities, and durable signals survive restarts and can be distributed across runners:
607
+ For production, use `ClusterWorkflowEngine.layer` from `effect/cluster`. It wires the workflow engine into `Sharding` + `MessageStorage` so executions, activities, and durable signals survive restarts and can be distributed across runners:
573
608
 
574
609
  ```ts
575
610
  import { Layer } from 'effect';
576
- import { ClusterWorkflowEngine } from 'effect/unstable/cluster';
611
+ import { ClusterWorkflowEngine } from 'effect/cluster';
577
612
 
578
613
  const WorkflowsLayer = Layer.mergeAll(
579
614
  SendEmailLive,
@@ -585,14 +620,18 @@ const WorkflowsLayer = Layer.mergeAll(
585
620
 
586
621
  The `ClusterWorkflowEngine` requires `Sharding | MessageStorage` in context; both come from any of the cluster runtime bundles. See the `effect-rpc-cluster` skill for cluster setup.
587
622
 
623
+ Workflow tags are unique registration identities. Reusing a tag with a different
624
+ workflow definition logs a warning (including payload shapes) and retains the
625
+ existing definition; it is not a schema replacement mechanism.
626
+
588
627
  #### Workflow shard-group routing
589
628
 
590
- A workflow can be annotated with `ClusterSchema.ShardGroup` (from `effect/unstable/cluster`), exactly like an entity:
629
+ A workflow can be annotated with `ClusterSchema.ShardGroup` (from `effect/cluster`), exactly like an entity:
591
630
 
592
631
  ```ts
593
- import { ClusterSchema } from 'effect/unstable/cluster';
632
+ import { ClusterSchema } from 'effect/cluster';
594
633
 
595
- const OrderWorkflow = Workflow.make({ /* ... */ })
634
+ const OrderWorkflow = Workflow.make('OrderWorkflow', { /* ... */ })
596
635
  .annotate(ClusterSchema.ShardGroup, () => 'workflow');
597
636
  ```
598
637
 
@@ -633,8 +672,7 @@ The `Encoded` interface works with raw/encoded values (JSON-safe), while the `Wo
633
672
  When a workflow suspends (waiting for an activity or deferred), the engine retries with `Schedule.min([Schedule.exponential("200 millis", 1.5), Schedule.spaced("30 seconds")])`: exponential backoff from 200ms, capped at 30s. (`Schedule.andThen` / `andThenResult` were renamed to `Schedule.concat` / `concatResult`, and the old `Schedule.either` cap pattern is now `Schedule.min`.) Override per-workflow:
634
673
 
635
674
  ```ts
636
- const MyWorkflow = Workflow.make({
637
- name: 'MyWorkflow',
675
+ const MyWorkflow = Workflow.make('MyWorkflow', {
638
676
  payload: { id: Schema.String },
639
677
  idempotencyKey: (p) => p.id,
640
678
  suspendedRetrySchedule: Schedule.spaced('5 seconds')
@@ -651,7 +689,7 @@ import {
651
689
  DurableDeferred,
652
690
  Workflow,
653
691
  WorkflowEngine
654
- } from 'effect/unstable/workflow';
692
+ } from 'effect/workflow';
655
693
 
656
694
  // --- Schemas ---
657
695
 
@@ -700,8 +738,7 @@ const shipOrder = Activity.make({
700
738
 
701
739
  // --- Workflow ---
702
740
 
703
- const ProcessOrder = Workflow.make({
704
- name: 'ProcessOrder',
741
+ const ProcessOrder = Workflow.make('ProcessOrder', {
705
742
  payload: {
706
743
  orderId: Schema.String,
707
744
  items: Schema.Array(Schema.String)
@@ -806,10 +843,10 @@ Compensation finalizers are only registered for top-level effects in the workflo
806
843
 
807
844
  ## Integration with Effect Cluster
808
845
 
809
- For production durability and distribution, swap `WorkflowEngine.layerMemory` for `ClusterWorkflowEngine.layer` (from `effect/unstable/cluster`):
846
+ For production durability and distribution, swap `WorkflowEngine.layerMemory` for `ClusterWorkflowEngine.layer` (from `effect/cluster`):
810
847
 
811
848
  ```ts
812
- import { ClusterWorkflowEngine } from 'effect/unstable/cluster';
849
+ import { ClusterWorkflowEngine } from 'effect/cluster';
813
850
  import { NodeClusterSocket } from '@effect/platform-node';
814
851
 
815
852
  const MainLive = Layer.mergeAll(SendEmailLive, ProcessOrderLive).pipe(