@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,367 @@
1
+ # Resolvers
2
+
3
+ > Covers `@directive-run/core` — resolver definition: retry policies, batching, cancellation, custom dedup keys, `owns` binding.
4
+
5
+ Resolvers fulfill requirements emitted by constraints. They are the supply side of the constraint-resolver pattern. Resolvers handle async work and mutate state through `context.facts`.
6
+
7
+ ## Decision Tree: "How should this resolver work?"
8
+
9
+ ```
10
+ Is the work async (API calls, timers)?
11
+ ├── Yes → Use resolve: async (req, context) => { ... }
12
+
13
+ │ Does it fail often?
14
+ │ ├── Yes → Add retry: { attempts: 3, backoff: "exponential" }
15
+ │ └── No → No retry needed
16
+
17
+ │ Are there many similar requirements at once?
18
+ │ ├── Yes → Add batch config to group them
19
+ │ └── No → Single resolve is fine
20
+
21
+ └── No → Reconsider – maybe this is an event handler or derivation
22
+ ```
23
+
24
+ ## Basic Resolver
25
+
26
+ ```typescript
27
+ resolvers: {
28
+ fetchUser: {
29
+ // Which requirement type this resolver handles
30
+ requirement: "FETCH_USER",
31
+
32
+ // Async function – req is the requirement, context has facts + signal
33
+ resolve: async (req, context) => {
34
+ const res = await fetch(`/api/users/${req.userId}`);
35
+ const user = await res.json();
36
+
37
+ // Mutate facts to store results – resolvers return void
38
+ context.facts.user = user;
39
+ context.facts.phase = "loaded";
40
+ },
41
+ },
42
+ },
43
+ ```
44
+
45
+ ## Resolver Context
46
+
47
+ The `context` object provides:
48
+
49
+ ```typescript
50
+ resolve: async (req, context) => {
51
+ // context.facts – mutable proxy to the module's facts
52
+ context.facts.status = "loading";
53
+
54
+ // context.signal – AbortSignal, cancelled when system stops or requirement removed
55
+ const res = await fetch("/api/data", { signal: context.signal });
56
+
57
+ // context.snapshot() – read-only snapshot for before/after comparisons
58
+ const before = context.snapshot();
59
+ context.facts.count += 1;
60
+ const after = context.snapshot();
61
+ console.log(`Count: ${before.count} -> ${after.count}`);
62
+ },
63
+ ```
64
+
65
+ ## Custom Deduplication Keys
66
+
67
+ By default, requirements are deduped by their full content. Use `key` for custom deduplication:
68
+
69
+ ```typescript
70
+ resolvers: {
71
+ fetchUser: {
72
+ requirement: "FETCH_USER",
73
+
74
+ // Custom key – only one inflight resolver per userId
75
+ key: (req) => `fetch-user-${req.userId}`,
76
+
77
+ resolve: async (req, context) => {
78
+ const user = await fetchUser(req.userId);
79
+ context.facts.user = user;
80
+ },
81
+ },
82
+ },
83
+ ```
84
+
85
+ Without `key`, two requirements `{ type: "FETCH_USER", userId: "1" }` are deduped because they are structurally identical. With `key`, you control exactly what counts as a duplicate.
86
+
87
+ ## Retry Policies
88
+
89
+ ```typescript
90
+ resolvers: {
91
+ fetchData: {
92
+ requirement: "FETCH_DATA",
93
+
94
+ retry: {
95
+ // Maximum number of attempts (including the first)
96
+ attempts: 3,
97
+
98
+ // Backoff strategy between retries
99
+ backoff: "exponential", // "none" | "linear" | "exponential"
100
+
101
+ // Initial delay in ms (default: 100)
102
+ initialDelay: 200,
103
+
104
+ // Maximum delay in ms (caps exponential growth)
105
+ maxDelay: 5000,
106
+
107
+ // Optional: only retry certain errors
108
+ shouldRetry: (error, attempt) => {
109
+ // Don't retry 4xx client errors
110
+ if (error.message.includes("404")) {
111
+ return false;
112
+ }
113
+
114
+ return true;
115
+ },
116
+ },
117
+
118
+ resolve: async (req, context) => {
119
+ const res = await fetch("/api/data");
120
+ if (!res.ok) {
121
+ throw new Error(`HTTP ${res.status}`);
122
+ }
123
+ context.facts.data = await res.json();
124
+ },
125
+ },
126
+ },
127
+ ```
128
+
129
+ ### Backoff Strategies
130
+
131
+ | Strategy | Delay Pattern |
132
+ |---|---|
133
+ | `"none"` | No delay between retries |
134
+ | `"linear"` | initialDelay, 2x, 3x, ... |
135
+ | `"exponential"` | initialDelay, 2x, 4x, 8x, ... (capped by maxDelay) |
136
+
137
+ ## Batch Resolution
138
+
139
+ Group similar requirements and resolve them together. Prevents N+1 problems.
140
+
141
+ ### All-or-Nothing Batch
142
+
143
+ ```typescript
144
+ resolvers: {
145
+ fetchUsers: {
146
+ requirement: "FETCH_USER",
147
+
148
+ batch: {
149
+ enabled: true,
150
+ windowMs: 50, // Collect requirements for 50ms
151
+ maxSize: 20, // Flush immediately at 20 items
152
+ },
153
+
154
+ // resolveBatch receives all collected requirements
155
+ resolveBatch: async (reqs, context) => {
156
+ const ids = reqs.map((req) => req.userId);
157
+ const users = await fetchUsersBatch(ids);
158
+
159
+ // Store results
160
+ context.facts.users = users;
161
+ },
162
+ },
163
+ },
164
+ ```
165
+
166
+ ### Batch with Per-Item Results
167
+
168
+ For partial success/failure handling:
169
+
170
+ ```typescript
171
+ resolvers: {
172
+ fetchUsers: {
173
+ requirement: "FETCH_USER",
174
+
175
+ batch: {
176
+ enabled: true,
177
+ windowMs: 50,
178
+ maxSize: 20,
179
+ timeoutMs: 10000, // Per-batch timeout
180
+ },
181
+
182
+ // Return results array matching input order
183
+ resolveBatchWithResults: async (reqs, context) => {
184
+ const results = await Promise.all(
185
+ reqs.map(async (req) => {
186
+ try {
187
+ const user = await fetchUser(req.userId);
188
+ context.facts.users = {
189
+ ...context.facts.users,
190
+ [req.userId]: user,
191
+ };
192
+
193
+ return { success: true };
194
+ } catch (error) {
195
+ return { success: false, error: error as Error };
196
+ }
197
+ }),
198
+ );
199
+
200
+ return results;
201
+ },
202
+ },
203
+ },
204
+ ```
205
+
206
+ Failed items from `resolveBatchWithResults` can be individually retried if a retry policy is configured.
207
+
208
+ ## Timeout
209
+
210
+ ```typescript
211
+ resolvers: {
212
+ fetchData: {
213
+ requirement: "FETCH_DATA",
214
+ timeout: 10000, // Abort after 10 seconds
215
+
216
+ resolve: async (req, context) => {
217
+ // context.signal is automatically aborted on timeout
218
+ const res = await fetch("/api/data", { signal: context.signal });
219
+ context.facts.data = await res.json();
220
+ },
221
+ },
222
+ },
223
+ ```
224
+
225
+ ## Waiting for Resolution
226
+
227
+ ```typescript
228
+ const system = createSystem({ module: myModule });
229
+ system.start();
230
+
231
+ // Wait for all resolvers to complete
232
+ await system.settle();
233
+
234
+ // Wait with timeout
235
+ await system.settle(5000); // Throws if not settled in 5s
236
+
237
+ // Check settlement state
238
+ system.isSettled; // boolean
239
+
240
+ // Subscribe to settlement changes
241
+ const unsub = system.onSettledChange(() => {
242
+ console.log("Settlement state:", system.isSettled);
243
+ });
244
+ ```
245
+
246
+ ## Inspecting Resolver Status
247
+
248
+ ```typescript
249
+ const inspection = system.inspect();
250
+
251
+ // All resolver definitions
252
+ inspection.resolverDefs;
253
+ // [{ id: "fetchUser", requirement: "FETCH_USER" }, ...]
254
+
255
+ // Current resolver statuses
256
+ inspection.resolvers;
257
+ // { fetchUser: { state: "success", completedAt: ..., duration: 150 } }
258
+
259
+ // Inflight resolvers
260
+ inspection.inflight;
261
+ // [{ id: "req-1", resolverId: "fetchData", startedAt: 1709000000 }]
262
+
263
+ // Unmet requirements (no resolver matched)
264
+ inspection.unmet;
265
+
266
+ // Explain why a specific requirement exists
267
+ const explanation = system.explain("req-123");
268
+ ```
269
+
270
+ ## Common Mistakes
271
+
272
+ ### Returning data from resolve
273
+
274
+ ```typescript
275
+ // WRONG – return value is ignored
276
+ resolve: async (req, context) => {
277
+ const user = await fetchUser(req.userId);
278
+
279
+ return user; // Ignored!
280
+ },
281
+
282
+ // CORRECT – mutate context.facts
283
+ resolve: async (req, context) => {
284
+ const user = await fetchUser(req.userId);
285
+ context.facts.user = user;
286
+ },
287
+ ```
288
+
289
+ ### Abbreviating context to ctx
290
+
291
+ ```typescript
292
+ // WRONG
293
+ resolve: async (req, ctx) => { /* ... */ },
294
+
295
+ // CORRECT
296
+ resolve: async (req, context) => { /* ... */ },
297
+ ```
298
+
299
+ ### Checking conditions in resolve (constraint's job)
300
+
301
+ ```typescript
302
+ // WRONG – condition checking belongs in constraint's when()
303
+ resolve: async (req, context) => {
304
+ if (!context.facts.isAuthenticated) {
305
+ return;
306
+ }
307
+ // ...
308
+ },
309
+
310
+ // CORRECT – let constraints handle conditions
311
+ // The resolver only runs when a requirement is emitted
312
+ resolve: async (req, context) => {
313
+ const data = await fetch("/api/data");
314
+ context.facts.data = await data.json();
315
+ },
316
+ ```
317
+
318
+ ### Forgetting error handling
319
+
320
+ ```typescript
321
+ // WRONG – unhandled errors with no recovery
322
+ resolvers: {
323
+ fetch: {
324
+ requirement: "FETCH",
325
+ resolve: async (req, context) => {
326
+ const res = await fetch("/api");
327
+ context.facts.data = await res.json();
328
+ },
329
+ },
330
+ },
331
+
332
+ // CORRECT – retry policy + error handling
333
+ resolvers: {
334
+ fetch: {
335
+ requirement: "FETCH",
336
+ retry: { attempts: 3, backoff: "exponential" },
337
+ resolve: async (req, context) => {
338
+ const res = await fetch("/api");
339
+ if (!res.ok) {
340
+ throw new Error(`HTTP ${res.status}`);
341
+ }
342
+ context.facts.data = await res.json();
343
+ },
344
+ },
345
+ },
346
+ ```
347
+
348
+ ### Missing settle() after start()
349
+
350
+ ```typescript
351
+ // WRONG – reading facts before resolvers finish
352
+ system.start();
353
+ console.log(system.facts.data); // Likely null
354
+
355
+ // CORRECT
356
+ system.start();
357
+ await system.settle();
358
+ console.log(system.facts.data); // Resolved value
359
+ ```
360
+
361
+ ## See also
362
+
363
+ - [`constraints.md`](./constraints.md) — the demand side of the loop; what emits the requirements this resolver fulfills
364
+ - [`core-patterns.md`](./core-patterns.md) — how resolvers fit into the module shape
365
+ - [`error-boundaries.md`](./error-boundaries.md) — recovery strategies when a resolver throws (retry, fallback, circuit breaker)
366
+ - [`testing.md`](./testing.md) — mock resolvers via `mocks.resolvers` and the programmatic `mockResolver(type)` helper
367
+ - [`anti-patterns.md`](./anti-patterns.md) — the resolver-shaped mistakes (returning data from `resolve`, abbreviating `context` to `ctx`)
@@ -0,0 +1,342 @@
1
+ ---
2
+ name: writing-directive-modules
3
+ description: "Create and structure Directive modules with schema definitions, init functions, derivations, effects, events, constraints, and resolvers using the correct t.* type builders and naming conventions. Use when asked to build a Directive module, add facts/derivations/resolvers to an existing module, or scaffold any stateful domain with Directive."
4
+ ---
5
+
6
+ # Writing Directive Modules
7
+
8
+ ## Prerequisites
9
+
10
+ This skill applies when the project uses `@directive-run/core`. If not found in `package.json`, suggest installing it: `npm install @directive-run/core`.
11
+
12
+ ## When Claude Should Use This Skill
13
+
14
+ **Auto-invoke when the user:**
15
+ - Says "create a module", "add a fact", "build with Directive", or "scaffold state"
16
+ - Asks to model a domain (auth, cart, user profile, game state, etc.) in Directive
17
+ - Shows a `createModule()` call and asks to extend it
18
+ - Asks about schema types, derivations, effects, resolvers, or events in Directive context
19
+
20
+ **Do NOT invoke when:**
21
+ - Working on system composition (see `building-directive-systems.md`)
22
+ - Writing tests (see `testing-directive-code.md`)
23
+ - Asking about constraints/resolver interaction patterns only (see `writing-directive-constraints.md`)
24
+
25
+ ---
26
+
27
+ ## Module Shape – Canonical Form
28
+
29
+ ```typescript
30
+ import { createModule, t } from "@directive-run/core";
31
+
32
+ const myModule = createModule("name", {
33
+ schema: {
34
+ facts: {
35
+ phase: t.string<"idle" | "loading" | "done">(),
36
+ count: t.number(),
37
+ user: t.object<{ id: string; name: string } | null>(),
38
+ },
39
+ derivations: { // Optional – declare types here, define in derive
40
+ isLoading: t.boolean(),
41
+ displayName: t.string(),
42
+ },
43
+ events: { // Optional – declare event payloads here
44
+ increment: {},
45
+ setUser: { user: t.object<{ id: string; name: string }>() },
46
+ },
47
+ requirements: { // Optional – declare requirement payloads here
48
+ FETCH_USER: { userId: t.string() },
49
+ },
50
+ },
51
+
52
+ init: (facts) => { // Synchronous. Sets all facts to initial values.
53
+ facts.phase = "idle";
54
+ facts.count = 0;
55
+ facts.user = null;
56
+ },
57
+
58
+ derive: { // Auto-tracked – no manual deps needed
59
+ isLoading: (facts) => facts.phase === "loading",
60
+ displayName: (facts) => {
61
+ if (!facts.user) {
62
+ return "Guest";
63
+ }
64
+
65
+ return facts.user.name;
66
+ },
67
+ },
68
+
69
+ effects: { // Fire-and-forget side effects
70
+ logPhase: {
71
+ run: (facts, prev) => {
72
+ if (prev?.phase !== facts.phase) {
73
+ console.log(`Phase: ${facts.phase}`);
74
+ }
75
+ },
76
+ },
77
+ },
78
+
79
+ events: { // Synchronous fact mutations driven by user actions
80
+ increment: (facts) => {
81
+ facts.count += 1;
82
+ },
83
+ setUser: (facts, payload) => {
84
+ facts.user = payload.user;
85
+ },
86
+ },
87
+
88
+ constraints: { // WHEN conditions are true, REQUIRE something
89
+ fetchWhenReady: {
90
+ when: (facts) => facts.phase === "idle" && facts.count > 0,
91
+ require: (facts) => ({ type: "FETCH_USER", userId: "user-1" }),
92
+ },
93
+ },
94
+
95
+ resolvers: { // HOW to fulfill requirements
96
+ fetchUser: {
97
+ requirement: "FETCH_USER",
98
+ resolve: async (req, context) => {
99
+ context.facts.phase = "loading";
100
+ const res = await fetch(`/api/users/${req.userId}`);
101
+ context.facts.user = await res.json();
102
+ context.facts.phase = "done";
103
+ },
104
+ },
105
+ },
106
+ });
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Decision Tree: Where Does This Logic Go?
112
+
113
+ ```
114
+ What does this code do?
115
+ ├── Store a value that changes over time
116
+ │ └── schema.facts + init()
117
+ ├── Compute a value from other facts (synchronous, cached)
118
+ │ └── schema.derivations + derive
119
+ ├── React to fact changes (side effect, fire-and-forget)
120
+ │ └── effects
121
+ ├── Respond to a user action
122
+ │ └── schema.events + events
123
+ ├── Declare "when X is true, the system needs Y"
124
+ │ └── constraints
125
+ └── Fulfill a requirement with async work
126
+ └── resolvers
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Schema Type Builders
132
+
133
+ ### Which builder to use
134
+
135
+ ```
136
+ Value type?
137
+ ├── string (possibly with union) → t.string() or t.string<"a" | "b">()
138
+ ├── number (possibly with bounds) → t.number() or t.number().min(0).max(100)
139
+ ├── boolean → t.boolean()
140
+ ├── object / record → t.object<Shape>()
141
+ ├── array → t.array<ItemType>()
142
+ ├── string literal union → t.enum("a", "b", "c")
143
+ ├── exact value → t.literal(42)
144
+ ├── T | null → t.nullable(t.string()) or t.object<T | null>()
145
+ ├── T | undefined → t.optional(t.string())
146
+ └── Map / Set / Date → t.object<Map<K,V>>() / t.object<Set<T>>() / t.object<Date>()
147
+ ```
148
+
149
+ ### Chainable modifiers (available on all types)
150
+
151
+ ```typescript
152
+ t.string().default("light") // Default if init doesn't set it
153
+ t.string().validate((v) => v.includes("@")) // Dev-mode validation
154
+ t.string().transform((v) => v.trim()) // Runs on every set
155
+ t.string().brand<"UserId">() // Nominal typing
156
+ t.number().min(0).max(100).describe("Player score")
157
+ t.array<string>().nonEmpty().maxLength(10)
158
+ t.object<{ url: string }>().nonNull()
159
+ ```
160
+
161
+ ### Type assertion alternative (for simple schemas)
162
+
163
+ ```typescript
164
+ schema: {
165
+ facts: {} as { count: number; name: string },
166
+ derivations: {} as { doubled: number },
167
+ },
168
+ ```
169
+
170
+ Gives full TypeScript inference, skips runtime validation.
171
+
172
+ ---
173
+
174
+ ## Key Patterns
175
+
176
+ ### Derivation composition (derivation depending on another)
177
+
178
+ ```typescript
179
+ derive: {
180
+ isRed: (facts) => facts.phase === "red",
181
+ status: (facts, derived) => ({ // Second param accesses other derivations
182
+ phase: facts.phase,
183
+ isRed: derived.isRed,
184
+ }),
185
+ },
186
+ ```
187
+
188
+ ### Dynamic requirement from facts
189
+
190
+ ```typescript
191
+ constraints: {
192
+ fetchUser: {
193
+ when: (facts) => facts.isAuthenticated && !facts.profile,
194
+ require: (facts) => ({ type: "FETCH_USER", userId: facts.userId }),
195
+ },
196
+ },
197
+ ```
198
+
199
+ ### Returning null from require to suppress
200
+
201
+ ```typescript
202
+ require: (facts) => {
203
+ if (!facts.userId) {
204
+ return null; // No requirement emitted
205
+ }
206
+
207
+ return { type: "FETCH_USER", userId: facts.userId };
208
+ },
209
+ ```
210
+
211
+ ### Array/object mutation – always replace, never mutate in place
212
+
213
+ ```typescript
214
+ // CORRECT
215
+ facts.items = [...facts.items, newItem];
216
+ facts.config = { ...facts.config, theme: "dark" };
217
+ ```
218
+
219
+ ---
220
+
221
+ ## Critical Anti-Patterns
222
+
223
+ ### 1. Missing facts wrapper in schema
224
+
225
+ ```typescript
226
+ // WRONG
227
+ schema: { phase: t.string(), count: t.number() }
228
+
229
+ // CORRECT
230
+ schema: { facts: { phase: t.string(), count: t.number() } }
231
+ ```
232
+
233
+ ### 2. Nonexistent type builders
234
+
235
+ ```typescript
236
+ // WRONG – these do not exist
237
+ t.map<string, User>()
238
+ t.set<string>()
239
+ t.date()
240
+ t.record<string, number>()
241
+ t.any()
242
+
243
+ // CORRECT
244
+ t.object<Map<string, User>>()
245
+ t.object<Set<string>>()
246
+ t.object<Date>()
247
+ t.object<Record<string, number>>()
248
+ t.object<unknown>()
249
+ ```
250
+
251
+ ### 3. Async init
252
+
253
+ ```typescript
254
+ // WRONG – init is synchronous
255
+ init: async (facts) => { facts.config = await fetch("/api/config").then(r => r.json()); }
256
+
257
+ // CORRECT – init sets defaults; async work goes in constraints/resolvers
258
+ init: (facts) => { facts.config = null; }
259
+ ```
260
+
261
+ ### 4. Resolver parameter naming
262
+ Always use `(req, context)` – never `(req, ctx)` or `(request, context)`.
263
+
264
+ ### 5. Resolver returning data
265
+
266
+ ```typescript
267
+ // WRONG – return value is ignored
268
+ resolve: async (req, context) => { return await fetchUser(req.userId); }
269
+
270
+ // CORRECT – mutate context.facts
271
+ resolve: async (req, context) => { context.facts.user = await fetchUser(req.userId); }
272
+ ```
273
+
274
+ ### 6. Unnecessary type casting
275
+
276
+ ```typescript
277
+ // WRONG
278
+ const profile = system.facts.profile as UserProfile;
279
+
280
+ // CORRECT – schema provides the type
281
+ const profile = system.facts.profile;
282
+ ```
283
+
284
+ ### 7. Passthrough derivations
285
+
286
+ ```typescript
287
+ // WRONG – derivation just returns a fact unchanged
288
+ derive: { count: (facts) => facts.count }
289
+
290
+ // CORRECT – read the fact directly: system.facts.count
291
+ ```
292
+
293
+ ### 8. String require instead of object
294
+
295
+ ```typescript
296
+ // WRONG
297
+ require: "FETCH_DATA"
298
+
299
+ // CORRECT
300
+ require: { type: "FETCH_DATA" }
301
+ ```
302
+
303
+ ### 9. Accessing facts.self in single-module context
304
+
305
+ ```typescript
306
+ // facts.self.* is only for multi-module cross-module deps contexts
307
+ // In single-module resolvers: use context.facts.fieldName directly
308
+ ```
309
+
310
+ ### 10. Deep imports
311
+
312
+ ```typescript
313
+ // WRONG
314
+ import { createModule } from "@directive-run/core/module";
315
+
316
+ // CORRECT
317
+ import { createModule } from "@directive-run/core";
318
+ // Exception: plugins use @directive-run/core/plugins
319
+ ```
320
+
321
+ ---
322
+
323
+ ## Terminology Quick Reference
324
+
325
+ | Use | Never Use |
326
+ |-----|-----------|
327
+ | `facts` | state, store, atoms |
328
+ | `derivations` / `derive` | computed, selectors, getters, memos |
329
+ | `constraints` | rules, conditions, triggers |
330
+ | `resolvers` | handlers, actions, reducers |
331
+ | `requirements` | requests, commands |
332
+ | `effects` | watchers, subscriptions, reactions |
333
+ | `(req, context)` | `(req, ctx)` or `(request, context)` |
334
+
335
+ ---
336
+
337
+ ## Reference Files
338
+
339
+ - `core-patterns.md` – decision trees, full module example, system creation patterns
340
+ - `schema-types.md` – complete t.* builder reference, chainable methods, nonexistent types
341
+ - `naming.md` – req/context rules, return style, blank-line conventions, multi-module naming
342
+ - `anti-patterns.md` – 20 ranked anti-patterns with correct/wrong examples