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,161 @@
1
+ ---
2
+ name: effect-typeclass-design
3
+ description: Implement typeclasses with curried signatures and dual APIs for both data-first and data-last usage
4
+ ---
5
+
6
+ # Typeclass Design Skill
7
+
8
+ Use this skill when implementing typeclasses that provide reusable abstractions across multiple types.
9
+
10
+ ## Pattern: Curried Typeclass Functions
11
+
12
+ All typeclass functions must be **fully curried** to enable partial application:
13
+
14
+ ```typescript
15
+ import { Duration } from 'effect';
16
+
17
+ declare interface Durable<A> {
18
+ readonly getDuration: (self: A) => Duration.Duration;
19
+ }
20
+
21
+ // Create a typeclass function that takes the typeclass instance first,
22
+ // then curries out all other parameters
23
+ export const isMoreThan =
24
+ <A>(D: Durable<A>) =>
25
+ (minimum: Duration.Duration) =>
26
+ (self: A): boolean => {
27
+ const current = D.getDuration(self);
28
+ return Duration.isGreaterThanOrEqualTo(current, minimum);
29
+ };
30
+ ```
31
+
32
+ ## Pattern: Dual APIs
33
+
34
+ Provide both **data-first** (uncurried) and **data-last** (curried) variants using `Function.dual`:
35
+
36
+ ```typescript
37
+ import { Duration } from 'effect';
38
+ import * as Function from 'effect/Function';
39
+
40
+ declare interface Durable<A> {
41
+ readonly getDuration: (self: A) => Duration.Duration;
42
+ }
43
+
44
+ export const isMoreThan = <A>(D: Durable<A>) =>
45
+ Function.dual<
46
+ // Data-last (curried) - pipe-friendly
47
+ (minimum: Duration.Duration) => (self: A) => boolean,
48
+ // Data-first (uncurried) - direct call
49
+ (self: A, minimum: Duration.Duration) => boolean
50
+ >(
51
+ 2, // Number of arguments for data-first form
52
+ (self: A, minimum: Duration.Duration): boolean => {
53
+ const current = D.getDuration(self);
54
+ return Duration.isGreaterThanOrEqualTo(current, minimum);
55
+ }
56
+ );
57
+ ```
58
+
59
+ ## Usage Patterns
60
+
61
+ The dual API enables both styles:
62
+
63
+ ```typescript
64
+ import { pipe } from 'effect/Function';
65
+ import * as Duration from 'effect/Duration';
66
+ import * as Function from 'effect/Function';
67
+
68
+ declare interface Durable<A> {
69
+ readonly getDuration: (self: A) => Duration.Duration;
70
+ }
71
+
72
+ declare const isMoreThan: <A>(D: Durable<A>) => {
73
+ (minimum: Duration.Duration): (self: A) => boolean;
74
+ (self: A, minimum: Duration.Duration): boolean;
75
+ };
76
+
77
+ declare interface Appointment {
78
+ duration: Duration.Duration;
79
+ }
80
+
81
+ declare const Appointment: {
82
+ Durable: Durable<Appointment>;
83
+ };
84
+
85
+ declare const appointment: Appointment;
86
+ declare const appointments: Appointment[];
87
+
88
+ // Data-first: Direct function call
89
+ const hasMinimum = isMoreThan(Appointment.Durable)(
90
+ appointment,
91
+ Duration.hours(1)
92
+ );
93
+
94
+ // Data-last: Pipe-friendly
95
+ const hasMinimum2 = pipe(
96
+ appointment,
97
+ isMoreThan(Appointment.Durable)(Duration.hours(1))
98
+ );
99
+
100
+ // Partial application for filtering
101
+ const longAppointments = appointments.filter(
102
+ isMoreThan(Appointment.Durable)(Duration.hours(1))
103
+ );
104
+ ```
105
+
106
+ ## Complete Typeclass Example
107
+
108
+ ```typescript
109
+ import { Duration, Order } from 'effect';
110
+ import * as Function from 'effect/Function';
111
+
112
+ /**
113
+ * Typeclass for types that have a duration.
114
+ */
115
+ export interface Durable<A> {
116
+ readonly getDuration: (self: A) => Duration.Duration;
117
+ readonly setDuration: (self: A, duration: Duration.Duration) => A;
118
+ }
119
+
120
+ /**
121
+ * Create a Durable instance.
122
+ */
123
+ export const make = <A>(
124
+ getDuration: (self: A) => Duration.Duration,
125
+ setDuration: (self: A, duration: Duration.Duration) => A
126
+ ): Durable<A> => ({
127
+ getDuration,
128
+ setDuration
129
+ });
130
+
131
+ /**
132
+ * Check if duration is more than minimum.
133
+ */
134
+ export const isMoreThan = <A>(D: Durable<A>) =>
135
+ Function.dual<
136
+ (minimum: Duration.Duration) => (self: A) => boolean,
137
+ (self: A, minimum: Duration.Duration) => boolean
138
+ >(2, (self: A, minimum: Duration.Duration): boolean =>
139
+ Duration.isGreaterThanOrEqualTo(D.getDuration(self), minimum)
140
+ );
141
+
142
+ /**
143
+ * Order by duration.
144
+ */
145
+ export const OrderByDuration = <A>(D: Durable<A>): Order.Order<A> =>
146
+ Order.mapInput(Duration.Order, (self: A) => D.getDuration(self));
147
+ ```
148
+
149
+ ## When to Use
150
+
151
+ - Creating reusable abstractions (Schedulable, Durable, Priceable)
152
+ - Implementing operations that work across multiple types
153
+ - Providing composable, pipe-friendly APIs
154
+ - Enabling partial application for filtering/mapping
155
+
156
+ ## Key Principles
157
+
158
+ 1. **Curry everything** - Enable partial application
159
+ 2. **Dual APIs always** - Support both usage styles
160
+ 3. **Typeclass first** - First parameter is always the typeclass instance
161
+ 4. **Type lambda for HKT** - Use TypeLambda pattern when needed
@@ -0,0 +1,66 @@
1
+ # Logging Sucks - Wide Events & Observability
2
+
3
+ > **Source**: https://loggingsucks.com/
4
+ > **Author**: Boris Tane
5
+
6
+ ## Core Problem
7
+
8
+ Traditional logging is fundamentally broken for modern distributed systems. A single user request might touch 15 services, 3 databases, 2 caches, and a message queue, yet logs remain structured around monolithic, single-server assumptions.
9
+
10
+ The central issue: logs optimize for writing, not querying. Developers emit convenient `console.log()` statements without considering how teams will search them during incidents.
11
+
12
+ ## Key Terminology
13
+
14
+ **Structured Logging**: Key-value formatted output (typically JSON) replacing plain-text strings.
15
+
16
+ **Cardinality**: The number of unique values a field can contain. User IDs have high cardinality; HTTP methods have low cardinality.
17
+
18
+ **Dimensionality**: The field count per log event. More fields enable more sophisticated queries.
19
+
20
+ **Wide Events/Canonical Log Lines**: One comprehensive event per request, with all context attached instead of scattered multi-line outputs.
21
+
22
+ ## The OpenTelemetry Misconception
23
+
24
+ OpenTelemetry functions as a protocol and SDK for standardizing telemetry collection. However, it does not:
25
+
26
+ - Determine what gets logged
27
+ - Add business context automatically
28
+ - Fix poor instrumentation practices
29
+
30
+ OpenTelemetry is a delivery mechanism. It doesn't know critical business context. You have to tell it.
31
+
32
+ ## Wide Events Implementation
33
+
34
+ Rather than multiple debug statements throughout code execution, teams should emit a single enriched event containing:
35
+
36
+ - **Request metadata**: ID, timestamp, service name
37
+ - **User context**: Subscription tier, account age, lifetime value
38
+ - **Business data**: Cart contents, feature flags enabled
39
+ - **Performance metrics**: Latency, attempt counts
40
+ - **Error details**: If applicable
41
+
42
+ This allows single queries to answer complex questions: "Show checkout failures for premium users where the new flow was enabled."
43
+
44
+ ## Tail Sampling Strategy
45
+
46
+ Managing observability costs requires intelligent sampling:
47
+
48
+ - **100% retention**: All errors, slow requests (above p99), VIP users
49
+ - **Partial retention**: Random sampling of successful, fast requests (1-5%)
50
+
51
+ This preserves critical debugging information while controlling infrastructure expenses.
52
+
53
+ ## The Transformation
54
+
55
+ Wide events shift debugging from archaeological text searching to structured analytics—replacing "grep through 50 services hoping for clues" with targeted data queries returning results instantaneously.
56
+
57
+ ## Summary
58
+
59
+ | Traditional Logs | Wide Events |
60
+ | --------------------- | ------------------------------------ |
61
+ | Many small log lines | One comprehensive event per request |
62
+ | Scattered context | All context attached to single event |
63
+ | Low dimensionality | High dimensionality |
64
+ | Text search (grep) | Structured queries |
65
+ | Hard to correlate | Easy to correlate |
66
+ | Optimized for writing | Optimized for querying |
@@ -0,0 +1,95 @@
1
+ ---
2
+ name: effect-wide-events
3
+ description: Conceptual guide to wide events (canonical log lines) for observability. Use when thinking about instrumentation strategy, span annotations, or designing what context to capture.
4
+ ---
5
+
6
+ <wide-events>
7
+
8
+ <philosophy>
9
+ traditional := many(log-lines) → grep(services) → hope
10
+ wide := one(event) → query(structured) → answer
11
+
12
+ optimize(querying) ∧ ¬optimize(writing)
13
+ </philosophy>
14
+
15
+ <current-practice>
16
+ implementation := OTel spans + annotations
17
+ wide-events := mental-model for annotation strategy
18
+
19
+ annotate(span, context) where context = {
20
+ business ∪ user ∪ technical ∪ outcome
21
+ }
22
+
23
+ effect-api := Effect.annotateCurrentSpan ∨ Effect.annotateLogs
24
+ queryable-via := annotations ⊄ log-message-args
25
+ // extra log-message args → message body, not auto-indexed dimensions
26
+
27
+ execution-plan := Effect.withExecutionPlan ∨ Stream.withExecutionPlan
28
+ attempt-events := onEvent(AttemptStart | AttemptSuccess | AttemptFailure)
29
+ // AttemptFailure.cause retains typed failures, defects, and interruption
30
+ // every AttemptStart has exactly one terminal event; observer defects are isolated
31
+ </current-practice>
32
+
33
+ <dimensionality>
34
+ wide-event.fields := {
35
+ identity: {traceId, spanId, service, operation}
36
+ user: {userId, accountTier, accountAge, lifetimeValue}
37
+ business: {featureFlags, experimentGroup, cartValue}
38
+ performance: {durationMs, dbQueryCount, cacheHitRate, retryCount}
39
+ outcome: {success, errorCode, httpStatus}
40
+ }
41
+
42
+ high-dimensionality → better-queryability
43
+ high-cardinality(userId) → acceptable
44
+ </dimensionality>
45
+
46
+ <anti-patterns>
47
+ scattered-logs := console.log("step1") >> console.log("step2") >> ...
48
+ low-dimensionality := span.set("success", true) ∧ |fields| < 5
49
+ technical-only := {http.status, db.queries} ∧ ¬{user, business}
50
+ </anti-patterns>
51
+
52
+ <correct-pattern>
53
+ span.setAttributes({
54
+ "request.operation", "user.id", "user.tier",
55
+ "cart.items", "cart.value", "feature.*",
56
+ "db.query_count", "cache.hit_rate",
57
+ "request.success"
58
+ })
59
+
60
+ ∀ span → attach(identity ∪ user ∪ business ∪ performance ∪ outcome)
61
+ </correct-pattern>
62
+
63
+ <tail-sampling>
64
+ retain(100%) := errors ∨ slow(>p99) ∨ vip
65
+ retain(1-5%) := success ∧ fast
66
+ </tail-sampling>
67
+
68
+ <queryability-test>
69
+ before(instrument) → verify(answerable({
70
+ "failures where tier=premium ∧ feature.new_flow=true"
71
+ "p99(latency) group by tier"
72
+ "errors group by featureFlags"
73
+ "full context for user X incident"
74
+ }))
75
+
76
+ ¬queryable → ¬enough-context
77
+ </queryability-test>
78
+
79
+ <terminology>
80
+ cardinality := |unique values| (userId=high, httpMethod=low)
81
+ dimensionality := |fields per event| (more → better)
82
+ wide-event := canonical-log-line := one comprehensive record
83
+ </terminology>
84
+
85
+ <when-to-apply>
86
+ deciding(span-annotations)
87
+ reviewing(instrumentation-coverage)
88
+ debugging(incidents) → "what context was missing?"
89
+ planning(new-service-observability)
90
+ choosing(fields-to-index)
91
+ </when-to-apply>
92
+
93
+ </wide-events>
94
+
95
+ Reference: See `Article.md` for full article by Boris Tane (loggingsucks.com)