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.
- package/LICENSE +21 -0
- package/README.md +278 -0
- package/guidance/effect-first-development.md +1247 -0
- package/guidance/post__effect-and-the-near-inexpressible-majesty-of-layers.md +490 -0
- package/guidance/post__parse-dont-validate.md +109 -0
- package/guidance/progressive-disclosure-guidance.md +38 -0
- package/package.json +63 -0
- package/patterns/avoid-any.md +37 -0
- package/patterns/avoid-data-tagged-error.md +34 -0
- package/patterns/avoid-direct-json.md +51 -0
- package/patterns/avoid-direct-tag-checks.md +54 -0
- package/patterns/avoid-expect-in-if.md +52 -0
- package/patterns/avoid-mutable-state.md +70 -0
- package/patterns/avoid-native-fetch.md +61 -0
- package/patterns/avoid-node-imports.md +86 -0
- package/patterns/avoid-non-null-assertion.md +44 -0
- package/patterns/avoid-object-type.md +46 -0
- package/patterns/avoid-option-getorthrow.md +39 -0
- package/patterns/avoid-platform-coupling.md +43 -0
- package/patterns/avoid-process-env.md +43 -0
- package/patterns/avoid-react-hooks.md +73 -0
- package/patterns/avoid-schema-suffix.md +45 -0
- package/patterns/avoid-sync-fs.md +68 -0
- package/patterns/avoid-try-catch.md +47 -0
- package/patterns/avoid-ts-ignore.md +38 -0
- package/patterns/avoid-untagged-errors.md +67 -0
- package/patterns/avoid-yield-ref.md +46 -0
- package/patterns/casting-awareness.md +46 -0
- package/patterns/context-tag-extends.md +84 -0
- package/patterns/effect-catchall-default.md +61 -0
- package/patterns/effect-promise-vs-trypromise.md +47 -0
- package/patterns/effect-run-in-body.md +58 -0
- package/patterns/imperative-loops.md +76 -0
- package/patterns/prefer-arr-sort.md +52 -0
- package/patterns/prefer-duration-values.md +56 -0
- package/patterns/prefer-effect-fn.md +161 -0
- package/patterns/prefer-match-over-switch.md +48 -0
- package/patterns/prefer-option-over-null.md +56 -0
- package/patterns/prefer-redacted-config.md +70 -0
- package/patterns/prefer-schema-class.md +54 -0
- package/patterns/require-effect-concurrency.md +83 -0
- package/patterns/stream-large-files.md +63 -0
- package/patterns/throw-in-effect-gen.md +62 -0
- package/patterns/use-clock-service.md +45 -0
- package/patterns/use-command-executor-service.md +54 -0
- package/patterns/use-console-service.md +54 -0
- package/patterns/use-filesystem-service.md +59 -0
- package/patterns/use-http-client-service.md +77 -0
- package/patterns/use-path-service.md +53 -0
- package/patterns/use-random-service.md +45 -0
- package/patterns/use-temp-file-scoped.md +66 -0
- package/patterns/vm-in-wrong-file.md +51 -0
- package/patterns/yield-in-for-loop.md +61 -0
- package/skills/effect-ai-chat/SKILL.md +472 -0
- package/skills/effect-ai-language-model/SKILL.md +652 -0
- package/skills/effect-ai-prompt/SKILL.md +752 -0
- package/skills/effect-ai-provider/SKILL.md +668 -0
- package/skills/effect-ai-streaming/SKILL.md +418 -0
- package/skills/effect-ai-tool/SKILL.md +1132 -0
- package/skills/effect-atom-rpc/SKILL.md +488 -0
- package/skills/effect-atom-state/SKILL.md +640 -0
- package/skills/effect-batching/SKILL.md +614 -0
- package/skills/effect-cache/SKILL.md +570 -0
- package/skills/effect-cli/SKILL.md +523 -0
- package/skills/effect-command-executor/SKILL.md +675 -0
- package/skills/effect-concurrency-testing/SKILL.md +612 -0
- package/skills/effect-config/SKILL.md +580 -0
- package/skills/effect-context-witness/SKILL.md +274 -0
- package/skills/effect-domain-modeling/SKILL.md +1212 -0
- package/skills/effect-domain-predicates/SKILL.md +867 -0
- package/skills/effect-error-handling/SKILL.md +1581 -0
- package/skills/effect-fiber/SKILL.md +731 -0
- package/skills/effect-filesystem/SKILL.md +624 -0
- package/skills/effect-graph/SKILL.md +571 -0
- package/skills/effect-http-api/SKILL.md +1760 -0
- package/skills/effect-http-client/SKILL.md +989 -0
- package/skills/effect-http-server/SKILL.md +920 -0
- package/skills/effect-incremental-migration/SKILL.md +362 -0
- package/skills/effect-layer-design/SKILL.md +642 -0
- package/skills/effect-managed-runtime/SKILL.md +395 -0
- package/skills/effect-mcp-server/SKILL.md +608 -0
- package/skills/effect-observability/SKILL.md +719 -0
- package/skills/effect-optics/SKILL.md +554 -0
- package/skills/effect-parallelization/SKILL.md +668 -0
- package/skills/effect-path/SKILL.md +296 -0
- package/skills/effect-pattern-matching/SKILL.md +914 -0
- package/skills/effect-platform-abstraction/SKILL.md +1175 -0
- package/skills/effect-platform-layers/SKILL.md +514 -0
- package/skills/effect-pubsub-event-bus/SKILL.md +384 -0
- package/skills/effect-react-composition/SKILL.md +986 -0
- package/skills/effect-react-vm/SKILL.md +675 -0
- package/skills/effect-rpc-api/SKILL.md +624 -0
- package/skills/effect-rpc-client/SKILL.md +666 -0
- package/skills/effect-rpc-cluster/SKILL.md +1623 -0
- package/skills/effect-rpc-server/SKILL.md +767 -0
- package/skills/effect-scheduling/SKILL.md +124 -0
- package/skills/effect-schema-composition/SKILL.md +975 -0
- package/skills/effect-schema-v4/SKILL.md +691 -0
- package/skills/effect-scope/SKILL.md +682 -0
- package/skills/effect-service-implementation/SKILL.md +656 -0
- package/skills/effect-socket/SKILL.md +703 -0
- package/skills/effect-sql/SKILL.md +781 -0
- package/skills/effect-stream/SKILL.md +765 -0
- package/skills/effect-testing/SKILL.md +1331 -0
- package/skills/effect-typeclass-design/SKILL.md +161 -0
- package/skills/effect-wide-events/Article.md +66 -0
- package/skills/effect-wide-events/SKILL.md +95 -0
- package/skills/effect-workflow/SKILL.md +810 -0
- package/src/agent-policy.ts +22 -0
- package/src/enforcer.ts +104 -0
- package/src/frontmatter.ts +34 -0
- package/src/guidance.ts +66 -0
- package/src/index.ts +38 -0
- package/src/pattern-catalog.ts +115 -0
- package/src/pattern-matcher.ts +178 -0
- package/src/pattern.ts +97 -0
- package/src/skills.ts +29 -0
- 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)
|