@directive-run/claude-plugin 1.16.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 (83) hide show
  1. package/.claude-plugin/plugin.json +6 -0
  2. package/LICENSE +26 -0
  3. package/README.md +113 -0
  4. package/dist/index.cjs +2 -0
  5. package/dist/index.cjs.map +1 -0
  6. package/dist/index.d.cts +66 -0
  7. package/dist/index.d.ts +66 -0
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -0
  10. package/package.json +64 -0
  11. package/skills/README.md +9 -0
  12. package/skills/building-ai-agents/SKILL.md +414 -0
  13. package/skills/building-ai-agents/ai-adapters.md +257 -0
  14. package/skills/building-ai-agents/ai-agents-streaming.md +307 -0
  15. package/skills/building-ai-agents/ai-communication.md +304 -0
  16. package/skills/building-ai-agents/api-skeleton.md +5 -0
  17. package/skills/building-ai-agents/examples.md +849 -0
  18. package/skills/building-ai-orchestrators/SKILL.md +401 -0
  19. package/skills/building-ai-orchestrators/ai-multi-agent.md +375 -0
  20. package/skills/building-ai-orchestrators/ai-orchestrator.md +466 -0
  21. package/skills/building-ai-orchestrators/ai-tasks.md +268 -0
  22. package/skills/building-ai-orchestrators/api-skeleton.md +5 -0
  23. package/skills/building-ai-orchestrators/examples.md +1609 -0
  24. package/skills/building-directive-systems/SKILL.md +499 -0
  25. package/skills/building-directive-systems/api-skeleton.md +5 -0
  26. package/skills/building-directive-systems/examples.md +4149 -0
  27. package/skills/building-directive-systems/multi-module.md +327 -0
  28. package/skills/building-directive-systems/plugins.md +361 -0
  29. package/skills/building-directive-systems/react-adapter.md +376 -0
  30. package/skills/building-directive-systems/system-api.md +463 -0
  31. package/skills/getting-started-with-directive/SKILL.md +198 -0
  32. package/skills/getting-started-with-directive/api-skeleton.md +5 -0
  33. package/skills/getting-started-with-directive/core-patterns.md +240 -0
  34. package/skills/getting-started-with-directive/examples.md +87 -0
  35. package/skills/getting-started-with-directive/sitemap.md +202 -0
  36. package/skills/hardening-ai-systems/SKILL.md +377 -0
  37. package/skills/hardening-ai-systems/ai-budget-resilience.md +408 -0
  38. package/skills/hardening-ai-systems/ai-guardrails-memory.md +370 -0
  39. package/skills/hardening-ai-systems/ai-security.md +400 -0
  40. package/skills/hardening-ai-systems/api-skeleton.md +5 -0
  41. package/skills/hardening-ai-systems/examples.md +628 -0
  42. package/skills/migrating-to-directive/SKILL.md +249 -0
  43. package/skills/migrating-to-directive/anti-patterns.md +382 -0
  44. package/skills/migrating-to-directive/api-skeleton.md +5 -0
  45. package/skills/migrating-to-directive/core-patterns.md +240 -0
  46. package/skills/migrating-to-directive/examples.md +511 -0
  47. package/skills/migrating-to-directive/schema-types.md +273 -0
  48. package/skills/reviewing-directive-code/SKILL.md +232 -0
  49. package/skills/reviewing-directive-code/anti-patterns.md +382 -0
  50. package/skills/reviewing-directive-code/api-skeleton.md +5 -0
  51. package/skills/reviewing-directive-code/core-patterns.md +240 -0
  52. package/skills/reviewing-directive-code/examples.md +463 -0
  53. package/skills/reviewing-directive-code/naming.md +321 -0
  54. package/skills/scaffolding-directive-modules/SKILL.md +218 -0
  55. package/skills/scaffolding-directive-modules/api-skeleton.md +5 -0
  56. package/skills/scaffolding-directive-modules/core-patterns.md +240 -0
  57. package/skills/scaffolding-directive-modules/examples.md +1386 -0
  58. package/skills/scaffolding-directive-modules/naming.md +321 -0
  59. package/skills/scaffolding-directive-modules/schema-types.md +273 -0
  60. package/skills/testing-ai-systems/SKILL.md +398 -0
  61. package/skills/testing-ai-systems/ai-debug-observability.md +329 -0
  62. package/skills/testing-ai-systems/ai-mcp-rag.md +293 -0
  63. package/skills/testing-ai-systems/ai-testing-evals.md +485 -0
  64. package/skills/testing-ai-systems/api-skeleton.md +5 -0
  65. package/skills/testing-ai-systems/examples.md +1261 -0
  66. package/skills/testing-directive-code/SKILL.md +479 -0
  67. package/skills/testing-directive-code/api-skeleton.md +5 -0
  68. package/skills/testing-directive-code/examples.md +156 -0
  69. package/skills/testing-directive-code/history.md +351 -0
  70. package/skills/testing-directive-code/testing.md +412 -0
  71. package/skills/writing-directive-constraints/SKILL.md +486 -0
  72. package/skills/writing-directive-constraints/api-skeleton.md +5 -0
  73. package/skills/writing-directive-constraints/constraints.md +274 -0
  74. package/skills/writing-directive-constraints/error-boundaries.md +330 -0
  75. package/skills/writing-directive-constraints/examples.md +1361 -0
  76. package/skills/writing-directive-constraints/resolvers.md +367 -0
  77. package/skills/writing-directive-modules/SKILL.md +342 -0
  78. package/skills/writing-directive-modules/anti-patterns.md +382 -0
  79. package/skills/writing-directive-modules/api-skeleton.md +5 -0
  80. package/skills/writing-directive-modules/core-patterns.md +240 -0
  81. package/skills/writing-directive-modules/examples.md +1639 -0
  82. package/skills/writing-directive-modules/naming.md +321 -0
  83. package/skills/writing-directive-modules/schema-types.md +273 -0
@@ -0,0 +1,274 @@
1
+ # Constraints
2
+
3
+ > Covers `@directive-run/core` — constraint definition: `when` / `require`, async constraints with `deps`, priority, data-form predicates.
4
+
5
+ Constraints declare WHEN something is needed. They are the demand side of the constraint-resolver pattern. Constraints evaluate conditions against facts and emit requirements that resolvers fulfill.
6
+
7
+ ## Decision Tree: "Should this be a constraint?"
8
+
9
+ ```
10
+ Is this a condition that triggers work?
11
+ ├── Yes, and the work is async/side-effectful → Constraint + Resolver
12
+ ├── Yes, but the work is just a state derivation → Use derive instead
13
+ ├── No, it's reacting to a change that already happened → Use effect
14
+ └── No, it's user-initiated → Use event handler
15
+ ```
16
+
17
+ ## Basic Constraint Anatomy
18
+
19
+ A constraint has two parts: `when` (condition) and `require` (what's needed).
20
+
21
+ ```typescript
22
+ constraints: {
23
+ fetchUserWhenReady: {
24
+ // when() returns boolean – evaluated on every fact change
25
+ when: (facts) => facts.isAuthenticated && !facts.user,
26
+
27
+ // require – the requirement to emit when condition is true.
28
+ // Use the function form whenever you need to read facts —
29
+ // facts is NOT in scope inside the static object form.
30
+ require: (facts) => ({ type: "FETCH_USER", userId: facts.userId }),
31
+ },
32
+ },
33
+ ```
34
+
35
+ ### Static vs Dynamic Requirements
36
+
37
+ ```typescript
38
+ // Static requirement – same object every time
39
+ constraints: {
40
+ loadConfig: {
41
+ when: (facts) => facts.config === null,
42
+ require: { type: "LOAD_CONFIG" },
43
+ },
44
+ },
45
+
46
+ // Dynamic requirement – function that reads facts
47
+ constraints: {
48
+ fetchUser: {
49
+ when: (facts) => facts.isAuthenticated && !facts.profile,
50
+ require: (facts) => ({ type: "FETCH_USER", userId: facts.userId }),
51
+ },
52
+ },
53
+
54
+ // Multiple requirements – return an array
55
+ constraints: {
56
+ loadAll: {
57
+ when: (facts) => facts.phase === "init",
58
+ require: [
59
+ { type: "LOAD_CONFIG" },
60
+ { type: "LOAD_USER" },
61
+ ],
62
+ },
63
+ },
64
+ ```
65
+
66
+ ## Priority
67
+
68
+ Higher priority constraints are evaluated first. Use priority for conflict resolution when multiple constraints could fire simultaneously.
69
+
70
+ ```typescript
71
+ constraints: {
72
+ normalTransition: {
73
+ priority: 50,
74
+ when: (facts) => facts.phase === "red" && facts.elapsed > 30,
75
+ require: { type: "TRANSITION", to: "green" },
76
+ },
77
+
78
+ emergencyOverride: {
79
+ priority: 100, // Evaluated before normalTransition
80
+ when: (facts) => facts.emergencyActive,
81
+ require: { type: "TRANSITION", to: "red" },
82
+ },
83
+ },
84
+ ```
85
+
86
+ Default priority is 0. Higher numbers run first.
87
+
88
+ ## Ordering with `after`
89
+
90
+ Use `after` to declare that a constraint should only be evaluated after another constraint's resolver completes. This is different from priority (evaluation order) -- `after` blocks evaluation entirely until the dependency is resolved.
91
+
92
+ ```typescript
93
+ constraints: {
94
+ authenticate: {
95
+ when: (facts) => !facts.token,
96
+ require: { type: "AUTHENTICATE" },
97
+ },
98
+
99
+ // Only evaluate after authenticate's resolver completes
100
+ loadProfile: {
101
+ after: ["authenticate"],
102
+ when: (facts) => facts.token && !facts.profile,
103
+ require: { type: "LOAD_PROFILE" },
104
+ },
105
+
106
+ // Cross-module after reference
107
+ loadData: {
108
+ after: ["auth::authenticate"],
109
+ when: (facts) => facts.self.dataNeeded,
110
+ require: { type: "LOAD_DATA" },
111
+ },
112
+ },
113
+ ```
114
+
115
+ If the dependency's `when()` returns false (no requirement emitted), the blocked constraint proceeds normally. If the dependency's resolver fails, the blocked constraint remains blocked.
116
+
117
+ ## Async Constraints
118
+
119
+ For conditions that require async evaluation (e.g., remote validation). Async constraints MUST declare `deps` for dependency tracking.
120
+
121
+ ```typescript
122
+ constraints: {
123
+ validateToken: {
124
+ async: true,
125
+ deps: ["token"], // REQUIRED for async constraints
126
+
127
+ when: async (facts) => {
128
+ const valid = await validateTokenRemotely(facts.token);
129
+
130
+ return valid;
131
+ },
132
+
133
+ require: { type: "REFRESH_TOKEN" },
134
+ timeout: 5000, // Optional timeout in ms
135
+ },
136
+ },
137
+ ```
138
+
139
+ ### Why `deps` is Required for Async
140
+
141
+ Synchronous constraints use auto-tracking (proxy-based). Async constraints cannot be auto-tracked because the function is suspended across await boundaries. The `deps` array tells the engine which facts to watch.
142
+
143
+ ```typescript
144
+ // WRONG – async without deps, engine cannot track dependencies
145
+ constraints: {
146
+ check: {
147
+ async: true,
148
+ when: async (facts) => await validate(facts.token),
149
+ require: { type: "REFRESH" },
150
+ },
151
+ },
152
+
153
+ // CORRECT – deps tells the engine to re-evaluate when token changes
154
+ constraints: {
155
+ check: {
156
+ async: true,
157
+ deps: ["token"],
158
+ when: async (facts) => await validate(facts.token),
159
+ require: { type: "REFRESH" },
160
+ },
161
+ },
162
+ ```
163
+
164
+ ## Disabling Constraints at Runtime
165
+
166
+ ```typescript
167
+ const system = createSystem({ module: myModule });
168
+ system.start();
169
+
170
+ // Disable a constraint – it won't be evaluated
171
+ system.constraints.disable("fetchUserWhenReady");
172
+
173
+ // Check if disabled
174
+ system.constraints.isDisabled("fetchUserWhenReady"); // true
175
+
176
+ // Re-enable – triggers re-evaluation on next cycle
177
+ system.constraints.enable("fetchUserWhenReady");
178
+ ```
179
+
180
+ ## Common Mistakes
181
+
182
+ ### Putting async logic in resolvers instead of constraints
183
+
184
+ ```typescript
185
+ // WRONG – resolver checks conditions (constraint's job)
186
+ resolvers: {
187
+ fetchData: {
188
+ requirement: "FETCH",
189
+ resolve: async (req, context) => {
190
+ if (!context.facts.isAuthenticated) {
191
+ return; // Should be in constraint's when()
192
+ }
193
+ // ...
194
+ },
195
+ },
196
+ },
197
+
198
+ // CORRECT – constraint declares when, resolver just does the work
199
+ constraints: {
200
+ fetchWhenAuth: {
201
+ when: (facts) => facts.isAuthenticated && !facts.data,
202
+ require: { type: "FETCH" },
203
+ },
204
+ },
205
+
206
+ resolvers: {
207
+ fetchData: {
208
+ requirement: "FETCH",
209
+ resolve: async (req, context) => {
210
+ const res = await fetch("/api/data");
211
+ context.facts.data = await res.json();
212
+ },
213
+ },
214
+ },
215
+ ```
216
+
217
+ ### String literal for require
218
+
219
+ ```typescript
220
+ // WRONG – require must be an object
221
+ require: "FETCH_DATA",
222
+
223
+ // CORRECT – object with type property
224
+ require: { type: "FETCH_DATA" },
225
+
226
+ // CORRECT – with payload
227
+ require: { type: "FETCH_DATA", endpoint: "/api/users" },
228
+
229
+ // CORRECT – dynamic from facts
230
+ require: (facts) => ({ type: "FETCH_DATA", userId: facts.currentUserId }),
231
+ ```
232
+
233
+ ### Returning null to conditionally skip
234
+
235
+ ```typescript
236
+ // require can return null to suppress the requirement
237
+ constraints: {
238
+ conditionalFetch: {
239
+ when: (facts) => facts.needsUpdate,
240
+ require: (facts) => {
241
+ if (!facts.userId) {
242
+ return null; // No requirement emitted
243
+ }
244
+
245
+ return { type: "FETCH_USER", userId: facts.userId };
246
+ },
247
+ },
248
+ },
249
+ ```
250
+
251
+ ## Constraints vs Effects vs Derivations
252
+
253
+ | Feature | Purpose | Triggers |
254
+ |---|---|---|
255
+ | Constraint | Declare a need (emit requirement) | Fact changes, re-evaluated automatically |
256
+ | Resolver | Fulfill a need (async work) | Requirement emitted by constraint |
257
+ | Effect | React to changes (fire-and-forget) | Fact changes, runs after reconciliation |
258
+ | Derivation | Compute a value (synchronous, cached) | Fact changes, recomputed lazily |
259
+
260
+ ### When to Use Which
261
+
262
+ ```
263
+ "When X is true, the system needs Y" → Constraint
264
+ "Do Y" → Resolver
265
+ "Whenever X changes, log it" → Effect
266
+ "X is always facts.a + facts.b" → Derivation
267
+ ```
268
+
269
+ ## See also
270
+
271
+ - [`resolvers.md`](./resolvers.md) — the other half of the constraint-resolver loop; the supply side that fulfills the requirements constraints emit
272
+ - [`core-patterns.md`](./core-patterns.md) — how constraints fit into the module shape alongside facts, derivations, events, and effects
273
+ - [`multi-module.md`](./multi-module.md) — `crossModuleDeps` for constraints that fire on facts owned by another module
274
+ - [`anti-patterns.md`](./anti-patterns.md) — the constraint-shaped mistakes AI assistants reliably make
@@ -0,0 +1,330 @@
1
+ # Error Boundaries
2
+
3
+ > Covers `@directive-run/core` — error boundaries, recovery strategies, lifecycle hooks, and circuit breakers.
4
+
5
+ How to handle errors in Directive: recovery strategies, error boundaries, lifecycle hooks, and the circuit breaker pattern.
6
+
7
+ ## Decision Tree: "How should errors be handled?"
8
+
9
+ ```
10
+ Where did the error occur?
11
+ ├── Resolver (API call, async work)
12
+ │ ├── Transient failure (network, timeout) → retry / retry-later
13
+ │ ├── Permanent failure (404, auth) → skip or throw
14
+ │ └── Unknown → retry-later with maxRetries
15
+
16
+ ├── Constraint (evaluation error)
17
+ │ ├── Bug in when() logic → throw (fix the code)
18
+ │ └── Unexpected data shape → skip (disable constraint)
19
+
20
+ ├── Effect (side effect failed)
21
+ │ ├── Non-critical (logging, analytics) → skip
22
+ │ └── Critical (sync to external system) → retry-later
23
+
24
+ ├── Derivation (computation error)
25
+ │ └── Usually a bug → throw (fix the code)
26
+
27
+ └── External service (repeated failures)
28
+ └── Circuit breaker pattern
29
+ ```
30
+
31
+ ## Recovery Strategies
32
+
33
+ Directive supports five recovery strategies:
34
+
35
+ | Strategy | Behavior |
36
+ |---|---|
37
+ | `"skip"` | Swallow the error, continue processing |
38
+ | `"retry"` | Retry immediately (respects resolver retry policy) |
39
+ | `"retry-later"` | Retry after a delay with exponential backoff |
40
+ | `"disable"` | Disable the failing constraint/effect permanently |
41
+ | `"throw"` | Re-throw the error, halting the system |
42
+
43
+ ## System-Level Error Boundary
44
+
45
+ Configure error handling for the entire system:
46
+
47
+ ```typescript
48
+ const system = createSystem({
49
+ module: myModule,
50
+
51
+ errorBoundary: {
52
+ // Per-subsystem strategies (string or function)
53
+ onConstraintError: "skip",
54
+ onResolverError: "retry-later",
55
+ onEffectError: "skip",
56
+ onDerivationError: "throw",
57
+
58
+ // Global error callback – fires for all errors
59
+ onError: (error) => {
60
+ // error is a DirectiveError with source tracking
61
+ console.error(`[${error.source}] ${error.sourceId}: ${error.message}`);
62
+ console.error("Recoverable:", error.recoverable);
63
+ console.error("Context:", error.context);
64
+ },
65
+
66
+ // Configuration for retry-later strategy
67
+ retryLater: {
68
+ delayMs: 1000, // Initial delay (default: 1000)
69
+ maxRetries: 3, // Max retry attempts (default: 3)
70
+ backoffMultiplier: 2, // Multiply delay each retry (default: 2)
71
+ maxDelayMs: 30000, // Cap on delay growth (default: 30000)
72
+ },
73
+ },
74
+ });
75
+ ```
76
+
77
+ ## Dynamic Error Handling with Functions
78
+
79
+ Use functions instead of strings for conditional recovery:
80
+
81
+ ```typescript
82
+ errorBoundary: {
83
+ onResolverError: (error, resolverId) => {
84
+ // Network errors – retry later
85
+ if (error.message.includes("NetworkError")) {
86
+ return "retry-later";
87
+ }
88
+
89
+ // Auth errors – skip, don't retry
90
+ if (error.message.includes("401")) {
91
+ return "skip";
92
+ }
93
+
94
+ // Everything else – throw
95
+ return "throw";
96
+ },
97
+
98
+ onConstraintError: (error, constraintId) => {
99
+ // Disable constraints that repeatedly fail
100
+ if (constraintId === "experimentalFeature") {
101
+ return "disable";
102
+ }
103
+
104
+ return "skip";
105
+ },
106
+
107
+ onEffectError: (error, effectId) => {
108
+ // Analytics can fail silently
109
+ if (effectId.startsWith("analytics")) {
110
+ return "skip";
111
+ }
112
+
113
+ return "throw";
114
+ },
115
+ },
116
+ ```
117
+
118
+ ## DirectiveError
119
+
120
+ All errors passed to error boundary callbacks are `DirectiveError` instances with source tracking:
121
+
122
+ ```typescript
123
+ import { DirectiveError } from "@directive-run/core";
124
+
125
+ try {
126
+ await system.settle();
127
+ } catch (err) {
128
+ if (err instanceof DirectiveError) {
129
+ err.source; // "constraint" | "resolver" | "effect" | "derivation" | "system"
130
+ err.sourceId; // e.g., "fetchUser" – the specific item that failed
131
+ err.recoverable; // boolean – whether recovery strategies apply
132
+ err.context; // arbitrary debug data (e.g., the requirement object)
133
+ err.message; // human-readable description
134
+ }
135
+ }
136
+ ```
137
+
138
+ ## Resolver-Level Error Handling
139
+
140
+ Resolvers have their own retry policy independent of the error boundary:
141
+
142
+ ```typescript
143
+ resolvers: {
144
+ fetchData: {
145
+ requirement: "FETCH_DATA",
146
+
147
+ // Resolver-level retry policy
148
+ retry: {
149
+ attempts: 3,
150
+ backoff: "exponential",
151
+ initialDelay: 200,
152
+ maxDelay: 5000,
153
+ shouldRetry: (error, attempt) => {
154
+ // Only retry server errors, not client errors
155
+ if (error.message.includes("4")) {
156
+ return false;
157
+ }
158
+
159
+ return true;
160
+ },
161
+ },
162
+
163
+ resolve: async (req, context) => {
164
+ const res = await fetch("/api/data");
165
+ if (!res.ok) {
166
+ throw new Error(`HTTP ${res.status}`);
167
+ }
168
+ context.facts.data = await res.json();
169
+ },
170
+ },
171
+ },
172
+ ```
173
+
174
+ When both resolver retry and error boundary are configured, the resolver retries first. If all retries fail, the error boundary strategy is applied.
175
+
176
+ ## Lifecycle Hooks
177
+
178
+ Module-level hooks for lifecycle events:
179
+
180
+ ```typescript
181
+ const myModule = createModule("app", {
182
+ schema: { facts: { status: t.string() } },
183
+
184
+ hooks: {
185
+ onInit: (system) => {
186
+ console.log("Module initialized");
187
+ },
188
+ onStart: (system) => {
189
+ console.log("System started");
190
+ },
191
+ onStop: (system) => {
192
+ console.log("System stopped");
193
+ },
194
+ onError: (error, hookContext) => {
195
+ console.error("Module error:", error.message);
196
+ // error is a DirectiveError
197
+ // hookContext provides additional details
198
+ },
199
+ },
200
+
201
+ init: (facts) => {
202
+ facts.status = "ready";
203
+ },
204
+ });
205
+ ```
206
+
207
+ ## Circuit Breaker
208
+
209
+ For protecting against cascading failures from external services. The circuit breaker tracks failure rates and short-circuits requests when a threshold is exceeded.
210
+
211
+ ```typescript
212
+ import { createCircuitBreaker } from "@directive-run/core/plugins";
213
+
214
+ const apiBreaker = createCircuitBreaker({
215
+ name: "external-api",
216
+ failureThreshold: 5, // Open after 5 failures (default: 5)
217
+ recoveryTimeMs: 30000, // Wait 30s before trying again (default: 30000)
218
+ halfOpenMaxRequests: 3, // Allow 3 trial requests in half-open (default: 3)
219
+ failureWindowMs: 60000, // Count failures within 60s window (default: 60000)
220
+
221
+ // Optional: classify which errors count as failures
222
+ isFailure: (error) => {
223
+ // Don't count 404s as circuit-breaking failures
224
+ if (error.message.includes("404")) {
225
+ return false;
226
+ }
227
+
228
+ return true;
229
+ },
230
+
231
+ // Optional: react to state changes
232
+ onStateChange: (from, to) => {
233
+ console.log(`Circuit: ${from} -> ${to}`);
234
+ },
235
+ });
236
+ ```
237
+
238
+ ### Using Circuit Breaker in Resolvers
239
+
240
+ ```typescript
241
+ resolvers: {
242
+ fetchData: {
243
+ requirement: "FETCH_DATA",
244
+ resolve: async (req, context) => {
245
+ const data = await apiBreaker.execute(async () => {
246
+ const res = await fetch("/api/data");
247
+
248
+ return res.json();
249
+ });
250
+
251
+ context.facts.data = data;
252
+ },
253
+ },
254
+ },
255
+ ```
256
+
257
+ ### Circuit Breaker + Constraints
258
+
259
+ Wire the circuit breaker state into constraints for automatic fallback:
260
+
261
+ ```typescript
262
+ constraints: {
263
+ apiDown: {
264
+ when: () => apiBreaker.getState() === "OPEN",
265
+ require: { type: "USE_FALLBACK" },
266
+ },
267
+
268
+ fetchNormally: {
269
+ when: (facts) => apiBreaker.getState() !== "OPEN" && !facts.data,
270
+ require: { type: "FETCH_DATA" },
271
+ },
272
+ },
273
+ ```
274
+
275
+ ### Circuit Breaker States
276
+
277
+ ```
278
+ CLOSED → Normal operation, all requests pass through
279
+ ↓ (failures >= threshold)
280
+ OPEN → All requests rejected immediately
281
+ ↓ (after recoveryTimeMs)
282
+ HALF_OPEN → Limited trial requests allowed
283
+ ↓ (trial succeeds) → CLOSED
284
+ ↓ (trial fails) → OPEN
285
+ ```
286
+
287
+ ### Circuit Breaker API
288
+
289
+ ```typescript
290
+ apiBreaker.getState(); // "CLOSED" | "OPEN" | "HALF_OPEN"
291
+ apiBreaker.isAllowed(); // boolean – would a request be allowed?
292
+ apiBreaker.getStats(); // { totalRequests, totalFailures, recentFailures, ... }
293
+ apiBreaker.forceState("CLOSED"); // Force state (useful in tests)
294
+ apiBreaker.reset(); // Reset to CLOSED with cleared stats
295
+ ```
296
+
297
+ ### CircuitBreakerOpenError
298
+
299
+ When the circuit is open, `execute()` throws a `CircuitBreakerOpenError`:
300
+
301
+ ```typescript
302
+ import { CircuitBreakerOpenError } from "@directive-run/core/plugins";
303
+
304
+ try {
305
+ await apiBreaker.execute(() => fetch("/api"));
306
+ } catch (error) {
307
+ if (error instanceof CircuitBreakerOpenError) {
308
+ error.code; // "CIRCUIT_OPEN"
309
+ error.retryAfterMs; // ms until circuit transitions to HALF_OPEN
310
+ error.state; // "OPEN" | "HALF_OPEN"
311
+ }
312
+ }
313
+ ```
314
+
315
+ ## Error Handling Checklist
316
+
317
+ 1. Set system-level `errorBoundary` with strategies for each subsystem
318
+ 2. Add `retry` policy on resolvers that call external services
319
+ 3. Use `shouldRetry` to avoid retrying permanent failures (4xx errors)
320
+ 4. Use circuit breaker for services with known reliability issues
321
+ 5. Wire circuit breaker state into constraints for automatic fallback
322
+ 6. Add `onError` callback for logging/monitoring
323
+ 7. Use `"throw"` for derivation errors (they indicate bugs)
324
+ 8. Use `"skip"` for non-critical effects (logging, analytics)
325
+
326
+ ## See also
327
+
328
+ - [`resolvers.md`](./resolvers.md) — retry policies and cancellation; the most common error source
329
+ - [`plugins.md`](./plugins.md) — `createCircuitBreaker` lives in `@directive-run/core/plugins`; pair it with error boundaries for cascading-failure isolation
330
+ - [`anti-patterns.md`](./anti-patterns.md) — common error-handling mistakes (catching the wrong type, swallowing recoverable errors)