@directive-run/knowledge 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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -0
  3. package/ai/ai-adapters.md +250 -0
  4. package/ai/ai-agents-streaming.md +269 -0
  5. package/ai/ai-budget-resilience.md +235 -0
  6. package/ai/ai-communication.md +281 -0
  7. package/ai/ai-debug-observability.md +243 -0
  8. package/ai/ai-guardrails-memory.md +332 -0
  9. package/ai/ai-mcp-rag.md +288 -0
  10. package/ai/ai-multi-agent.md +274 -0
  11. package/ai/ai-orchestrator.md +227 -0
  12. package/ai/ai-security.md +293 -0
  13. package/ai/ai-tasks.md +261 -0
  14. package/ai/ai-testing-evals.md +378 -0
  15. package/api-skeleton.md +5 -0
  16. package/core/anti-patterns.md +382 -0
  17. package/core/constraints.md +263 -0
  18. package/core/core-patterns.md +228 -0
  19. package/core/error-boundaries.md +322 -0
  20. package/core/multi-module.md +315 -0
  21. package/core/naming.md +283 -0
  22. package/core/plugins.md +344 -0
  23. package/core/react-adapter.md +262 -0
  24. package/core/resolvers.md +357 -0
  25. package/core/schema-types.md +262 -0
  26. package/core/system-api.md +271 -0
  27. package/core/testing.md +257 -0
  28. package/core/time-travel.md +238 -0
  29. package/dist/index.cjs +111 -0
  30. package/dist/index.cjs.map +1 -0
  31. package/dist/index.d.cts +10 -0
  32. package/dist/index.d.ts +10 -0
  33. package/dist/index.js +102 -0
  34. package/dist/index.js.map +1 -0
  35. package/examples/ab-testing.ts +385 -0
  36. package/examples/ai-checkpoint.ts +509 -0
  37. package/examples/ai-guardrails.ts +319 -0
  38. package/examples/ai-orchestrator.ts +589 -0
  39. package/examples/async-chains.ts +287 -0
  40. package/examples/auth-flow.ts +371 -0
  41. package/examples/batch-resolver.ts +341 -0
  42. package/examples/checkers.ts +589 -0
  43. package/examples/contact-form.ts +176 -0
  44. package/examples/counter.ts +393 -0
  45. package/examples/dashboard-loader.ts +512 -0
  46. package/examples/debounce-constraints.ts +105 -0
  47. package/examples/dynamic-modules.ts +293 -0
  48. package/examples/error-boundaries.ts +430 -0
  49. package/examples/feature-flags.ts +220 -0
  50. package/examples/form-wizard.ts +347 -0
  51. package/examples/fraud-analysis.ts +663 -0
  52. package/examples/goal-heist.ts +341 -0
  53. package/examples/multi-module.ts +57 -0
  54. package/examples/newsletter.ts +241 -0
  55. package/examples/notifications.ts +210 -0
  56. package/examples/optimistic-updates.ts +317 -0
  57. package/examples/pagination.ts +260 -0
  58. package/examples/permissions.ts +337 -0
  59. package/examples/provider-routing.ts +403 -0
  60. package/examples/server.ts +316 -0
  61. package/examples/shopping-cart.ts +422 -0
  62. package/examples/sudoku.ts +630 -0
  63. package/examples/theme-locale.ts +204 -0
  64. package/examples/time-machine.ts +225 -0
  65. package/examples/topic-guard.ts +306 -0
  66. package/examples/url-sync.ts +333 -0
  67. package/examples/websocket.ts +404 -0
  68. package/package.json +65 -0
@@ -0,0 +1,382 @@
1
+ # Anti-Patterns
2
+
3
+ 20 most common mistakes when generating Directive code, ranked by AI hallucination frequency. Every code generation MUST be checked against this list.
4
+
5
+ ## 1. Unnecessary Type Casting on Facts/Derivations
6
+
7
+ ```typescript
8
+ // WRONG — schema already provides the type
9
+ const profile = system.facts.profile as ResourceState<Profile>;
10
+
11
+ // CORRECT — trust the schema
12
+ const profile = system.facts.profile;
13
+ ```
14
+
15
+ ## 2. Flat Schema (Missing facts Wrapper)
16
+
17
+ ```typescript
18
+ // WRONG — facts must be nested under schema.facts
19
+ createModule("counter", {
20
+ schema: {
21
+ phase: t.string(),
22
+ count: t.number(),
23
+ },
24
+ });
25
+
26
+ // CORRECT
27
+ createModule("counter", {
28
+ schema: {
29
+ facts: {
30
+ phase: t.string(),
31
+ count: t.number(),
32
+ },
33
+ },
34
+ });
35
+ ```
36
+
37
+ ## 3. Bare `facts.*` in Multi-Module Constraints
38
+
39
+ ```typescript
40
+ // WRONG — multi-module constraints use facts.self for own module
41
+ constraints: {
42
+ checkItems: {
43
+ when: (facts) => facts.items.length > 0,
44
+ require: { type: "PROCESS" },
45
+ },
46
+ },
47
+
48
+ // CORRECT — use facts.self.* for own module facts
49
+ constraints: {
50
+ checkItems: {
51
+ when: (facts) => facts.self.items.length > 0,
52
+ require: { type: "PROCESS" },
53
+ },
54
+ },
55
+ ```
56
+
57
+ ## 4. Nonexistent Schema Builders
58
+
59
+ ```typescript
60
+ // WRONG — t.map(), t.set(), t.promise() do not exist
61
+ schema: {
62
+ facts: {
63
+ cache: t.map<string, User>(),
64
+ tags: t.set<string>(),
65
+ pending: t.promise<Data>(),
66
+ },
67
+ },
68
+
69
+ // CORRECT — use t.object() with type parameter
70
+ schema: {
71
+ facts: {
72
+ cache: t.object<Map<string, User>>(),
73
+ tags: t.object<Set<string>>(),
74
+ pending: t.object<Promise<Data>>(),
75
+ },
76
+ },
77
+ ```
78
+
79
+ ## 5. Abbreviating `context` to `ctx`
80
+
81
+ ```typescript
82
+ // WRONG — never abbreviate context
83
+ resolve: async (req, ctx) => {
84
+ ctx.facts.status = "done";
85
+ },
86
+
87
+ // CORRECT — always spell out context
88
+ resolve: async (req, context) => {
89
+ context.facts.status = "done";
90
+ },
91
+ ```
92
+
93
+ ## 6. Flat Module Config (No schema Wrapper)
94
+
95
+ ```typescript
96
+ // WRONG — properties must be inside schema.facts
97
+ createModule("timer", {
98
+ phase: t.string(),
99
+ elapsed: t.number(),
100
+ });
101
+
102
+ // CORRECT — wrap in schema: { facts: {} }
103
+ createModule("timer", {
104
+ schema: {
105
+ facts: {
106
+ phase: t.string(),
107
+ elapsed: t.number(),
108
+ },
109
+ },
110
+ });
111
+ ```
112
+
113
+ ## 7. String-Based Event Dispatch
114
+
115
+ ```typescript
116
+ // WRONG — events are not dispatched by string
117
+ system.dispatch("login", { token: "abc" });
118
+
119
+ // CORRECT — use the events accessor
120
+ system.events.login({ token: "abc" });
121
+ ```
122
+
123
+ ## 8. Direct Array/Object Mutation
124
+
125
+ ```typescript
126
+ // WRONG — proxy cannot detect in-place mutations
127
+ facts.items.push(item);
128
+ facts.config.theme = "dark";
129
+
130
+ // CORRECT — replace the entire value
131
+ facts.items = [...facts.items, item];
132
+ facts.config = { ...facts.config, theme: "dark" };
133
+ ```
134
+
135
+ ## 9. Nonexistent `useDirective` Hook
136
+
137
+ ```typescript
138
+ // WRONG — there is no useDirective hook
139
+ const state = useDirective(system);
140
+
141
+ // CORRECT — use useSelector with a selector function
142
+ const count = useSelector(system, (s) => s.facts.count);
143
+ const isLoading = useSelector(system, (s) => s.derive.isLoading);
144
+ ```
145
+
146
+ ## 10. Bracket Notation for Namespaced Facts
147
+
148
+ ```typescript
149
+ // WRONG — internal separator is not part of the public API
150
+ const status = facts["auth::status"];
151
+ const token = facts["auth_token"];
152
+
153
+ // CORRECT — use dot notation through the namespace proxy
154
+ const status = facts.auth.status;
155
+ const token = facts.auth.token;
156
+ ```
157
+
158
+ ## 11. Builder/Chaining API
159
+
160
+ ```typescript
161
+ // WRONG — there is no builder pattern
162
+ const mod = module("counter")
163
+ .schema({ count: t.number() })
164
+ .build();
165
+
166
+ // CORRECT — use createModule with object syntax
167
+ const mod = createModule("counter", {
168
+ schema: {
169
+ facts: { count: t.number() },
170
+ },
171
+ });
172
+ ```
173
+
174
+ ## 12. Returning Data from Resolvers
175
+
176
+ ```typescript
177
+ // WRONG — resolvers return void, not data
178
+ resolve: async (req, context) => {
179
+ const user = await fetchUser(req.userId);
180
+
181
+ return user; // Return value is ignored
182
+ },
183
+
184
+ // CORRECT — mutate context.facts to store results
185
+ resolve: async (req, context) => {
186
+ const user = await fetchUser(req.userId);
187
+ context.facts.user = user;
188
+ },
189
+ ```
190
+
191
+ ## 13. Async Logic in `init`
192
+
193
+ ```typescript
194
+ // WRONG — init is synchronous, facts assignment only
195
+ init: async (facts) => {
196
+ const data = await fetch("/api/config");
197
+ facts.config = await data.json();
198
+ },
199
+
200
+ // CORRECT — init sets defaults; use constraints/resolvers for async work
201
+ init: (facts) => {
202
+ facts.config = null;
203
+ facts.phase = "loading";
204
+ },
205
+
206
+ constraints: {
207
+ loadConfig: {
208
+ when: (facts) => facts.config === null,
209
+ require: { type: "LOAD_CONFIG" },
210
+ },
211
+ },
212
+ ```
213
+
214
+ ## 14. Missing `settle()` After `start()`
215
+
216
+ ```typescript
217
+ // WRONG — constraints fire on start, resolvers are async
218
+ system.start();
219
+ console.log(system.facts.data); // May still be null
220
+
221
+ // CORRECT — wait for resolvers to complete
222
+ system.start();
223
+ await system.settle();
224
+ console.log(system.facts.data); // Resolved
225
+ ```
226
+
227
+ ## 15. Missing `crossModuleDeps` Declaration
228
+
229
+ ```typescript
230
+ // WRONG — accessing auth facts without declaring dependency
231
+ const dataModule = createModule("data", {
232
+ schema: { facts: { items: t.array(t.string()) } },
233
+ constraints: {
234
+ fetchWhenAuth: {
235
+ when: (facts) => facts.auth.isAuthenticated, // Type error
236
+ require: { type: "FETCH" },
237
+ },
238
+ },
239
+ });
240
+
241
+ // CORRECT — declare crossModuleDeps for type-safe cross-module access
242
+ const dataModule = createModule("data", {
243
+ schema: { facts: { items: t.array(t.string()) } },
244
+ crossModuleDeps: { auth: authSchema },
245
+ constraints: {
246
+ fetchWhenAuth: {
247
+ when: (facts) => facts.auth.isAuthenticated,
248
+ require: { type: "FETCH" },
249
+ },
250
+ },
251
+ });
252
+ ```
253
+
254
+ ## 16. String Literal for `require`
255
+
256
+ ```typescript
257
+ // WRONG — require must be an object with type property
258
+ constraints: {
259
+ check: {
260
+ when: (facts) => facts.ready,
261
+ require: "FETCH_DATA",
262
+ },
263
+ },
264
+
265
+ // CORRECT — use object form with type
266
+ constraints: {
267
+ check: {
268
+ when: (facts) => facts.ready,
269
+ require: { type: "FETCH_DATA" },
270
+ },
271
+ },
272
+ ```
273
+
274
+ ## 17. Passthrough Derivations
275
+
276
+ ```typescript
277
+ // WRONG — derivation just returns a fact value unchanged
278
+ derive: {
279
+ count: (facts) => facts.count,
280
+ },
281
+
282
+ // CORRECT — remove it, read the fact directly instead
283
+ // system.facts.count instead of system.derive.count
284
+ ```
285
+
286
+ ## 18. Deep Import Paths
287
+
288
+ ```typescript
289
+ // WRONG — internal module paths are not public API
290
+ import { createModule } from "@directive-run/core/module";
291
+ import { createSystem } from "@directive-run/core/system";
292
+
293
+ // CORRECT — import from package root
294
+ import { createModule, createSystem } from "@directive-run/core";
295
+
296
+ // Exception: plugins have their own entry point
297
+ import { loggingPlugin } from "@directive-run/core/plugins";
298
+ ```
299
+
300
+ ## 19. Async `when()` Without `deps`
301
+
302
+ ```typescript
303
+ // WRONG — async constraints need explicit deps for tracking
304
+ constraints: {
305
+ validate: {
306
+ async: true,
307
+ when: async (facts) => {
308
+ const valid = await checkRemote(facts.token);
309
+
310
+ return valid;
311
+ },
312
+ require: { type: "REFRESH_TOKEN" },
313
+ },
314
+ },
315
+
316
+ // CORRECT — add deps array for async constraints
317
+ constraints: {
318
+ validate: {
319
+ async: true,
320
+ deps: ["token"],
321
+ when: async (facts) => {
322
+ const valid = await checkRemote(facts.token);
323
+
324
+ return valid;
325
+ },
326
+ require: { type: "REFRESH_TOKEN" },
327
+ },
328
+ },
329
+ ```
330
+
331
+ ## 20. No Error Handling on Failing Resolvers
332
+
333
+ ```typescript
334
+ // WRONG — unhandled errors crash the system
335
+ resolvers: {
336
+ fetchData: {
337
+ requirement: "FETCH",
338
+ resolve: async (req, context) => {
339
+ const res = await fetch("/api/data");
340
+ context.facts.data = await res.json();
341
+ },
342
+ },
343
+ },
344
+
345
+ // CORRECT — use retry policy and/or module error boundary
346
+ resolvers: {
347
+ fetchData: {
348
+ requirement: "FETCH",
349
+ retry: { attempts: 3, backoff: "exponential" },
350
+ resolve: async (req, context) => {
351
+ const res = await fetch("/api/data");
352
+ if (!res.ok) {
353
+ throw new Error(`HTTP ${res.status}`);
354
+ }
355
+ context.facts.data = await res.json();
356
+ },
357
+ },
358
+ },
359
+
360
+ // Also set error boundary at system level
361
+ const system = createSystem({
362
+ module: myModule,
363
+ errorBoundary: {
364
+ onResolverError: "retry-later",
365
+ },
366
+ });
367
+ ```
368
+
369
+ ## Quick Reference Checklist
370
+
371
+ Before generating any Directive code, verify:
372
+
373
+ 1. Schema is nested: `schema: { facts: { ... } }` (not flat)
374
+ 2. No `as` casts when reading facts or derivations
375
+ 3. Resolver params are `(req, context)` not `(req, ctx)`
376
+ 4. `require` is an object `{ type: "..." }` not a string
377
+ 5. `init()` is synchronous
378
+ 6. Resolvers return void and mutate `context.facts`
379
+ 7. Arrays/objects replaced, not mutated in place
380
+ 8. Multi-module uses `facts.self.*` for own facts
381
+ 9. Imports from `@directive-run/core`, not deep paths
382
+ 10. `await system.settle()` after `system.start()`
@@ -0,0 +1,263 @@
1
+ # Constraints
2
+
3
+ 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.
4
+
5
+ ## Decision Tree: "Should this be a constraint?"
6
+
7
+ ```
8
+ Is this a condition that triggers work?
9
+ ├── Yes, and the work is async/side-effectful → Constraint + Resolver
10
+ ├── Yes, but the work is just a state derivation → Use derive instead
11
+ ├── No, it's reacting to a change that already happened → Use effect
12
+ └── No, it's user-initiated → Use event handler
13
+ ```
14
+
15
+ ## Basic Constraint Anatomy
16
+
17
+ A constraint has two parts: `when` (condition) and `require` (what's needed).
18
+
19
+ ```typescript
20
+ constraints: {
21
+ fetchUserWhenReady: {
22
+ // when() returns boolean — evaluated on every fact change
23
+ when: (facts) => facts.isAuthenticated && !facts.user,
24
+
25
+ // require — the requirement to emit when condition is true
26
+ require: { type: "FETCH_USER", userId: facts.userId },
27
+ },
28
+ },
29
+ ```
30
+
31
+ ### Static vs Dynamic Requirements
32
+
33
+ ```typescript
34
+ // Static requirement — same object every time
35
+ constraints: {
36
+ loadConfig: {
37
+ when: (facts) => facts.config === null,
38
+ require: { type: "LOAD_CONFIG" },
39
+ },
40
+ },
41
+
42
+ // Dynamic requirement — function that reads facts
43
+ constraints: {
44
+ fetchUser: {
45
+ when: (facts) => facts.isAuthenticated && !facts.profile,
46
+ require: (facts) => ({ type: "FETCH_USER", userId: facts.userId }),
47
+ },
48
+ },
49
+
50
+ // Multiple requirements — return an array
51
+ constraints: {
52
+ loadAll: {
53
+ when: (facts) => facts.phase === "init",
54
+ require: [
55
+ { type: "LOAD_CONFIG" },
56
+ { type: "LOAD_USER" },
57
+ ],
58
+ },
59
+ },
60
+ ```
61
+
62
+ ## Priority
63
+
64
+ Higher priority constraints are evaluated first. Use priority for conflict resolution when multiple constraints could fire simultaneously.
65
+
66
+ ```typescript
67
+ constraints: {
68
+ normalTransition: {
69
+ priority: 50,
70
+ when: (facts) => facts.phase === "red" && facts.elapsed > 30,
71
+ require: { type: "TRANSITION", to: "green" },
72
+ },
73
+
74
+ emergencyOverride: {
75
+ priority: 100, // Evaluated before normalTransition
76
+ when: (facts) => facts.emergencyActive,
77
+ require: { type: "TRANSITION", to: "red" },
78
+ },
79
+ },
80
+ ```
81
+
82
+ Default priority is 0. Higher numbers run first.
83
+
84
+ ## Ordering with `after`
85
+
86
+ 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.
87
+
88
+ ```typescript
89
+ constraints: {
90
+ authenticate: {
91
+ when: (facts) => !facts.token,
92
+ require: { type: "AUTHENTICATE" },
93
+ },
94
+
95
+ // Only evaluate after authenticate's resolver completes
96
+ loadProfile: {
97
+ after: ["authenticate"],
98
+ when: (facts) => facts.token && !facts.profile,
99
+ require: { type: "LOAD_PROFILE" },
100
+ },
101
+
102
+ // Cross-module after reference
103
+ loadData: {
104
+ after: ["auth::authenticate"],
105
+ when: (facts) => facts.self.dataNeeded,
106
+ require: { type: "LOAD_DATA" },
107
+ },
108
+ },
109
+ ```
110
+
111
+ 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.
112
+
113
+ ## Async Constraints
114
+
115
+ For conditions that require async evaluation (e.g., remote validation). Async constraints MUST declare `deps` for dependency tracking.
116
+
117
+ ```typescript
118
+ constraints: {
119
+ validateToken: {
120
+ async: true,
121
+ deps: ["token"], // REQUIRED for async constraints
122
+
123
+ when: async (facts) => {
124
+ const valid = await validateTokenRemotely(facts.token);
125
+
126
+ return valid;
127
+ },
128
+
129
+ require: { type: "REFRESH_TOKEN" },
130
+ timeout: 5000, // Optional timeout in ms
131
+ },
132
+ },
133
+ ```
134
+
135
+ ### Why `deps` is Required for Async
136
+
137
+ 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.
138
+
139
+ ```typescript
140
+ // WRONG — async without deps, engine cannot track dependencies
141
+ constraints: {
142
+ check: {
143
+ async: true,
144
+ when: async (facts) => await validate(facts.token),
145
+ require: { type: "REFRESH" },
146
+ },
147
+ },
148
+
149
+ // CORRECT — deps tells the engine to re-evaluate when token changes
150
+ constraints: {
151
+ check: {
152
+ async: true,
153
+ deps: ["token"],
154
+ when: async (facts) => await validate(facts.token),
155
+ require: { type: "REFRESH" },
156
+ },
157
+ },
158
+ ```
159
+
160
+ ## Disabling Constraints at Runtime
161
+
162
+ ```typescript
163
+ const system = createSystem({ module: myModule });
164
+ system.start();
165
+
166
+ // Disable a constraint — it won't be evaluated
167
+ system.constraints.disable("fetchUserWhenReady");
168
+
169
+ // Check if disabled
170
+ system.constraints.isDisabled("fetchUserWhenReady"); // true
171
+
172
+ // Re-enable — triggers re-evaluation on next cycle
173
+ system.constraints.enable("fetchUserWhenReady");
174
+ ```
175
+
176
+ ## Common Mistakes
177
+
178
+ ### Putting async logic in resolvers instead of constraints
179
+
180
+ ```typescript
181
+ // WRONG — resolver checks conditions (constraint's job)
182
+ resolvers: {
183
+ fetchData: {
184
+ requirement: "FETCH",
185
+ resolve: async (req, context) => {
186
+ if (!context.facts.isAuthenticated) {
187
+ return; // Should be in constraint's when()
188
+ }
189
+ // ...
190
+ },
191
+ },
192
+ },
193
+
194
+ // CORRECT — constraint declares when, resolver just does the work
195
+ constraints: {
196
+ fetchWhenAuth: {
197
+ when: (facts) => facts.isAuthenticated && !facts.data,
198
+ require: { type: "FETCH" },
199
+ },
200
+ },
201
+
202
+ resolvers: {
203
+ fetchData: {
204
+ requirement: "FETCH",
205
+ resolve: async (req, context) => {
206
+ const res = await fetch("/api/data");
207
+ context.facts.data = await res.json();
208
+ },
209
+ },
210
+ },
211
+ ```
212
+
213
+ ### String literal for require
214
+
215
+ ```typescript
216
+ // WRONG — require must be an object
217
+ require: "FETCH_DATA",
218
+
219
+ // CORRECT — object with type property
220
+ require: { type: "FETCH_DATA" },
221
+
222
+ // CORRECT — with payload
223
+ require: { type: "FETCH_DATA", endpoint: "/api/users" },
224
+
225
+ // CORRECT — dynamic from facts
226
+ require: (facts) => ({ type: "FETCH_DATA", userId: facts.currentUserId }),
227
+ ```
228
+
229
+ ### Returning null to conditionally skip
230
+
231
+ ```typescript
232
+ // require can return null to suppress the requirement
233
+ constraints: {
234
+ conditionalFetch: {
235
+ when: (facts) => facts.needsUpdate,
236
+ require: (facts) => {
237
+ if (!facts.userId) {
238
+ return null; // No requirement emitted
239
+ }
240
+
241
+ return { type: "FETCH_USER", userId: facts.userId };
242
+ },
243
+ },
244
+ },
245
+ ```
246
+
247
+ ## Constraints vs Effects vs Derivations
248
+
249
+ | Feature | Purpose | Triggers |
250
+ |---|---|---|
251
+ | Constraint | Declare a need (emit requirement) | Fact changes, re-evaluated automatically |
252
+ | Resolver | Fulfill a need (async work) | Requirement emitted by constraint |
253
+ | Effect | React to changes (fire-and-forget) | Fact changes, runs after reconciliation |
254
+ | Derivation | Compute a value (synchronous, cached) | Fact changes, recomputed lazily |
255
+
256
+ ### When to Use Which
257
+
258
+ ```
259
+ "When X is true, the system needs Y" → Constraint
260
+ "Do Y" → Resolver
261
+ "Whenever X changes, log it" → Effect
262
+ "X is always facts.a + facts.b" → Derivation
263
+ ```