@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,306 @@
1
+ // Example: topic-guard
2
+ // Source: examples/topic-guard/src/topic-guard.ts
3
+ // Pure module file — no DOM wiring
4
+
5
+ /**
6
+ * Topic Guard — Directive Module
7
+ *
8
+ * Demonstrates input guardrails for AI agents. Messages are checked against
9
+ * configurable guardrails before reaching the mock agent. Blocked messages
10
+ * are rejected with an explanation; allowed messages get a mock response.
11
+ */
12
+
13
+ import { type ModuleSchema, createModule, t } from "@directive-run/core";
14
+ import {
15
+ type GuardrailResult,
16
+ checkKeywordGuardrail,
17
+ checkTopicClassifier,
18
+ getMockAgentResponse,
19
+ } from "./mock-guardrails.js";
20
+
21
+ // ============================================================================
22
+ // Types
23
+ // ============================================================================
24
+
25
+ export interface ChatMessage {
26
+ role: "user" | "agent" | "system";
27
+ text: string;
28
+ blocked: boolean;
29
+ guardrail?: string;
30
+ }
31
+
32
+ export interface GuardrailLogEntry {
33
+ timestamp: number;
34
+ input: string;
35
+ result: GuardrailResult;
36
+ }
37
+
38
+ // ============================================================================
39
+ // Schema
40
+ // ============================================================================
41
+
42
+ export const topicGuardSchema = {
43
+ facts: {
44
+ input: t.string(),
45
+ messages: t.object<ChatMessage[]>(),
46
+ isProcessing: t.boolean(),
47
+ lastGuardrailResult: t.object<GuardrailResult | null>(),
48
+ guardrailLog: t.object<GuardrailLogEntry[]>(),
49
+ allowedTopics: t.object<string[]>(),
50
+ },
51
+ derivations: {
52
+ messageCount: t.number(),
53
+ blockedCount: t.number(),
54
+ allowedCount: t.number(),
55
+ blockRate: t.string(),
56
+ canSend: t.boolean(),
57
+ lastMessageBlocked: t.boolean(),
58
+ },
59
+ events: {
60
+ send: {},
61
+ clear: {},
62
+ setInput: { value: t.string() },
63
+ toggleTopic: { topic: t.string() },
64
+ },
65
+ requirements: {
66
+ BLOCK_MESSAGE: {
67
+ reason: t.string(),
68
+ guardrailName: t.string(),
69
+ },
70
+ ALLOW_MESSAGE: {},
71
+ },
72
+ } satisfies ModuleSchema;
73
+
74
+ // ============================================================================
75
+ // Module
76
+ // ============================================================================
77
+
78
+ export const topicGuardModule = createModule("topic-guard", {
79
+ schema: topicGuardSchema,
80
+
81
+ init: (facts) => {
82
+ facts.input = "";
83
+ facts.messages = [];
84
+ facts.isProcessing = false;
85
+ facts.lastGuardrailResult = null;
86
+ facts.guardrailLog = [];
87
+ facts.allowedTopics = ["product", "billing", "support", "technical"];
88
+ },
89
+
90
+ // ============================================================================
91
+ // Derivations
92
+ // ============================================================================
93
+
94
+ derive: {
95
+ messageCount: (facts) => {
96
+ return (facts.messages as ChatMessage[]).filter((m) => m.role === "user")
97
+ .length;
98
+ },
99
+
100
+ blockedCount: (facts) => {
101
+ return (facts.messages as ChatMessage[]).filter(
102
+ (m) => m.role === "user" && m.blocked,
103
+ ).length;
104
+ },
105
+
106
+ allowedCount: (facts) => {
107
+ return (facts.messages as ChatMessage[]).filter(
108
+ (m) => m.role === "user" && !m.blocked,
109
+ ).length;
110
+ },
111
+
112
+ blockRate: (facts, derive) => {
113
+ const total = derive.messageCount as number;
114
+ if (total === 0) {
115
+ return "0%";
116
+ }
117
+ const blocked = derive.blockedCount as number;
118
+ const rate = Math.round((blocked / total) * 100);
119
+
120
+ return `${rate}%`;
121
+ },
122
+
123
+ canSend: (facts) => {
124
+ return (
125
+ (facts.input as string).trim().length > 0 &&
126
+ !(facts.isProcessing as boolean)
127
+ );
128
+ },
129
+
130
+ lastMessageBlocked: (facts) => {
131
+ const msgs = facts.messages as ChatMessage[];
132
+ if (msgs.length === 0) {
133
+ return false;
134
+ }
135
+
136
+ return msgs[msgs.length - 1].blocked;
137
+ },
138
+ },
139
+
140
+ // ============================================================================
141
+ // Events
142
+ // ============================================================================
143
+
144
+ events: {
145
+ send: (facts) => {
146
+ const text = (facts.input as string).trim();
147
+ if (text.length === 0 || facts.isProcessing) {
148
+ return;
149
+ }
150
+
151
+ // Add user message
152
+ const messages = [...(facts.messages as ChatMessage[])];
153
+ messages.push({ role: "user", text, blocked: false });
154
+ facts.messages = messages;
155
+
156
+ // Run guardrails
157
+ const keywordResult = checkKeywordGuardrail(text);
158
+ if (keywordResult.blocked) {
159
+ facts.lastGuardrailResult = keywordResult;
160
+ facts.isProcessing = true;
161
+ facts.input = "";
162
+
163
+ return;
164
+ }
165
+
166
+ const classifierResult = checkTopicClassifier(
167
+ text,
168
+ facts.allowedTopics as string[],
169
+ );
170
+ facts.lastGuardrailResult = classifierResult;
171
+ facts.isProcessing = true;
172
+ facts.input = "";
173
+ },
174
+
175
+ clear: (facts) => {
176
+ facts.messages = [];
177
+ facts.guardrailLog = [];
178
+ facts.lastGuardrailResult = null;
179
+ facts.isProcessing = false;
180
+ },
181
+
182
+ setInput: (facts, { value }) => {
183
+ facts.input = value;
184
+ },
185
+
186
+ toggleTopic: (facts, { topic }) => {
187
+ const topics = [...(facts.allowedTopics as string[])];
188
+ const idx = topics.indexOf(topic);
189
+ if (idx >= 0) {
190
+ topics.splice(idx, 1);
191
+ } else {
192
+ topics.push(topic);
193
+ }
194
+ facts.allowedTopics = topics;
195
+ },
196
+ },
197
+
198
+ // ============================================================================
199
+ // Constraints
200
+ // ============================================================================
201
+
202
+ constraints: {
203
+ offTopicDetected: {
204
+ priority: 100,
205
+ when: (facts) => {
206
+ const result = facts.lastGuardrailResult as GuardrailResult | null;
207
+
208
+ return result?.blocked === true && (facts.isProcessing as boolean);
209
+ },
210
+ require: (facts) => {
211
+ const result = facts.lastGuardrailResult as GuardrailResult;
212
+
213
+ return {
214
+ type: "BLOCK_MESSAGE",
215
+ reason: result.reason,
216
+ guardrailName: result.guardrailName,
217
+ };
218
+ },
219
+ },
220
+
221
+ onTopicConfirmed: {
222
+ priority: 90,
223
+ when: (facts) => {
224
+ const result = facts.lastGuardrailResult as GuardrailResult | null;
225
+
226
+ return result?.blocked === false && (facts.isProcessing as boolean);
227
+ },
228
+ require: () => ({
229
+ type: "ALLOW_MESSAGE",
230
+ }),
231
+ },
232
+ },
233
+
234
+ // ============================================================================
235
+ // Resolvers
236
+ // ============================================================================
237
+
238
+ resolvers: {
239
+ blockMessage: {
240
+ requirement: "BLOCK_MESSAGE",
241
+ resolve: async (req, context) => {
242
+ const messages = [...(context.facts.messages as ChatMessage[])];
243
+ // Mark the last user message as blocked
244
+ const lastUserIdx = messages.length - 1;
245
+ if (lastUserIdx >= 0) {
246
+ messages[lastUserIdx] = {
247
+ ...messages[lastUserIdx],
248
+ blocked: true,
249
+ guardrail: req.guardrailName,
250
+ };
251
+ }
252
+ // Add system rejection message
253
+ messages.push({
254
+ role: "system",
255
+ text: "I can only help with product-related questions.",
256
+ blocked: true,
257
+ guardrail: req.guardrailName,
258
+ });
259
+ context.facts.messages = messages;
260
+ context.facts.isProcessing = false;
261
+ },
262
+ },
263
+
264
+ allowMessage: {
265
+ requirement: "ALLOW_MESSAGE",
266
+ resolve: async (_req, context) => {
267
+ const messages = [...(context.facts.messages as ChatMessage[])];
268
+ const lastUserMsg = messages.filter((m) => m.role === "user").pop();
269
+ const responseText = getMockAgentResponse(lastUserMsg?.text ?? "");
270
+ messages.push({
271
+ role: "agent",
272
+ text: responseText,
273
+ blocked: false,
274
+ });
275
+ context.facts.messages = messages;
276
+ context.facts.isProcessing = false;
277
+ },
278
+ },
279
+ },
280
+
281
+ // ============================================================================
282
+ // Effects
283
+ // ============================================================================
284
+
285
+ effects: {
286
+ logGuardrailResult: {
287
+ deps: ["lastGuardrailResult"],
288
+ run: (facts) => {
289
+ const result = facts.lastGuardrailResult as GuardrailResult | null;
290
+ if (!result) {
291
+ return;
292
+ }
293
+
294
+ const msgs = facts.messages as ChatMessage[];
295
+ const lastUserMsg = [...msgs].reverse().find((m) => m.role === "user");
296
+ const log = [...(facts.guardrailLog as GuardrailLogEntry[])];
297
+ log.push({
298
+ timestamp: Date.now(),
299
+ input: lastUserMsg?.text ?? "",
300
+ result,
301
+ });
302
+ facts.guardrailLog = log;
303
+ },
304
+ },
305
+ },
306
+ });
@@ -0,0 +1,333 @@
1
+ // Example: url-sync
2
+ // Source: examples/url-sync/src/url-sync.ts
3
+ // Pure module file — no DOM wiring
4
+
5
+ /**
6
+ * URL Sync — Directive Modules
7
+ *
8
+ * Two modules that synchronize URL query parameters with product filtering:
9
+ * - **url module**: Reads/writes URL params, dispatches filter changes
10
+ * - **products module**: Fetches filtered products via cross-module constraints
11
+ *
12
+ * Demonstrates bidirectional URL sync (popstate ↔ replaceState), cross-module
13
+ * constraints, and resolver-driven data fetching with mock delay.
14
+ */
15
+
16
+ import {
17
+ type ModuleSchema,
18
+ createModule,
19
+ createSystem,
20
+ t,
21
+ } from "@directive-run/core";
22
+ import { devtoolsPlugin } from "@directive-run/core/plugins";
23
+ import { type Product, allProducts, filterProducts } from "./mock-products.js";
24
+
25
+ // ============================================================================
26
+ // URL Module — Schema
27
+ // ============================================================================
28
+
29
+ export const urlSchema = {
30
+ facts: {
31
+ search: t.string(),
32
+ category: t.string(),
33
+ sortBy: t.string<"newest" | "price-asc" | "price-desc">(),
34
+ page: t.number(),
35
+ syncingFromUrl: t.boolean(),
36
+ },
37
+ derivations: {},
38
+ events: {
39
+ setSearch: { value: t.string() },
40
+ setCategory: { value: t.string() },
41
+ setSortBy: { value: t.string() },
42
+ setPage: { value: t.number() },
43
+ syncFromUrl: {
44
+ search: t.string(),
45
+ category: t.string(),
46
+ sortBy: t.string(),
47
+ page: t.number(),
48
+ },
49
+ syncComplete: {},
50
+ },
51
+ requirements: {},
52
+ } satisfies ModuleSchema;
53
+
54
+ // ============================================================================
55
+ // URL Module — Helpers
56
+ // ============================================================================
57
+
58
+ function readUrlParams(): {
59
+ search: string;
60
+ category: string;
61
+ sortBy: string;
62
+ page: number;
63
+ } {
64
+ const params = new URLSearchParams(window.location.search);
65
+
66
+ return {
67
+ search: params.get("q") ?? "",
68
+ category: params.get("cat") ?? "",
69
+ sortBy: params.get("sort") ?? "newest",
70
+ page: Math.max(1, Number.parseInt(params.get("page") ?? "1", 10) || 1),
71
+ };
72
+ }
73
+
74
+ // ============================================================================
75
+ // URL Module
76
+ // ============================================================================
77
+
78
+ export const urlModule = createModule("url", {
79
+ schema: urlSchema,
80
+
81
+ init: (facts) => {
82
+ const params = readUrlParams();
83
+ facts.search = params.search;
84
+ facts.category = params.category;
85
+ facts.sortBy = params.sortBy;
86
+ facts.page = params.page;
87
+ facts.syncingFromUrl = false;
88
+ },
89
+
90
+ // ============================================================================
91
+ // Events
92
+ // ============================================================================
93
+
94
+ events: {
95
+ setSearch: (facts, { value }) => {
96
+ facts.search = value;
97
+ facts.page = 1;
98
+ },
99
+
100
+ setCategory: (facts, { value }) => {
101
+ facts.category = value;
102
+ facts.page = 1;
103
+ },
104
+
105
+ setSortBy: (facts, { value }) => {
106
+ facts.sortBy = value;
107
+ facts.page = 1;
108
+ },
109
+
110
+ setPage: (facts, { value }) => {
111
+ facts.page = value;
112
+ },
113
+
114
+ syncFromUrl: (facts, { search, category, sortBy, page }) => {
115
+ facts.syncingFromUrl = true;
116
+ facts.search = search;
117
+ facts.category = category;
118
+ facts.sortBy = sortBy;
119
+ facts.page = page;
120
+ },
121
+
122
+ syncComplete: (facts) => {
123
+ facts.syncingFromUrl = false;
124
+ },
125
+ },
126
+
127
+ // ============================================================================
128
+ // Effects
129
+ // ============================================================================
130
+
131
+ effects: {
132
+ urlToState: {
133
+ run: () => {
134
+ const handler = () => {
135
+ const params = readUrlParams();
136
+ system.events.url.syncFromUrl({
137
+ search: params.search,
138
+ category: params.category,
139
+ sortBy: params.sortBy,
140
+ page: params.page,
141
+ });
142
+ system.events.url.syncComplete();
143
+ };
144
+
145
+ window.addEventListener("popstate", handler);
146
+
147
+ return () => {
148
+ window.removeEventListener("popstate", handler);
149
+ };
150
+ },
151
+ },
152
+
153
+ stateToUrl: {
154
+ deps: ["search", "category", "sortBy", "page"],
155
+ run: (facts) => {
156
+ if (facts.syncingFromUrl) {
157
+ return;
158
+ }
159
+
160
+ const params = new URLSearchParams();
161
+
162
+ if (facts.search !== "") {
163
+ params.set("q", facts.search as string);
164
+ }
165
+ if (facts.category !== "" && facts.category !== "all") {
166
+ params.set("cat", facts.category as string);
167
+ }
168
+ if (facts.sortBy !== "newest") {
169
+ params.set("sort", facts.sortBy as string);
170
+ }
171
+ if ((facts.page as number) > 1) {
172
+ params.set("page", String(facts.page));
173
+ }
174
+
175
+ const search = params.toString();
176
+ const newUrl = search
177
+ ? `${window.location.pathname}?${search}`
178
+ : window.location.pathname;
179
+
180
+ if (newUrl !== `${window.location.pathname}${window.location.search}`) {
181
+ history.replaceState(null, "", newUrl);
182
+ }
183
+ },
184
+ },
185
+ },
186
+ });
187
+
188
+ // ============================================================================
189
+ // Products Module — Schema
190
+ // ============================================================================
191
+
192
+ export const productsSchema = {
193
+ facts: {
194
+ items: t.object<Product[]>(),
195
+ totalItems: t.number(),
196
+ isLoading: t.boolean(),
197
+ itemsPerPage: t.number(),
198
+ },
199
+ derivations: {
200
+ totalPages: t.number(),
201
+ currentPageDisplay: t.string(),
202
+ },
203
+ events: {
204
+ setItemsPerPage: { value: t.number() },
205
+ },
206
+ requirements: {
207
+ FETCH_PRODUCTS: {
208
+ search: t.string(),
209
+ category: t.string(),
210
+ sortBy: t.string(),
211
+ page: t.number(),
212
+ itemsPerPage: t.number(),
213
+ },
214
+ },
215
+ } satisfies ModuleSchema;
216
+
217
+ // ============================================================================
218
+ // Products Module
219
+ // ============================================================================
220
+
221
+ export const productsModule = createModule("products", {
222
+ schema: productsSchema,
223
+
224
+ crossModuleDeps: { url: urlSchema },
225
+
226
+ init: (facts) => {
227
+ facts.items = [];
228
+ facts.totalItems = 0;
229
+ facts.isLoading = false;
230
+ facts.itemsPerPage = 10;
231
+ },
232
+
233
+ // ============================================================================
234
+ // Derivations
235
+ // ============================================================================
236
+
237
+ derive: {
238
+ totalPages: (facts) => {
239
+ if (facts.self.totalItems === 0) {
240
+ return 0;
241
+ }
242
+
243
+ return Math.ceil(facts.self.totalItems / facts.self.itemsPerPage);
244
+ },
245
+
246
+ currentPageDisplay: (facts) => {
247
+ const total = facts.self.totalItems;
248
+ if (total === 0) {
249
+ return "No results";
250
+ }
251
+
252
+ const page = facts.url.page;
253
+ const perPage = facts.self.itemsPerPage;
254
+ const start = (page - 1) * perPage + 1;
255
+ const end = Math.min(page * perPage, total);
256
+
257
+ return `${start}\u2013${end} of ${total}`;
258
+ },
259
+ },
260
+
261
+ // ============================================================================
262
+ // Events
263
+ // ============================================================================
264
+
265
+ events: {
266
+ setItemsPerPage: (facts, { value }) => {
267
+ facts.itemsPerPage = value;
268
+ },
269
+ },
270
+
271
+ // ============================================================================
272
+ // Constraints
273
+ // ============================================================================
274
+
275
+ constraints: {
276
+ fetchProducts: {
277
+ priority: 100,
278
+ when: () => true,
279
+ require: (facts) => ({
280
+ type: "FETCH_PRODUCTS",
281
+ search: facts.url.search,
282
+ category: facts.url.category,
283
+ sortBy: facts.url.sortBy,
284
+ page: facts.url.page,
285
+ itemsPerPage: facts.self.itemsPerPage,
286
+ }),
287
+ },
288
+ },
289
+
290
+ // ============================================================================
291
+ // Resolvers
292
+ // ============================================================================
293
+
294
+ resolvers: {
295
+ fetchProducts: {
296
+ requirement: "FETCH_PRODUCTS",
297
+ key: (req) =>
298
+ `fetch-${req.search}-${req.category}-${req.sortBy}-${req.page}-${req.itemsPerPage}`,
299
+ timeout: 10000,
300
+ resolve: async (req, context) => {
301
+ context.facts.isLoading = true;
302
+
303
+ // Simulate network delay
304
+ await new Promise((resolve) => setTimeout(resolve, 300));
305
+
306
+ const result = filterProducts(allProducts, {
307
+ search: req.search,
308
+ category: req.category,
309
+ sortBy: req.sortBy,
310
+ page: req.page,
311
+ itemsPerPage: req.itemsPerPage,
312
+ });
313
+
314
+ context.facts.items = result.items;
315
+ context.facts.totalItems = result.totalItems;
316
+ context.facts.isLoading = false;
317
+ },
318
+ },
319
+ },
320
+ });
321
+
322
+ // ============================================================================
323
+ // System
324
+ // ============================================================================
325
+
326
+ export const system = createSystem({
327
+ modules: {
328
+ url: urlModule,
329
+ products: productsModule,
330
+ },
331
+ debug: { runHistory: true },
332
+ plugins: [devtoolsPlugin({ name: "url-sync" })],
333
+ });