opencode-effect-enforcer 0.2.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 (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +278 -0
  3. package/guidance/effect-first-development.md +1247 -0
  4. package/guidance/post__effect-and-the-near-inexpressible-majesty-of-layers.md +490 -0
  5. package/guidance/post__parse-dont-validate.md +109 -0
  6. package/guidance/progressive-disclosure-guidance.md +38 -0
  7. package/package.json +63 -0
  8. package/patterns/avoid-any.md +37 -0
  9. package/patterns/avoid-data-tagged-error.md +34 -0
  10. package/patterns/avoid-direct-json.md +51 -0
  11. package/patterns/avoid-direct-tag-checks.md +54 -0
  12. package/patterns/avoid-expect-in-if.md +52 -0
  13. package/patterns/avoid-mutable-state.md +70 -0
  14. package/patterns/avoid-native-fetch.md +61 -0
  15. package/patterns/avoid-node-imports.md +86 -0
  16. package/patterns/avoid-non-null-assertion.md +44 -0
  17. package/patterns/avoid-object-type.md +46 -0
  18. package/patterns/avoid-option-getorthrow.md +39 -0
  19. package/patterns/avoid-platform-coupling.md +43 -0
  20. package/patterns/avoid-process-env.md +43 -0
  21. package/patterns/avoid-react-hooks.md +73 -0
  22. package/patterns/avoid-schema-suffix.md +45 -0
  23. package/patterns/avoid-sync-fs.md +68 -0
  24. package/patterns/avoid-try-catch.md +47 -0
  25. package/patterns/avoid-ts-ignore.md +38 -0
  26. package/patterns/avoid-untagged-errors.md +67 -0
  27. package/patterns/avoid-yield-ref.md +46 -0
  28. package/patterns/casting-awareness.md +46 -0
  29. package/patterns/context-tag-extends.md +84 -0
  30. package/patterns/effect-catchall-default.md +61 -0
  31. package/patterns/effect-promise-vs-trypromise.md +47 -0
  32. package/patterns/effect-run-in-body.md +58 -0
  33. package/patterns/imperative-loops.md +76 -0
  34. package/patterns/prefer-arr-sort.md +52 -0
  35. package/patterns/prefer-duration-values.md +56 -0
  36. package/patterns/prefer-effect-fn.md +161 -0
  37. package/patterns/prefer-match-over-switch.md +48 -0
  38. package/patterns/prefer-option-over-null.md +56 -0
  39. package/patterns/prefer-redacted-config.md +70 -0
  40. package/patterns/prefer-schema-class.md +54 -0
  41. package/patterns/require-effect-concurrency.md +83 -0
  42. package/patterns/stream-large-files.md +63 -0
  43. package/patterns/throw-in-effect-gen.md +62 -0
  44. package/patterns/use-clock-service.md +45 -0
  45. package/patterns/use-command-executor-service.md +54 -0
  46. package/patterns/use-console-service.md +54 -0
  47. package/patterns/use-filesystem-service.md +59 -0
  48. package/patterns/use-http-client-service.md +77 -0
  49. package/patterns/use-path-service.md +53 -0
  50. package/patterns/use-random-service.md +45 -0
  51. package/patterns/use-temp-file-scoped.md +66 -0
  52. package/patterns/vm-in-wrong-file.md +51 -0
  53. package/patterns/yield-in-for-loop.md +61 -0
  54. package/skills/effect-ai-chat/SKILL.md +472 -0
  55. package/skills/effect-ai-language-model/SKILL.md +652 -0
  56. package/skills/effect-ai-prompt/SKILL.md +752 -0
  57. package/skills/effect-ai-provider/SKILL.md +668 -0
  58. package/skills/effect-ai-streaming/SKILL.md +418 -0
  59. package/skills/effect-ai-tool/SKILL.md +1132 -0
  60. package/skills/effect-atom-rpc/SKILL.md +488 -0
  61. package/skills/effect-atom-state/SKILL.md +640 -0
  62. package/skills/effect-batching/SKILL.md +614 -0
  63. package/skills/effect-cache/SKILL.md +570 -0
  64. package/skills/effect-cli/SKILL.md +523 -0
  65. package/skills/effect-command-executor/SKILL.md +675 -0
  66. package/skills/effect-concurrency-testing/SKILL.md +612 -0
  67. package/skills/effect-config/SKILL.md +580 -0
  68. package/skills/effect-context-witness/SKILL.md +274 -0
  69. package/skills/effect-domain-modeling/SKILL.md +1212 -0
  70. package/skills/effect-domain-predicates/SKILL.md +867 -0
  71. package/skills/effect-error-handling/SKILL.md +1581 -0
  72. package/skills/effect-fiber/SKILL.md +731 -0
  73. package/skills/effect-filesystem/SKILL.md +624 -0
  74. package/skills/effect-graph/SKILL.md +571 -0
  75. package/skills/effect-http-api/SKILL.md +1760 -0
  76. package/skills/effect-http-client/SKILL.md +989 -0
  77. package/skills/effect-http-server/SKILL.md +920 -0
  78. package/skills/effect-incremental-migration/SKILL.md +362 -0
  79. package/skills/effect-layer-design/SKILL.md +642 -0
  80. package/skills/effect-managed-runtime/SKILL.md +395 -0
  81. package/skills/effect-mcp-server/SKILL.md +608 -0
  82. package/skills/effect-observability/SKILL.md +719 -0
  83. package/skills/effect-optics/SKILL.md +554 -0
  84. package/skills/effect-parallelization/SKILL.md +668 -0
  85. package/skills/effect-path/SKILL.md +296 -0
  86. package/skills/effect-pattern-matching/SKILL.md +914 -0
  87. package/skills/effect-platform-abstraction/SKILL.md +1175 -0
  88. package/skills/effect-platform-layers/SKILL.md +514 -0
  89. package/skills/effect-pubsub-event-bus/SKILL.md +384 -0
  90. package/skills/effect-react-composition/SKILL.md +986 -0
  91. package/skills/effect-react-vm/SKILL.md +675 -0
  92. package/skills/effect-rpc-api/SKILL.md +624 -0
  93. package/skills/effect-rpc-client/SKILL.md +666 -0
  94. package/skills/effect-rpc-cluster/SKILL.md +1623 -0
  95. package/skills/effect-rpc-server/SKILL.md +767 -0
  96. package/skills/effect-scheduling/SKILL.md +124 -0
  97. package/skills/effect-schema-composition/SKILL.md +975 -0
  98. package/skills/effect-schema-v4/SKILL.md +691 -0
  99. package/skills/effect-scope/SKILL.md +682 -0
  100. package/skills/effect-service-implementation/SKILL.md +656 -0
  101. package/skills/effect-socket/SKILL.md +703 -0
  102. package/skills/effect-sql/SKILL.md +781 -0
  103. package/skills/effect-stream/SKILL.md +765 -0
  104. package/skills/effect-testing/SKILL.md +1331 -0
  105. package/skills/effect-typeclass-design/SKILL.md +161 -0
  106. package/skills/effect-wide-events/Article.md +66 -0
  107. package/skills/effect-wide-events/SKILL.md +95 -0
  108. package/skills/effect-workflow/SKILL.md +810 -0
  109. package/src/agent-policy.ts +22 -0
  110. package/src/enforcer.ts +104 -0
  111. package/src/frontmatter.ts +34 -0
  112. package/src/guidance.ts +66 -0
  113. package/src/index.ts +38 -0
  114. package/src/pattern-catalog.ts +115 -0
  115. package/src/pattern-matcher.ts +178 -0
  116. package/src/pattern.ts +97 -0
  117. package/src/skills.ts +29 -0
  118. package/src/write-projection.ts +66 -0
@@ -0,0 +1,274 @@
1
+ ---
2
+ name: effect-context-witness
3
+ description: Decide between Context.Service witness and capability patterns for dependency injection, understanding coupling trade-offs
4
+ ---
5
+
6
+ # Context Witness Pattern
7
+
8
+ Choose between witness (existence) and capability (behavior) patterns for Context.Service definitions.
9
+
10
+ ## Coupling: Hard vs Soft
11
+
12
+ **Some coupling is necessary and good** - but move it from hard to soft coupling.
13
+
14
+ ### Hard Coupling (Schema)
15
+
16
+ Field exists in the schema - tightly coupled to domain model:
17
+
18
+ ```typescript
19
+ import { Schema } from 'effect';
20
+
21
+ // ❌ HARD COUPLING - Serial is part of the schema
22
+ export const PaymentIntent = Schema.Struct({
23
+ id: Schema.String,
24
+ serial: Schema.String, // In schema = hard coupled
25
+ amount: Schema.BigInt
26
+ });
27
+
28
+ // Every PaymentIntent MUST have a serial
29
+ // Serialization/validation requires serial
30
+ // Cannot create without providing serial
31
+ // Schema change needed to remove/change serial
32
+ ```
33
+
34
+ ### Soft Coupling (Witness)
35
+
36
+ Field **removed from schema**, only injected in code:
37
+
38
+ ```typescript
39
+ import { Schema, Context, Effect, Logger } from 'effect';
40
+
41
+ declare const generateId: () => string;
42
+
43
+ // ✅ SOFT COUPLING - Serial not in schema
44
+ export const PaymentIntent = Schema.Struct({
45
+ id: Schema.String,
46
+ amount: Schema.BigInt
47
+ // No serial field!
48
+ });
49
+
50
+ // Serial is a witness - required but injected via Context
51
+ class Serial extends Context.Service<Serial, string>()('Serial') {}
52
+
53
+ const createPaymentIntent = (amount: bigint) =>
54
+ Effect.gen(function* () {
55
+ const serial = yield* Serial; // Injected from context
56
+
57
+ // Use serial in business logic, logging, etc.
58
+ // but it's not part of the persisted data
59
+ yield* Logger.info(`Creating payment intent ${serial}`);
60
+
61
+ return PaymentIntent.make({ id: generateId(), amount });
62
+ });
63
+
64
+ // Type: Effect<PaymentIntent, never, Serial>
65
+ ```
66
+
67
+ **Key insight:** `schema (hard coupling) => witness (soft coupling)`
68
+
69
+ By removing the field from the schema and injecting it only where needed, you:
70
+
71
+ - Keep domain models minimal
72
+ - Avoid unnecessary persistence
73
+ - Easy to test (provide test serial)
74
+ - Easy to remove/change (just change injection)
75
+ - Explicit dependencies in type signature
76
+
77
+ **When to use witnesses:**
78
+
79
+ - Correlation IDs (for tracing, not persistence)
80
+ - Request IDs (for logging, not data)
81
+ - Transaction contexts (for coordination, not storage)
82
+ - Tenant/Region markers (for routing, not schema)
83
+
84
+ ## Witness: Existence Only
85
+
86
+ Use when you only need to know something **exists** in the environment:
87
+
88
+ ```typescript
89
+ import { Schema, Context, Effect } from 'effect';
90
+
91
+ declare const PaymentIntent: Schema.Struct<{
92
+ id: typeof Schema.String;
93
+ serial: typeof Schema.String;
94
+ amount: typeof Schema.BigInt;
95
+ }>;
96
+ declare const other: any;
97
+
98
+ // Witness - a serial number exists
99
+ export class Serial extends Context.Service<Serial, string>()('Serial') {}
100
+
101
+ const createPaymentIntent = Effect.gen(function* () {
102
+ const serial = yield* Serial; // Pull from environment
103
+ return PaymentIntent.make({ serial, ...other });
104
+ });
105
+
106
+ // Type: Effect<PaymentIntent, never, Serial>
107
+ ```
108
+
109
+ ## Capability: Behavior
110
+
111
+ Use when you need **operations**:
112
+
113
+ ```typescript
114
+ import { Schema, Context, Effect } from 'effect';
115
+
116
+ declare const PaymentIntent: Schema.Struct<{
117
+ id: typeof Schema.String;
118
+ serial: typeof Schema.String;
119
+ amount: typeof Schema.BigInt;
120
+ }>;
121
+ declare const other: any;
122
+
123
+ // Capability - can generate/validate
124
+ export class SerialService extends Context.Service<
125
+ SerialService,
126
+ {
127
+ readonly next: () => string;
128
+ readonly validate: (s: string) => boolean;
129
+ }
130
+ >()('SerialService') {}
131
+
132
+ const createPaymentIntent = Effect.gen(function* () {
133
+ const svc = yield* SerialService;
134
+ const serial = svc.next(); // Behavior
135
+ return PaymentIntent.make({ serial, ...other });
136
+ });
137
+
138
+ // Type: Effect<PaymentIntent, never, SerialService>
139
+ ```
140
+
141
+ ## Decision Framework
142
+
143
+ | Need | Pattern |
144
+ | ------------------------ | ---------- |
145
+ | Just presence/value | Witness |
146
+ | Operations/generation | Capability |
147
+ | Precondition marker | Witness |
148
+ | Side effects | Capability |
149
+ | Multiple implementations | Capability |
150
+ | Mocking behavior | Capability |
151
+ | Correlation ID | Witness |
152
+ | Transaction context | Witness |
153
+ | Logger | Capability |
154
+ | Database | Capability |
155
+
156
+ ## When to Use Witness
157
+
158
+ Good fits:
159
+
160
+ - **Request ID** - must exist for tracing
161
+ - **Transaction context** - must be established
162
+ - **Tenant/Region** - required for data boundary
163
+ - **Pre-validated tokens** - already verified
164
+
165
+ ## When to Use Capability
166
+
167
+ Good fits:
168
+
169
+ - **Serial generation** - create/validate operations
170
+ - **Clock** - `now()` operation
171
+ - **Logger** - structured logging methods
172
+ - **Database** - query/transact operations
173
+ - **HTTP clients** - fetch/post operations
174
+
175
+ ## Testing Implications
176
+
177
+ Witnesses are trivial to provide:
178
+
179
+ ```typescript
180
+ import { Effect } from 'effect';
181
+
182
+ declare const myProgram: Effect.Effect<unknown, never, Serial>;
183
+ declare class Serial extends Context.Service<Serial, string>()('Serial') {}
184
+
185
+ const test = myProgram.pipe(Effect.provideService(Serial, 'test-serial-123'));
186
+ ```
187
+
188
+ Capabilities need implementation:
189
+
190
+ ```typescript
191
+ import { Effect } from 'effect';
192
+
193
+ declare const myProgram: Effect.Effect<unknown, never, SerialService>;
194
+ declare class SerialService extends Context.Service<
195
+ SerialService,
196
+ {
197
+ readonly next: () => string;
198
+ readonly validate: (s: string) => boolean;
199
+ }
200
+ >()('SerialService') {}
201
+
202
+ const test = myProgram.pipe(
203
+ Effect.provideService(SerialService, {
204
+ next: () => 'test-serial-123',
205
+ validate: () => true
206
+ })
207
+ );
208
+ ```
209
+
210
+ ## Coupling Strategy
211
+
212
+ **Rule of thumb**: Remove non-essential fields from schema, inject via witness instead.
213
+
214
+ **Ask yourself:** Does this need to be persisted/serialized?
215
+
216
+ - **No** → Remove from schema, inject via witness
217
+ - **Yes** → Keep in schema
218
+
219
+ ```typescript
220
+ import { Schema, Context, Effect, Logger, Clock } from 'effect';
221
+
222
+ declare const LineItem: Schema.Schema<any>;
223
+ declare const generateId: () => string;
224
+ declare const calculateTotal: (items: Array<any>) => bigint;
225
+
226
+ // ✅ Domain model - only persisted data
227
+ export const Order = Schema.Struct({
228
+ id: Schema.String,
229
+ items: Schema.Array(LineItem),
230
+ total: Schema.BigInt
231
+ // No correlationId - not persisted!
232
+ // No timestamp - derived from system!
233
+ });
234
+
235
+ // Witnesses for runtime context
236
+ class CorrelationId extends Context.Service<CorrelationId, string>()(
237
+ 'CorrelationId'
238
+ ) {}
239
+ class RequestId extends Context.Service<RequestId, string>()('RequestId') {}
240
+
241
+ // Use in code, not in data
242
+ const createOrder = (items: Array<Schema.Schema.Type<typeof LineItem>>) =>
243
+ Effect.gen(function* () {
244
+ const correlationId = yield* CorrelationId; // For tracing
245
+ const requestId = yield* RequestId; // For logging
246
+ const timestamp = yield* Clock.currentTimeMillis; // For timestamp
247
+
248
+ yield* Logger.info({
249
+ message: 'Creating order',
250
+ correlationId, // Used for tracing
251
+ requestId, // Used for logging
252
+ timestamp
253
+ });
254
+
255
+ // Data only contains what's persisted
256
+ return Order.make({
257
+ id: generateId(),
258
+ items,
259
+ total: calculateTotal(items)
260
+ });
261
+ });
262
+
263
+ // Type: Effect<Order, never, CorrelationId | RequestId>
264
+ ```
265
+
266
+ **Benefits:**
267
+
268
+ - Minimal schemas (only persisted data)
269
+ - Context values available when needed
270
+ - Easy to test with different context
271
+ - Can add/remove context without schema changes
272
+ - Explicit dependencies in type signatures
273
+
274
+ Choose witness for simplicity, capability for flexibility.