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
@@ -8,13 +8,15 @@ You are an Effect TypeScript expert specializing in building MCP (Model Context
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 `git show effect@4.0.0:<path>` in that checkout for this baseline; main
12
+ may be ahead. Keep Effect-family package versions aligned. The MCP/AI APIs remain
13
+ `@stability unstable` and can change incompatibly in minor releases.
12
14
 
13
15
  Reference these files:
14
16
 
15
17
  - `packages/effect/MCP.md` — primary MCP guide with full examples
16
- - `packages/effect/src/unstable/ai/McpServer.ts` — server implementation and API
17
- - `packages/effect/src/unstable/ai/McpSchema.ts` — schema types, param helper, error classes
18
+ - `packages/effect/src/ai/McpServer.ts` — server implementation and API
19
+ - `packages/effect/src/ai/McpSchema.ts` — schema types, param helper, error classes
18
20
 
19
21
  ## What is MCP
20
22
 
@@ -25,7 +27,7 @@ Model Context Protocol (MCP) is a standard protocol for LLM tool integration. It
25
27
  ```typescript
26
28
  import { Cause, Context, Effect, Layer, Logger } from 'effect';
27
29
  import { Schema } from 'effect';
28
- import { McpProtocol, McpServer, McpSchema, Tool, Toolkit } from 'effect/unstable/ai';
30
+ import { McpProtocol, McpServer, McpSchema, Tool, Toolkit } from 'effect/ai';
29
31
  ```
30
32
 
31
33
  For platform-specific transports:
@@ -72,7 +74,8 @@ const protocols = [
72
74
  - `2024-11-05` and `2025-03-26` are compatibility revisions.
73
75
  - `2025-06-18` supports form elicitation.
74
76
  - `2025-11-25` adds sampling with tools, independently advertised form/URL elicitation modes, descriptor icons, and elicitation-complete notifications.
75
- - `2026-07-28` is available as `McpProtocol.v2026_07_28`.
77
+ - `2026-07-28` is available as `McpProtocol.v2026_07_28`; it uses stateless
78
+ request-scoped execution rather than the older initialized-session lifecycle.
76
79
  - Duplicate versions or an empty protocol declaration fail layer construction with `Cause.IllegalArgumentError`.
77
80
  - `v2024_11_05` over `layerHttp` uses Effect's single-endpoint Streamable HTTP compatibility transport. It does not recreate the historical two-endpoint HTTP+SSE transport, GET SSE, event resumption, session expiry, or client session termination.
78
81
 
@@ -84,34 +87,60 @@ Server options accept `instructions` for initialization/discovery. Prompt
84
87
  registration accepts `title`; callbacks receive decoded prompt parameters.
85
88
 
86
89
  `Tool.Strict` controls MCP input schema generation and argument validation.
87
- Strict dynamic tools require Effect schemas; raw JSON Schema is rejected at
88
- registration. Non-strict tools may use identified input schemas. Invalid arguments
89
- produce `InvalidParams` before protocol 2025-11-25 and `isError: true` on newer
90
- protocols. Declared handler failures return `isError: true` without
91
- `structuredContent`; only JSON objects are valid structured content.
92
- Declared failures are distinct from internal diagnostics. Defects and encoding
93
- failures are logged/reported while client-facing messages stay generic.
90
+ Strict tools reject unknown keys; non-strict tools ignore them during decoding.
91
+ Both report all missing/invalid fields together (`errors: 'all'`), including
92
+ unknown keys for strict tools. Strict dynamic tools require Effect schemas;
93
+ raw JSON Schema cannot supply runtime strict validation and is rejected at registration.
94
+
95
+ Parameters must encode to a JSON Schema with `type: 'object'` at the root. Use
96
+ `Schema.Class` or `Schema.Struct`, or omit parameters/use `Tool.EmptyParams` for
97
+ parameterless tools. Identified object schemas are supported: the adapter inlines
98
+ top-level references for input/output schemas and form elicitation, retaining
99
+ referenced definitions. It does not turn primitive result types into objects.
100
+
101
+ Tool failure projection depends on the negotiated protocol:
102
+
103
+ | Failure | 2024-11-05 through 2025-06-18 | 2025-11-25 and 2026-07-28 |
104
+ | --- | --- | --- |
105
+ | Invalid arguments | `-32602 Invalid params` | `isError: true` tool result |
106
+ | Internal handler/encoding failure | `-32603 Internal error` | `isError: true` tool result |
107
+ | Declared tool failure | `isError: true` tool result | `isError: true` tool result |
108
+
109
+ Declared failures omit `structuredContent`; an error with an empty `message`
110
+ uses its encoded failure value rather than returning empty text. Internal defects
111
+ and encoding failures are reported internally with generic client-facing messages.
112
+
113
+ Success projection is also version-specific. The 2024-11-05/2025-03-26 adapters
114
+ omit structured output. The 2025-06-18/2025-11-25 adapters expose object-root
115
+ output schemas and object-valued `structuredContent`, omitting primitive/array
116
+ structured values. The 2026-07-28 adapter supports JSON-valued structured output.
117
+ When a protocol omits string structured content, a single text block that exactly
118
+ mirrors its JSON encoding is unquoted to plain text; other text blocks are preserved.
94
119
 
95
120
  ### Defining Tools
96
121
 
97
122
  Tools are defined with `Tool.make` specifying a name, description, parameter schemas, and success schema:
98
123
 
124
+ <!-- typecheck -->
99
125
  ```typescript
126
+ import * as Schema from 'effect/Schema';
127
+ import { Tool } from 'effect/ai';
128
+
100
129
  const GreetTool = Tool.make('GreetTool', {
101
130
  description: 'Generate a greeting message',
102
- parameters: {
131
+ parameters: Schema.Struct({
103
132
  name: Schema.String,
104
133
  style: Schema.Union([
105
134
  Schema.Literal('formal'),
106
135
  Schema.Literal('casual')
107
136
  ])
108
- },
137
+ }),
109
138
  success: Schema.String
110
139
  });
111
140
 
112
141
  const CalculatorTool = Tool.make('CalculatorTool', {
113
142
  description: 'Perform basic arithmetic',
114
- parameters: {
143
+ parameters: Schema.Struct({
115
144
  operation: Schema.Union([
116
145
  Schema.Literal('add'),
117
146
  Schema.Literal('subtract'),
@@ -120,16 +149,16 @@ const CalculatorTool = Tool.make('CalculatorTool', {
120
149
  ]),
121
150
  a: Schema.Number,
122
151
  b: Schema.Number
123
- },
152
+ }),
124
153
  success: Schema.Number
125
154
  });
126
155
 
127
156
  const NotifyTool = Tool.make('NotifyTool', {
128
157
  description: 'Send an optional notification',
129
- parameters: {
158
+ parameters: Schema.Struct({
130
159
  message: Schema.String,
131
160
  channel: Schema.optionalKey(Schema.String)
132
- },
161
+ }),
133
162
  success: Schema.Void
134
163
  });
135
164
  ```
@@ -184,6 +213,8 @@ Effect tool annotations are emitted as MCP tool hints:
184
213
  - `Tool.Destructive` → `destructiveHint`, default `true`
185
214
  - `Tool.Idempotent` → `idempotentHint`, default `false`
186
215
  - `Tool.OpenWorld` → `openWorldHint`, default `true`
216
+ - `Tool.Title` → annotation title; 2025-06-18 and 2025-11-25 preserve it in
217
+ `tools/list` alongside the descriptor's top-level title and behavioral hints
187
218
 
188
219
  These hints help clients decide how to present tools, but they are not authorization decisions. Always enforce access control in your server handlers.
189
220
 
@@ -282,6 +313,8 @@ const result = McpServer.elicit({
282
313
  - Returns `Effect<S["Type"], ElicitationDeclined, McpServerClient>`
283
314
  - Handle `ElicitationDeclined` with `catchTag` for fallback behavior
284
315
  - If the user cancels, the effect is interrupted
316
+ - Identified object schemas (including named `Schema.Class` values) are accepted;
317
+ the requested form still has to fit the negotiated revision's supported field shapes.
285
318
  - The negotiated client must advertise form elicitation. In `2025-11-25`, an empty `elicitation` capability is treated as form support; explicit `elicitation.form` and `elicitation.url` capabilities are otherwise gated independently.
286
319
 
287
320
  URL elicitation is a `2025-11-25` reverse-client operation. Use the scoped client facade, then notify that specific client when the external flow completes:
@@ -380,6 +413,9 @@ For stdio transport, route logs to stderr with `Logger.LogToStderr`. Stdout is
380
413
  reserved for protocol messages. `Logger.consolePretty` controls formatting;
381
414
  the context reference controls its destination.
382
415
 
416
+ With a stateful protocol configured, pre-initialization stdio `ping` returns an
417
+ empty result without creating a session, even when a stateless adapter is listed first.
418
+
383
419
  ### HTTP Transport
384
420
 
385
421
  For web-based MCP servers using Streamable HTTP:
@@ -396,7 +432,9 @@ McpServer.layerHttp({
396
432
  - Requires `HttpRouter.HttpRouter` in the context
397
433
  - Implements single-endpoint Streamable HTTP with JSON-RPC
398
434
  - The `path` parameter sets the HTTP endpoint path
399
- - Non-`initialize` HTTP requests with no session id return `400`; an unknown `Mcp-Session-Id` returns `404`. Clients must keep and resend the session id from initialization.
435
+ - For stateful revisions, non-`initialize` HTTP requests with no session id return
436
+ `400`; an unknown `Mcp-Session-Id` returns `404`. Keep and resend the session id
437
+ from initialization. The 2026-07-28 stateless adapter uses request metadata instead.
400
438
 
401
439
  ### Type signatures
402
440
 
@@ -442,7 +480,7 @@ Use `McpSchema.EnabledWhen` to conditionally list prompts, resources, resource t
442
480
  ```typescript
443
481
  import { Context, Effect, Layer } from 'effect';
444
482
  import { Schema } from 'effect';
445
- import { McpSchema, McpServer, Tool } from 'effect/unstable/ai';
483
+ import { McpSchema, McpServer, Tool } from 'effect/ai';
446
484
 
447
485
  const requiresRoots = Context.make(
448
486
  McpSchema.EnabledWhen,
@@ -481,12 +519,12 @@ const WorkspaceResource = Layer.effectDiscard(
481
519
  import { NodeRuntime, NodeStdio } from '@effect/platform-node';
482
520
  import { Effect, Layer, Logger } from 'effect';
483
521
  import { Schema } from 'effect';
484
- import { McpProtocol, McpSchema, McpServer, Tool, Toolkit } from 'effect/unstable/ai';
522
+ import { McpProtocol, McpSchema, McpServer, Tool, Toolkit } from 'effect/ai';
485
523
 
486
524
  // --- Tools ---
487
525
  const GreetTool = Tool.make('GreetTool', {
488
526
  description: 'Generate a greeting',
489
- parameters: { name: Schema.String },
527
+ parameters: Schema.Struct({ name: Schema.String }),
490
528
  success: Schema.String
491
529
  });
492
530
 
@@ -555,7 +593,7 @@ Layer.launch(ServerLayer).pipe(NodeRuntime.runMain);
555
593
  ```typescript
556
594
  const FetchTool = Tool.make('FetchData', {
557
595
  description: 'Fetch data from database',
558
- parameters: { id: Schema.String },
596
+ parameters: Schema.Struct({ id: Schema.String }),
559
597
  success: Schema.String
560
598
  });
561
599
 
@@ -617,7 +655,9 @@ const ConfigResource = McpServer.resource({
617
655
  2. **Provide transport last** — `Layer.provide(McpServer.layerStdio(...))` or `Layer.provide(McpServer.layerHttp(...))`
618
656
  3. **stderr for stdio** — Never log to stdout when using stdio transport
619
657
  4. **Toolkit pattern** — `McpServer.toolkit(tk).pipe(Layer.provideMerge(tk.toLayer({...})))` is the canonical pattern
620
- 5. **Schema for parameters** — All tool parameters and resource template params use Effect Schema
658
+ 5. **Schema for parameters** — `Tool.make` takes a schema with an encoded object
659
+ root, not a raw field record. `McpServer.prompt` still takes a field record;
660
+ resource template parameters take individual codecs.
621
661
  6. **`McpSchema.param`** — Use for resource URI template parameters with automatic string codec
622
662
  7. **`Effect.fn`** — Use for resource template content handlers that receive multiple arguments
623
663
  8. **Launch with `Layer.launch`** — The server runs as a long-lived layer: `Layer.launch(ServerLayer).pipe(NodeRuntime.runMain)`
@@ -8,14 +8,17 @@ You are an Effect TypeScript expert specializing in observability — structured
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 `git show effect@4.0.0:<path>` there for this baseline; main may be ahead.
12
+ Keep `effect` and `@effect/*` packages on the same version. The OTLP modules and
13
+ `@effect/opentelemetry` APIs are tagged `@stability unstable` and may break in
14
+ minor releases; the shorter `effect/observability` import path is not a stability guarantee.
12
15
 
13
16
  Reference this for:
14
17
 
15
18
  - `Logger` module: `packages/effect/src/Logger.ts`
16
19
  - `Tracer` module: `packages/effect/src/Tracer.ts`
17
20
  - `Metric` module: `packages/effect/src/Metric.ts`
18
- - OTLP export: `packages/effect/src/unstable/observability/`
21
+ - OTLP export: `packages/effect/src/observability/`
19
22
  - Observability examples: `ai-docs/src/08_observability/`
20
23
 
21
24
  ---
@@ -178,7 +181,7 @@ const BatchedLoggerLayer = Logger.layer([batchedLogger]);
178
181
 
179
182
  ### File Logging
180
183
 
181
- Write logs directly to a file (requires `FileSystem` from `@effect/platform`):
184
+ Write logs directly to a file (requires the `FileSystem` service from `effect`):
182
185
 
183
186
  ```ts
184
187
  import { NodeFileSystem } from '@effect/platform-node';
@@ -395,7 +398,7 @@ for (const metric of snapshots) {
395
398
 
396
399
  ## 5. OTLP Export
397
400
 
398
- Effect v4 includes built-in OTLP exporters in `effect/unstable/observability`. No external OpenTelemetry SDK needed.
401
+ Effect v4 includes built-in OTLP exporters in `effect/observability`. No external OpenTelemetry SDK needed.
399
402
 
400
403
  ### All-in-One: `Otlp.layerJson`
401
404
 
@@ -403,8 +406,8 @@ The simplest setup — exports traces, logs, and metrics to a single OTLP endpoi
403
406
 
404
407
  ```ts
405
408
  import { Layer } from 'effect';
406
- import { FetchHttpClient } from 'effect/unstable/http';
407
- import { Otlp } from 'effect/unstable/observability';
409
+ import { FetchHttpClient } from 'effect/http';
410
+ import { Otlp } from 'effect/observability';
408
411
 
409
412
  const ObservabilityLayer = Otlp.layerJson({
410
413
  baseUrl: 'http://localhost:4318',
@@ -426,19 +429,26 @@ Variants:
426
429
  - `Otlp.layerProtobuf` — Protobuf serialization (more efficient)
427
430
  - `Otlp.layer` — requires you to provide `OtlpSerialization` separately
428
431
 
432
+ Use `Otlp.layerFromConfig()` for standard OpenTelemetry environment
433
+ configuration (`OTEL_EXPORTER_OTLP_ENDPOINT`, signal-specific endpoints,
434
+ headers, exporter selection, and `OTEL_SDK_DISABLED`). Provide both
435
+ `OtlpSerialization.layerJson` (or protobuf) and an `HttpClient` layer. Explicit
436
+ `layerJson({ baseUrl })` is the programmatic endpoint path; it is not the
437
+ environment-selected exporter setup.
438
+
429
439
  ### Individual OTLP Exporters
430
440
 
431
441
  For fine-grained control, configure each exporter independently:
432
442
 
433
443
  ```ts
434
444
  import { Layer } from 'effect';
435
- import { FetchHttpClient } from 'effect/unstable/http';
445
+ import { FetchHttpClient } from 'effect/http';
436
446
  import {
437
447
  OtlpLogger,
438
448
  OtlpMetrics,
439
449
  OtlpSerialization,
440
450
  OtlpTracer
441
- } from 'effect/unstable/observability';
451
+ } from 'effect/observability';
442
452
 
443
453
  const OtlpTracingLayer = OtlpTracer.layer({
444
454
  url: 'http://localhost:4318/v1/traces',
@@ -482,7 +492,7 @@ Each OTLP signal layer now outputs the shared `OtlpExporter.Flusher` service. Us
482
492
 
483
493
  ```ts
484
494
  import { Effect } from 'effect';
485
- import { OtlpExporter } from 'effect/unstable/observability';
495
+ import { OtlpExporter } from 'effect/observability';
486
496
 
487
497
  const flushTelemetry = Effect.gen(function* () {
488
498
  const flusher = yield* OtlpExporter.Flusher;
@@ -490,7 +500,42 @@ const flushTelemetry = Effect.gen(function* () {
490
500
  });
491
501
  ```
492
502
 
493
- All signal layers in the same memoized layer graph share one flusher registry, so one call drains logs, traces, and metrics concurrently. `flush` cannot fail and has no built-in timeout; it waits only for exports it starts, not an export already in flight.
503
+ All signal layers in the same memoized layer graph share one flusher registry, so
504
+ one call requests exports concurrently. `flush` cannot fail and has no built-in
505
+ timeout; it waits only for exports it starts, not exports already in flight.
506
+ Exporters in their temporary-disable window are skipped, so completion is not a
507
+ delivery acknowledgement. Successful delta-metric flushes advance their aggregation window.
508
+
509
+ The combined `Otlp.layer*` signatures expose `never` as their output service.
510
+ To access `Flusher` with the combined layer, merge `OtlpExporter.layerFlusher`
511
+ explicitly; memoization shares that registry with the signal layers:
512
+
513
+ <!-- typecheck -->
514
+ ```ts
515
+ import { Effect, Layer } from 'effect';
516
+ import { FetchHttpClient } from 'effect/http';
517
+ import { Otlp, OtlpExporter } from 'effect/observability';
518
+
519
+ const telemetry = Layer.mergeAll(
520
+ Otlp.layerJson({ baseUrl: 'http://localhost:4318' }),
521
+ OtlpExporter.layerFlusher
522
+ ).pipe(Layer.provide(FetchHttpClient.layer));
523
+
524
+ const flush = Effect.gen(function* () {
525
+ const flusher = yield* OtlpExporter.Flusher;
526
+ return yield* flusher.flush.pipe(Effect.timeoutOption('5 seconds'));
527
+ }).pipe(Effect.provide(telemetry));
528
+ ```
529
+
530
+ ### Exporter lifecycle
531
+
532
+ Keep the exporter layer alive for the application's lifetime. Scope finalization
533
+ starts a final export and waits for tracked in-flight exports, bounded by
534
+ `shutdownTimeout`; a manual flush has the narrower semantics described above.
535
+ The HTTP exporter drains response bodies before completing or retrying requests,
536
+ including error responses that carry a body. Custom/mock HTTP clients must
537
+ supply a consumable response body rather than only a status code. This releases
538
+ connections between export attempts; callers need no separate drain step.
494
539
 
495
540
  ### OTLP Layer Options
496
541
 
@@ -547,7 +592,7 @@ Export Effect metrics in Prometheus exposition format:
547
592
 
548
593
  ```ts
549
594
  import { Effect, Metric } from 'effect';
550
- import * as PrometheusMetrics from 'effect/unstable/observability/PrometheusMetrics';
595
+ import * as PrometheusMetrics from 'effect/observability/PrometheusMetrics';
551
596
 
552
597
  const program = Effect.gen(function* () {
553
598
  const counter = Metric.counter('http_requests_total', {
@@ -572,7 +617,7 @@ const program = Effect.gen(function* () {
572
617
  Automatically register a `/metrics` endpoint on your HTTP router:
573
618
 
574
619
  ```ts
575
- import * as PrometheusMetrics from 'effect/unstable/observability/PrometheusMetrics';
620
+ import * as PrometheusMetrics from 'effect/observability/PrometheusMetrics';
576
621
 
577
622
  // Default: GET /metrics
578
623
  const PrometheusLayer = PrometheusMetrics.layerHttp();
@@ -633,8 +678,8 @@ Use a test tracer or assert on span attributes captured via `Effect.withSpan`.
633
678
 
634
679
  ```ts
635
680
  import { Config, Effect, Layer, Logger, References } from 'effect';
636
- import { FetchHttpClient } from 'effect/unstable/http';
637
- import { Otlp } from 'effect/unstable/observability';
681
+ import { FetchHttpClient } from 'effect/http';
682
+ import { Otlp } from 'effect/observability';
638
683
 
639
684
  const DevObservability = Logger.layer([Logger.consolePretty()]);
640
685
 
@@ -715,5 +760,6 @@ class Checkout extends Context.Service<
715
760
  7. **Metric names should follow conventions** — snake_case with units suffix (e.g., `http_request_duration_ms`).
716
761
  8. **`Metric.withAttributes` creates a tagged variant** — it does not mutate the original metric.
717
762
  9. **`OtlpMetrics` temporality** — use `"delta"` for backends like Datadog/Dynatrace, `"cumulative"` (default) for Prometheus-style backends.
718
- 10. **All OTLP modules are under `effect/unstable/observability`** — the API may evolve but the patterns are stable.
763
+ 10. **OTLP modules are under `effect/observability`** — they remain tagged
764
+ `@stability unstable`; inspect the matching release when upgrading.
719
765
  11. **Use `OtlpExporter.Flusher` for manual drains** — bound `flusher.flush` with `Effect.timeoutOption` when shutdown latency must be capped.
@@ -120,9 +120,9 @@ yield* Effect.all([logA, logB, logC], { concurrency: 'unbounded', discard: true
120
120
  // Effect<void, E, R>
121
121
  ```
122
122
 
123
- ### `mode: 'result'` — run everything, never fail
123
+ ### `mode: 'result'` — collect typed outcomes
124
124
 
125
- Each slot becomes a `Result<A, E>` and the error channel becomes `never`. Every effect runs to completion (no fail-fast interruption):
125
+ Each slot becomes a `Result<A, E>` and the error channel becomes `never`. Typed failures do not short-circuit the batch; defects and interruption still terminate it:
126
126
 
127
127
  ```ts
128
128
  const results = yield* Effect.all(
@@ -233,6 +233,7 @@ const settled = yield* Effect.raceFirst(primary, secondary);
233
233
  Semantics (verified in `internal/effect.ts`):
234
234
 
235
235
  - **Loser interruption is awaited**: when a winner settles, the remaining fibers are interrupted uninterruptibly and the race only resumes with the winning exit after those interruptions (including finalizers) complete.
236
+ - Cleanup also covers losers still starting when the race settles or the caller is interrupted. Use the race combinators directly rather than custom start/cancel bookkeeping.
236
237
  - `race`/`raceAll`: early failures do not finish the race — the race keeps waiting until one effect succeeds or all have failed. If all fail, the failure reasons are collected into one combined `Cause`.
237
238
  - `raceFirst`/`raceAllFirst`: the first fiber to settle (success *or* failure) decides the outcome.
238
239
  - Effects are forked in iteration order; if an early effect completes synchronously, later effects may never start at all.
@@ -300,17 +301,33 @@ const strong = yield* Effect.filterMapEffect(
300
301
  // Array of kept, transformed values
301
302
  ```
302
303
 
303
- ### `Effect.partition` — split failures from successes, never fail
304
+ ### `Effect.partition` — collect successes and typed failures
304
305
 
305
- Runs every element (no short-circuit). Returns `[excluded, satisfying]` — failures first. Both arrays preserve input order:
306
+ Typed failures do not short-circuit. Returns `[passes, fails]` — **successes first**. Both arrays preserve input order; defects and interruption still propagate:
306
307
 
307
308
  ```ts
308
- const [failures, users] = yield* Effect.partition(
309
+ const [users, failures] = yield* Effect.partition(
309
310
  userIds,
310
311
  (id) => fetchUser(id),
311
312
  { concurrency: 8 }
312
313
  );
313
- // Effect<[excluded: Array<E>, satisfying: Array<User>], never, R>
314
+ // Effect<[passes: Array<User>, fails: Array<E>], never, R>
315
+ ```
316
+
317
+ `Arr.partition`, `Arr.separate`, the corresponding `Chunk` / `Record` helpers,
318
+ and `Option.partitionMap` also put successes first, matching `Stream.partition`.
319
+ Name destructured tuple elements by meaning rather than assuming the older order.
320
+
321
+ <!-- typecheck -->
322
+ ```ts
323
+ import { Effect } from 'effect';
324
+
325
+ const partitioned = Effect.partition(
326
+ [1, 2, 3],
327
+ (n) => n === 2 ? Effect.fail('rejected') : Effect.succeed(n),
328
+ { concurrency: 2 }
329
+ );
330
+ // Success value: [[1, 3], ['rejected']]
314
331
  ```
315
332
 
316
333
  ### `Effect.validate` — accumulate all failures
@@ -518,7 +535,7 @@ import { Effect } from 'effect';
518
535
 
519
536
  const syncAllUsers = (userIds: ReadonlyArray<string>) =>
520
537
  Effect.gen(function* () {
521
- const [failures, synced] = yield* Effect.partition(
538
+ const [synced, failures] = yield* Effect.partition(
522
539
  userIds,
523
540
  (id) => syncUser(id),
524
541
  { concurrency: 8 }
@@ -7,6 +7,8 @@ description: Use effect Path for platform-abstract file path operations includin
7
7
 
8
8
  Use effect Path abstraction for platform-abstract file path operations. Apply this skill when working with file paths, joining segments, resolving absolute paths, or converting between file URLs and paths. `Path.layer` supplies POSIX semantics from `effect`; Node.js and Bun platform layers provide host-specific path semantics. `@effect/platform-browser` does not provide a `BrowserPath` layer, so browser code should provide `Path.layer` or a custom layer explicitly.
9
9
 
10
+ Targets **Effect 4.0.0** (`effect@4.0.0` source tag). In browsers without `process.cwd()`, supply an absolute POSIX base to `resolve`/`toFileUrl` when using `Path.layer`; it does not invent a browser working directory.
11
+
10
12
  ## Import Pattern
11
13
 
12
14
  ```typescript
@@ -195,6 +197,7 @@ Both operations can fail with `BadArgument` error.
195
197
 
196
198
  ### fromFileUrl - Convert file URL to path
197
199
 
200
+ <!-- typecheck -->
198
201
  ```typescript
199
202
  import { Path } from 'effect';
200
203
  import { Effect } from 'effect';
@@ -205,12 +208,13 @@ const program = Effect.gen(function* () {
205
208
  new URL('file:///home/user/file.txt')
206
209
  );
207
210
  // "/home/user/file.txt" on Unix
208
- // "C:\home\user\file.txt" on Windows
211
+ // On Windows, use a drive-qualified URL such as file:///C:/home/user/file.txt.
209
212
  });
210
213
  ```
211
214
 
212
215
  ### toFileUrl - Convert path to file URL
213
216
 
217
+ <!-- typecheck -->
214
218
  ```typescript
215
219
  import { Path } from 'effect';
216
220
  import { Effect } from 'effect';
@@ -224,6 +228,7 @@ const program = Effect.gen(function* () {
224
228
 
225
229
  ## Complete Example
226
230
 
231
+ <!-- typecheck -->
227
232
  ```typescript
228
233
  import { Path } from 'effect';
229
234
  import { Effect } from 'effect';
@@ -253,9 +258,10 @@ const buildOutputPath = Effect.gen(function* () {
253
258
 
254
259
  In Effect v4, `Migrator.fromFileSystem(directory)` requires both `FileSystem.FileSystem` and `Path.Path`. Migration modules are imported through `path.toFileUrl(path.join(directory, file))` so absolute Windows paths are valid ESM specifiers.
255
260
 
261
+ <!-- typecheck -->
256
262
  ```typescript
257
263
  import { FileSystem, Path } from 'effect';
258
- import * as Migrator from 'effect/unstable/sql/Migrator';
264
+ import * as Migrator from 'effect/sql/Migrator';
259
265
 
260
266
  const loader: Migrator.Loader<FileSystem.FileSystem | Path.Path> =
261
267
  Migrator.fromFileSystem('./migrations');