@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,315 @@
1
+ # Multi-Module Systems
2
+
3
+ How to compose multiple modules into a namespaced system with cross-module type safety.
4
+
5
+ ## Decision Tree: "Single or Multi-Module?"
6
+
7
+ ```
8
+ How many state domains?
9
+ ├── One → createSystem({ module: myModule })
10
+ │ Direct access: system.facts.count
11
+
12
+ └── Two or more → createSystem({ modules: { auth, cart, ui } })
13
+ Namespaced: system.facts.auth.token
14
+
15
+ Does module A need to read module B's state?
16
+ ├── No → No crossModuleDeps needed
17
+
18
+ └── Yes → Declare crossModuleDeps on the consuming module
19
+ Own facts at facts.self.*, other at facts.otherModule.*
20
+ ```
21
+
22
+ ## Creating a Multi-Module System
23
+
24
+ ```typescript
25
+ import { createModule, createSystem, t } from "@directive-run/core";
26
+
27
+ const authModule = createModule("auth", {
28
+ schema: {
29
+ facts: {
30
+ token: t.string<string | null>(),
31
+ isAuthenticated: t.boolean(),
32
+ },
33
+ events: {
34
+ login: { token: t.string() },
35
+ logout: {},
36
+ },
37
+ },
38
+ init: (facts) => {
39
+ facts.token = null;
40
+ facts.isAuthenticated = false;
41
+ },
42
+ events: {
43
+ login: (facts, payload) => {
44
+ facts.token = payload.token;
45
+ facts.isAuthenticated = true;
46
+ },
47
+ logout: (facts) => {
48
+ facts.token = null;
49
+ facts.isAuthenticated = false;
50
+ },
51
+ },
52
+ });
53
+
54
+ const cartModule = createModule("cart", {
55
+ schema: {
56
+ facts: {
57
+ items: t.array(t.object<{ id: string; qty: number }>()),
58
+ },
59
+ derivations: {
60
+ itemCount: t.number(),
61
+ },
62
+ },
63
+ init: (facts) => {
64
+ facts.items = [];
65
+ },
66
+ derive: {
67
+ itemCount: (facts) => facts.items.length,
68
+ },
69
+ });
70
+
71
+ // Multi-module system — namespaced access
72
+ const system = createSystem({
73
+ modules: { auth: authModule, cart: cartModule },
74
+ });
75
+
76
+ system.start();
77
+ ```
78
+
79
+ ## Accessing Namespaced State
80
+
81
+ ```typescript
82
+ // Facts — namespaced under module name
83
+ system.facts.auth.token;
84
+ system.facts.auth.isAuthenticated;
85
+ system.facts.cart.items;
86
+
87
+ // Derivations — namespaced under module name
88
+ system.derive.cart.itemCount;
89
+
90
+ // Events — namespaced under module name
91
+ system.events.auth.login({ token: "abc123" });
92
+ system.events.auth.logout();
93
+
94
+ // Subscribe — use "namespace.key" format
95
+ system.subscribe(["auth.token", "cart.items"], () => {
96
+ console.log("auth or cart changed");
97
+ });
98
+
99
+ // Watch — use "namespace.key" format
100
+ system.watch("auth.isAuthenticated", (newVal, oldVal) => {
101
+ console.log(`Auth: ${oldVal} -> ${newVal}`);
102
+ });
103
+
104
+ // Subscribe to all keys in a module
105
+ system.subscribeModule("cart", () => {
106
+ console.log("anything in cart changed");
107
+ });
108
+
109
+ // Wait for condition — facts are namespaced
110
+ await system.when((facts) => facts.auth.isAuthenticated);
111
+ ```
112
+
113
+ ## Cross-Module Dependencies
114
+
115
+ When a module needs to read another module's facts in its constraints, effects, or derivations, declare `crossModuleDeps`.
116
+
117
+ ### Extracting the Schema
118
+
119
+ Export the schema separately from the module so other modules can reference it:
120
+
121
+ ```typescript
122
+ // modules/auth.ts
123
+ export const authSchema = {
124
+ facts: {
125
+ token: t.string<string | null>(),
126
+ isAuthenticated: t.boolean(),
127
+ },
128
+ events: {
129
+ login: { token: t.string() },
130
+ logout: {},
131
+ },
132
+ } as const;
133
+
134
+ export const authModule = createModule("auth", {
135
+ schema: authSchema,
136
+ init: (facts) => {
137
+ facts.token = null;
138
+ facts.isAuthenticated = false;
139
+ },
140
+ // ...
141
+ });
142
+ ```
143
+
144
+ ### Declaring crossModuleDeps
145
+
146
+ ```typescript
147
+ // modules/data.ts
148
+ import { authSchema } from "./auth";
149
+
150
+ const dataModule = createModule("data", {
151
+ schema: {
152
+ facts: {
153
+ items: t.array(t.string()),
154
+ loaded: t.boolean(),
155
+ },
156
+ requirements: {
157
+ FETCH_ITEMS: {},
158
+ },
159
+ },
160
+
161
+ // Declare cross-module dependency
162
+ crossModuleDeps: { auth: authSchema },
163
+
164
+ init: (facts) => {
165
+ facts.items = [];
166
+ facts.loaded = false;
167
+ },
168
+
169
+ // CORRECT — facts.self for own module, facts.auth for cross-module
170
+ constraints: {
171
+ fetchWhenAuth: {
172
+ when: (facts) => facts.auth.isAuthenticated && !facts.self.loaded,
173
+ require: { type: "FETCH_ITEMS" },
174
+ },
175
+ },
176
+
177
+ resolvers: {
178
+ fetchItems: {
179
+ requirement: "FETCH_ITEMS",
180
+ resolve: async (req, context) => {
181
+ const res = await fetch("/api/items");
182
+ context.facts.items = await res.json();
183
+ context.facts.loaded = true;
184
+ },
185
+ },
186
+ },
187
+
188
+ // Effects also get cross-module facts
189
+ effects: {
190
+ onAuthChange: {
191
+ run: (facts, prev) => {
192
+ if (prev && prev.auth.isAuthenticated && !facts.auth.isAuthenticated) {
193
+ console.log("User logged out, clearing data");
194
+ }
195
+ },
196
+ },
197
+ },
198
+ });
199
+ ```
200
+
201
+ ## Common Mistakes
202
+
203
+ ### Using bare `facts.*` instead of `facts.self.*`
204
+
205
+ ```typescript
206
+ // WRONG — in cross-module context, bare facts has no self-module properties
207
+ constraints: {
208
+ check: {
209
+ when: (facts) => facts.loaded, // TypeScript error
210
+ require: { type: "FETCH" },
211
+ },
212
+ },
213
+
214
+ // CORRECT — use facts.self for own module
215
+ constraints: {
216
+ check: {
217
+ when: (facts) => facts.self.loaded,
218
+ require: { type: "FETCH" },
219
+ },
220
+ },
221
+ ```
222
+
223
+ ### Bracket notation for internal keys
224
+
225
+ ```typescript
226
+ // WRONG — the :: separator is internal, never use it directly
227
+ system.facts["auth::token"];
228
+ system.read("auth::status");
229
+
230
+ // CORRECT — dot notation through namespace proxy
231
+ system.facts.auth.token;
232
+ system.read("auth.status");
233
+ ```
234
+
235
+ ### Forgetting crossModuleDeps
236
+
237
+ ```typescript
238
+ // WRONG — facts.auth is untyped without crossModuleDeps
239
+ const dataModule = createModule("data", {
240
+ schema: { facts: { items: t.array(t.string()) } },
241
+ constraints: {
242
+ check: {
243
+ when: (facts) => facts.auth.isAuthenticated, // No type info
244
+ require: { type: "FETCH" },
245
+ },
246
+ },
247
+ });
248
+
249
+ // CORRECT — declare the dependency
250
+ const dataModule = createModule("data", {
251
+ schema: { facts: { items: t.array(t.string()) } },
252
+ crossModuleDeps: { auth: authSchema },
253
+ constraints: {
254
+ check: {
255
+ when: (facts) => facts.auth.isAuthenticated, // Fully typed
256
+ require: { type: "FETCH" },
257
+ },
258
+ },
259
+ });
260
+ ```
261
+
262
+ ## System Configuration Options
263
+
264
+ ```typescript
265
+ const system = createSystem({
266
+ modules: { auth: authModule, cart: cartModule, data: dataModule },
267
+
268
+ // Initial facts — applied after init(), before first reconciliation
269
+ initialFacts: {
270
+ auth: { token: "restored-token" },
271
+ cart: { items: cachedItems },
272
+ },
273
+
274
+ // Init order — control module initialization sequence
275
+ initOrder: "auto", // Sort by crossModuleDeps topology (default)
276
+ // initOrder: "declaration", // Use object key order
277
+ // initOrder: ["auth", "data", "cart"], // Explicit order
278
+
279
+ plugins: [loggingPlugin()],
280
+ debug: { timeTravel: true },
281
+ });
282
+
283
+ // Hydrate from async source (call before start)
284
+ await system.hydrate(async () => {
285
+ const stored = localStorage.getItem("app-state");
286
+
287
+ return stored ? JSON.parse(stored) : {};
288
+ });
289
+
290
+ system.start();
291
+ await system.settle();
292
+ ```
293
+
294
+ ## Dynamic Module Registration
295
+
296
+ ```typescript
297
+ // Lazy-load and register a module at runtime
298
+ const chatModule = await import("./modules/chat");
299
+ system.registerModule("chat", chatModule.default);
300
+
301
+ // Now accessible at system.facts.chat.*, system.events.chat.*, etc.
302
+ ```
303
+
304
+ ## Cross-Module Events
305
+
306
+ Events are namespaced at the system level but dispatched through the events accessor:
307
+
308
+ ```typescript
309
+ // Multi-module events
310
+ system.events.auth.login({ token: "abc" });
311
+ system.events.cart.addItem({ id: "item-1", qty: 1 });
312
+
313
+ // dispatch() also works with type discriminator
314
+ system.dispatch({ type: "login", token: "abc" });
315
+ ```
package/core/naming.md ADDED
@@ -0,0 +1,283 @@
1
+ # Naming Conventions
2
+
3
+ Directive naming rules that AI coding assistants must follow. These are non-negotiable project conventions.
4
+
5
+ ## Decision Tree: "What do I call this?"
6
+
7
+ ```
8
+ Resolver parameter names?
9
+ → (req, context) req = requirement, NEVER "request"
10
+ → NEVER (req, ctx) context is NEVER abbreviated
11
+
12
+ Computed values?
13
+ → "derivations" / derive NEVER "computed", "selectors", "getters"
14
+ → system.derive.myValue NEVER system.computed.myValue
15
+
16
+ State values?
17
+ → "facts" NEVER "state", "store", "atoms"
18
+ → system.facts.count NEVER system.state.count
19
+
20
+ Conditional triggers?
21
+ → "constraints" NEVER "rules", "conditions", "triggers"
22
+ → when() + require() NEVER if/then, trigger/action
23
+
24
+ Fulfillment logic?
25
+ → "resolvers" NEVER "handlers", "actions", "reducers"
26
+ → resolve(req, context) NEVER handle(req, ctx)
27
+ ```
28
+
29
+ ## Parameter Naming
30
+
31
+ ### `req` = requirement (NOT request)
32
+
33
+ The `req` parameter in resolvers and constraint `key()` functions is short for **requirement** -- the object emitted by a constraint's `require` property.
34
+
35
+ ```typescript
36
+ // CORRECT — req is a requirement
37
+ resolvers: {
38
+ fetchUser: {
39
+ requirement: "FETCH_USER",
40
+ key: (req) => `fetch-${req.userId}`,
41
+ resolve: async (req, context) => {
42
+ // req.type === "FETCH_USER"
43
+ // req.userId from the requirement payload
44
+ const user = await fetchUser(req.userId);
45
+ context.facts.user = user;
46
+ },
47
+ },
48
+ },
49
+
50
+ // WRONG — never use "request" or "r"
51
+ resolve: async (request, context) => { /* ... */ },
52
+ resolve: async (r, context) => { /* ... */ },
53
+ ```
54
+
55
+ ### `context` is Never Abbreviated
56
+
57
+ ```typescript
58
+ // CORRECT
59
+ resolve: async (req, context) => {
60
+ context.facts.status = "loaded";
61
+ context.signal; // AbortSignal
62
+ context.snapshot(); // facts snapshot
63
+ },
64
+
65
+ // WRONG — never abbreviate to ctx
66
+ resolve: async (req, ctx) => { /* ... */ },
67
+ ```
68
+
69
+ ## Return Style
70
+
71
+ ### Always Use Braces
72
+
73
+ No single-line returns. Always wrap in braces.
74
+
75
+ ```typescript
76
+ // WRONG
77
+ derive: {
78
+ isReady: (facts) => facts.phase === "ready",
79
+ },
80
+
81
+ constraints: {
82
+ check: {
83
+ when: (facts) => facts.count > 10,
84
+ require: { type: "PROCESS" },
85
+ },
86
+ },
87
+
88
+ // Wait -- the above IS correct for one-line arrow expressions.
89
+ // The brace rule applies to if/return blocks:
90
+
91
+ // WRONG — single-line if return
92
+ if (facts.user) return "ready";
93
+
94
+ // CORRECT — always use braces
95
+ if (facts.user) {
96
+ return "ready";
97
+ }
98
+ ```
99
+
100
+ ### Blank Line Before `return`
101
+
102
+ Add a blank line before `return` when there is code above it. Skip the blank line when `return` is the first statement in a block.
103
+
104
+ ```typescript
105
+ // CORRECT — blank line before return when code precedes it
106
+ function getStatus(facts) {
107
+ const phase = facts.phase;
108
+ const hasUser = facts.user !== null;
109
+
110
+ return phase === "ready" && hasUser;
111
+ }
112
+
113
+ // CORRECT — no blank line when return is first statement
114
+ function isReady(facts) {
115
+ return facts.phase === "ready";
116
+ }
117
+
118
+ // CORRECT — blank line after brace-style return block
119
+ function process(facts) {
120
+ if (!facts.ready) {
121
+ return null;
122
+ }
123
+
124
+ const result = computeResult(facts);
125
+
126
+ return result;
127
+ }
128
+
129
+ // WRONG — no blank line before return after code
130
+ function getStatus(facts) {
131
+ const phase = facts.phase;
132
+ return phase === "ready"; // Missing blank line
133
+ }
134
+ ```
135
+
136
+ ## Multi-Line Code Formatting
137
+
138
+ Never put properties or statements on a single line inside braces. Always expand to one item per line with proper indentation. This applies everywhere: schema definitions, init functions, events, effects, requirement types, and any other object or block.
139
+
140
+ ```typescript
141
+ // WRONG — properties crammed on one line
142
+ schema: {
143
+ facts: { phase: t.string(), count: t.number() },
144
+ requirements: { FETCH_USER: { id: t.string() }, RESET: {} },
145
+ },
146
+
147
+ // CORRECT — one property per line, always expanded
148
+ schema: {
149
+ facts: {
150
+ phase: t.string(),
151
+ count: t.number(),
152
+ },
153
+ requirements: {
154
+ FETCH_USER: {
155
+ id: t.string(),
156
+ },
157
+ RESET: {},
158
+ },
159
+ },
160
+
161
+ // WRONG — statements crammed on one line
162
+ init: (facts) => { facts.phase = "idle"; facts.count = 0; },
163
+
164
+ // CORRECT — one statement per line
165
+ init: (facts) => {
166
+ facts.phase = "idle";
167
+ facts.count = 0;
168
+ },
169
+
170
+ // WRONG
171
+ events: { reset: (facts) => { facts.count = 0; facts.phase = "idle"; } },
172
+
173
+ // CORRECT
174
+ events: {
175
+ reset: (facts) => {
176
+ facts.count = 0;
177
+ facts.phase = "idle";
178
+ },
179
+ },
180
+ ```
181
+
182
+ Single-expression arrows (no braces) are fine on one line. Empty objects `{}` are fine inline.
183
+ ```typescript
184
+ // OK — single expression, no braces
185
+ derive: {
186
+ isReady: (facts) => facts.phase === "ready",
187
+ },
188
+
189
+ // OK — empty object
190
+ RESET: {},
191
+ ```
192
+
193
+ ## Multi-Module Naming
194
+
195
+ ### `facts.self.*` for Own Module
196
+
197
+ In multi-module systems, constraints, effects, and derivations with `crossModuleDeps` receive namespaced facts. Own module facts are always at `facts.self.*`.
198
+
199
+ ```typescript
200
+ // CORRECT
201
+ constraints: {
202
+ loadWhenAuth: {
203
+ when: (facts) => facts.auth.isAuthenticated && !facts.self.loaded,
204
+ require: { type: "LOAD_DATA" },
205
+ },
206
+ },
207
+
208
+ // WRONG — bare facts.* in multi-module context
209
+ constraints: {
210
+ loadWhenAuth: {
211
+ when: (facts) => facts.isAuthenticated && !facts.loaded,
212
+ require: { type: "LOAD_DATA" },
213
+ },
214
+ },
215
+ ```
216
+
217
+ ### System-Level Access Uses Dot Notation
218
+
219
+ ```typescript
220
+ // CORRECT — dot notation through namespace proxy
221
+ system.facts.auth.token;
222
+ system.facts.cart.items;
223
+ system.derive.auth.isLoggedIn;
224
+ system.events.auth.login({ token: "..." });
225
+
226
+ // WRONG — bracket notation with internal separator
227
+ system.facts["auth::token"];
228
+ system.facts["auth_token"];
229
+ ```
230
+
231
+ ## Type Casting Rules
232
+
233
+ ### Never Cast When Reading
234
+
235
+ The schema provides all types. Do not add `as` casts when reading facts or derivations from the system.
236
+
237
+ ```typescript
238
+ // CORRECT — schema provides the type
239
+ const profile = system.facts.profile;
240
+ const isReady = system.derive.isReady;
241
+
242
+ // WRONG — unnecessary cast
243
+ const profile = system.facts.profile as UserProfile;
244
+ const isReady = system.derive.isReady as boolean;
245
+ ```
246
+
247
+ ### Cast Only in Schema Definition
248
+
249
+ Type assertions are only valid in schema definition using the `{} as {}` pattern:
250
+
251
+ ```typescript
252
+ // CORRECT — cast in schema definition
253
+ schema: {
254
+ facts: {} as { profile: UserProfile; settings: AppSettings },
255
+ derivations: {} as { displayName: string },
256
+ },
257
+
258
+ // OR use t.* builders (preferred)
259
+ schema: {
260
+ facts: {
261
+ profile: t.object<UserProfile>(),
262
+ settings: t.object<AppSettings>(),
263
+ },
264
+ derivations: {
265
+ displayName: t.string(),
266
+ },
267
+ },
268
+ ```
269
+
270
+ ## Terminology Quick Reference
271
+
272
+ | Directive Term | NEVER Use |
273
+ |---|---|
274
+ | facts | state, store, atoms, signals |
275
+ | derivations / derive | computed, selectors, getters, memos |
276
+ | constraints | rules, conditions, triggers, guards |
277
+ | resolvers | handlers, actions, reducers, sagas |
278
+ | requirements | requests, commands, intents |
279
+ | effects | watchers, subscriptions, reactions |
280
+ | module | slice, feature, domain |
281
+ | system | store, container, context |
282
+ | `req` (parameter) | request, r, requirement (spelled out) |
283
+ | `context` (parameter) | ctx, c, resolverContext |