@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,176 @@
1
+ // Example: contact-form
2
+ // Source: examples/contact-form/src/main.ts
3
+ // Extracted for AI rules — DOM wiring stripped
4
+
5
+ /**
6
+ * Contact Form — DOM Rendering & System Wiring
7
+ *
8
+ * Creates the Directive system, subscribes to state changes,
9
+ * renders the form and event timeline.
10
+ */
11
+
12
+ import {
13
+ type ModuleSchema,
14
+ createModule,
15
+ createSystem,
16
+ t,
17
+ } from "@directive-run/core";
18
+ import { devtoolsPlugin } from "@directive-run/core/plugins";
19
+
20
+ // ============================================================================
21
+ // Constants
22
+ // ============================================================================
23
+
24
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
25
+ const RATE_LIMIT_MS = 10_000; // 10 seconds (shorter for demo)
26
+
27
+ // ============================================================================
28
+ // Timeline
29
+ // ============================================================================
30
+
31
+ interface TimelineEntry {
32
+ time: number;
33
+ event: string;
34
+ detail: string;
35
+ type: string;
36
+ }
37
+
38
+ const timeline: TimelineEntry[] = [];
39
+
40
+ function addTimelineEntry(event: string, detail: string, type: string) {
41
+ timeline.unshift({ time: Date.now(), event, detail, type });
42
+ }
43
+
44
+ function log(msg: string) {
45
+ console.log(`[contact-form] ${msg}`);
46
+
47
+ // Classify and add to timeline
48
+ if (msg.startsWith("Sending:")) {
49
+ addTimelineEntry("submit", msg.replace("Sending: ", ""), "submit");
50
+ } else if (msg.includes("succeeded")) {
51
+ addTimelineEntry("success", msg, "submit");
52
+ } else if (msg.includes("failed")) {
53
+ addTimelineEntry("error", msg, "error");
54
+ } else if (msg.startsWith("Status:")) {
55
+ addTimelineEntry("status", msg.replace("Status: ", ""), "field");
56
+ } else if (msg.includes("Auto-resetting")) {
57
+ addTimelineEntry("auto-reset", msg, "reset");
58
+ } else if (msg === "Form reset") {
59
+ addTimelineEntry("reset", "Form cleared", "reset");
60
+ } else if (msg.includes("ready")) {
61
+ addTimelineEntry("init", msg, "field");
62
+ }
63
+ }
64
+
65
+ // ============================================================================
66
+ // Schema
67
+ // ============================================================================
68
+
69
+ const schema = {
70
+ facts: {
71
+ name: t.string(),
72
+ email: t.string(),
73
+ subject: t.string(),
74
+ message: t.string(),
75
+ touched: t.object<Record<string, boolean>>(),
76
+ status: t.string<"idle" | "submitting" | "success" | "error">(),
77
+ errorMessage: t.string(),
78
+ lastSubmittedAt: t.number(),
79
+ submissionCount: t.number(),
80
+ },
81
+ derivations: {
82
+ nameError: t.string(),
83
+ emailError: t.string(),
84
+ subjectError: t.string(),
85
+ messageError: t.string(),
86
+ isValid: t.boolean(),
87
+ canSubmit: t.boolean(),
88
+ messageCharCount: t.number(),
89
+ },
90
+ events: {
91
+ updateField: { field: t.string(), value: t.string() },
92
+ touchField: { field: t.string() },
93
+ submit: {},
94
+ reset: {},
95
+ },
96
+ requirements: {
97
+ SEND_MESSAGE: {},
98
+ RESET_AFTER_DELAY: {},
99
+ },
100
+ } satisfies ModuleSchema;
101
+
102
+ // ============================================================================
103
+ // Module
104
+ // ============================================================================
105
+
106
+
107
+ // ============================================================================
108
+ // System
109
+ // ============================================================================
110
+
111
+ const system = createSystem({
112
+ module: contactForm,
113
+ debug: { runHistory: true },
114
+ plugins: [devtoolsPlugin({ name: "contact-form" })],
115
+ });
116
+ system.start();
117
+
118
+ // ============================================================================
119
+ // DOM References
120
+ // ============================================================================
121
+
122
+ // Form inputs
123
+
124
+ // Timeline
125
+
126
+ // ============================================================================
127
+ // Input Handlers
128
+ // ============================================================================
129
+
130
+ for (const [el, field] of [
131
+ [nameInput, "name"],
132
+ [emailInput, "email"],
133
+ [subjectInput, "subject"],
134
+ [messageInput, "message"],
135
+ ] as const) {
136
+ }
137
+
138
+
139
+ // ============================================================================
140
+ // Render
141
+ // ============================================================================
142
+
143
+ function escapeHtml(text: string): string {
144
+
145
+ return div.innerHTML;
146
+ }
147
+
148
+
149
+ // Subscribe to all relevant facts and derivations
150
+ system.subscribe(
151
+ [
152
+ "name",
153
+ "email",
154
+ "subject",
155
+ "message",
156
+ "touched",
157
+ "status",
158
+ "errorMessage",
159
+ "lastSubmittedAt",
160
+ "submissionCount",
161
+ "nameError",
162
+ "emailError",
163
+ "subjectError",
164
+ "messageError",
165
+ "isValid",
166
+ "canSubmit",
167
+ "messageCharCount",
168
+ ],
169
+ render,
170
+ );
171
+
172
+ // Initial render
173
+ render();
174
+ log("Contact form ready. Fill in all fields and submit.");
175
+
176
+ // Signal to tests that initialization is complete
@@ -0,0 +1,393 @@
1
+ // Example: counter
2
+ // Source: examples/counter/src/main.ts
3
+ // Extracted for AI rules — DOM wiring stripped
4
+
5
+ /**
6
+ * Number Match — DOM Rendering & System Wiring
7
+ *
8
+ * Creates the Directive system, subscribes to state changes,
9
+ * renders the game grid and event timeline.
10
+ */
11
+
12
+ import {
13
+ type ModuleSchema,
14
+ createModule,
15
+ createSystem,
16
+ t,
17
+ } from "@directive-run/core";
18
+ import { devtoolsPlugin } from "@directive-run/core/plugins";
19
+
20
+ // ============================================================================
21
+ // Types
22
+ // ============================================================================
23
+
24
+ interface Tile {
25
+ id: string;
26
+ value: number;
27
+ }
28
+
29
+ interface TimelineEntry {
30
+ time: number;
31
+ event: string;
32
+ detail: string;
33
+ type: string;
34
+ }
35
+
36
+ // Create a pool of numbered tiles (1-9, four of each = 36 tiles)
37
+ function createPool(): Tile[] {
38
+ const tiles: Tile[] = [];
39
+ let id = 0;
40
+ for (let copy = 0; copy < 4; copy++) {
41
+ for (let value = 1; value <= 9; value++) {
42
+ tiles.push({ id: `t${id++}`, value });
43
+ }
44
+ }
45
+ // Shuffle
46
+ for (let i = tiles.length - 1; i > 0; i--) {
47
+ const j = Math.floor(Math.random() * (i + 1));
48
+ [tiles[i], tiles[j]] = [tiles[j], tiles[i]];
49
+ }
50
+
51
+ return tiles;
52
+ }
53
+
54
+ // ============================================================================
55
+ // Timeline
56
+ // ============================================================================
57
+
58
+ const timeline: TimelineEntry[] = [];
59
+
60
+ function log(msg: string) {
61
+ console.log(`[NumberMatch] ${msg}`);
62
+
63
+ // Classify and add significant events to the timeline
64
+ let event = "";
65
+ let detail = "";
66
+ let type = "info";
67
+
68
+ if (msg.startsWith("EVENT selectTile")) {
69
+ event = "tile selected";
70
+ const match = msg.match(/selectTile: (t\d+)/);
71
+ detail = match ? match[1] : "";
72
+ type = "selection";
73
+ } else if (msg.includes("pairAddsTen: TRUE")) {
74
+ event = "match found";
75
+ const match = msg.match(/\((.+)\)/);
76
+ detail = match ? match[1] : "";
77
+ type = "match";
78
+ } else if (msg === "RESOLVER removeTiles: DONE") {
79
+ event = "tiles removed";
80
+ detail = "";
81
+ type = "match";
82
+ } else if (msg.includes("refillTable: DONE")) {
83
+ event = "refill";
84
+ const match = msg.match(/table now: (\d+)/);
85
+ detail = match ? `table: ${match[1]} tiles` : "";
86
+ type = "refill";
87
+ } else if (msg.startsWith("RESOLVER endGame:")) {
88
+ event = "game over";
89
+ detail = msg.replace("RESOLVER endGame: ", "");
90
+ type = "gameover";
91
+ } else if (msg.includes("New game") || msg.includes("Game started")) {
92
+ event = "new game";
93
+ detail = msg;
94
+ type = "newgame";
95
+ } else {
96
+ // Skip verbose intermediate messages (RESOLVER steps, CONSTRAINT produce)
97
+ return;
98
+ }
99
+
100
+ timeline.unshift({ time: Date.now(), event, detail, type });
101
+ }
102
+
103
+ // ============================================================================
104
+ // Schema - same structure as eleven-up
105
+ // ============================================================================
106
+
107
+ const schema = {
108
+ facts: {
109
+ pool: t.object<Tile[]>(),
110
+ table: t.object<Tile[]>(),
111
+ removed: t.object<Tile[]>(),
112
+ selected: t.object<string[]>(),
113
+ message: t.string(),
114
+ moveCount: t.number(),
115
+ gameOver: t.boolean(),
116
+ },
117
+ derivations: {
118
+ poolCount: t.number(),
119
+ removedCount: t.number(),
120
+ selectedTiles: t.object<Tile[]>(),
121
+ hasValidMoves: t.boolean(),
122
+ },
123
+ events: {
124
+ newGame: {},
125
+ selectTile: { tileId: t.string() },
126
+ deselectTile: { tileId: t.string() },
127
+ clearSelection: {},
128
+ },
129
+ requirements: {
130
+ REMOVE_TILES: { tileIds: t.object<string[]>() },
131
+ REFILL_TABLE: { count: t.number() },
132
+ END_GAME: { reason: t.string() },
133
+ },
134
+ } satisfies ModuleSchema;
135
+
136
+ // ============================================================================
137
+ // Module
138
+ // ============================================================================
139
+
140
+ const numberMatch = createModule("number-match", {
141
+ schema,
142
+
143
+ init: (facts) => {
144
+ const pool = createPool();
145
+ facts.pool = pool.slice(9);
146
+ facts.table = pool.slice(0, 9);
147
+ facts.removed = [];
148
+ facts.selected = [];
149
+ facts.message = "Select two numbers that add to 10";
150
+ facts.moveCount = 0;
151
+ facts.gameOver = false;
152
+ },
153
+
154
+ derive: {
155
+ poolCount: (facts) => facts.pool.length,
156
+ removedCount: (facts) => facts.removed.length,
157
+ selectedTiles: (facts) =>
158
+ facts.table.filter((tile: Tile) => facts.selected.includes(tile.id)),
159
+ hasValidMoves: (facts) => {
160
+ const nums = facts.table.map((t: Tile) => t.value);
161
+ for (let i = 0; i < nums.length; i++) {
162
+ for (let j = i + 1; j < nums.length; j++) {
163
+ if (nums[i] + nums[j] === 10) return true;
164
+ }
165
+ }
166
+ return false;
167
+ },
168
+ },
169
+
170
+ events: {
171
+ newGame: (facts) => {
172
+ const pool = createPool();
173
+ facts.pool = pool.slice(9);
174
+ facts.table = pool.slice(0, 9);
175
+ facts.removed = [];
176
+ facts.selected = [];
177
+ facts.message = "New game! Select two numbers that add to 10";
178
+ facts.moveCount = 0;
179
+ facts.gameOver = false;
180
+ },
181
+ selectTile: (facts, { tileId }) => {
182
+ if (!facts.selected.includes(tileId) && !facts.gameOver) {
183
+ facts.selected = [...facts.selected, tileId];
184
+ log(`EVENT selectTile: ${tileId}, selected now: [${facts.selected}]`);
185
+ }
186
+ },
187
+ deselectTile: (facts, { tileId }) => {
188
+ facts.selected = facts.selected.filter((id: string) => id !== tileId);
189
+ },
190
+ clearSelection: (facts) => {
191
+ facts.selected = [];
192
+ },
193
+ },
194
+
195
+ // ============================================================================
196
+ // Constraints - same pattern as eleven-up
197
+ // ============================================================================
198
+ constraints: {
199
+ // When two selected tiles add to 10 -> remove them
200
+ pairAddsTen: {
201
+ priority: 100,
202
+ when: (facts) => {
203
+ if (facts.gameOver) return false;
204
+ const selected = facts.table.filter((tile: Tile) =>
205
+ facts.selected.includes(tile.id),
206
+ );
207
+ if (selected.length !== 2) return false;
208
+ const result = selected[0].value + selected[1].value === 10;
209
+ if (result)
210
+ log(
211
+ `CONSTRAINT pairAddsTen: TRUE (${selected[0].value} + ${selected[1].value})`,
212
+ );
213
+ return result;
214
+ },
215
+ require: (facts) => {
216
+ log("CONSTRAINT pairAddsTen: producing REMOVE_TILES");
217
+ return {
218
+ type: "REMOVE_TILES",
219
+ tileIds: [...facts.selected],
220
+ };
221
+ },
222
+ },
223
+
224
+ // Refill table when tiles are removed
225
+ refillTable: {
226
+ priority: 50,
227
+ when: (facts) => {
228
+ const result =
229
+ !facts.gameOver && facts.table.length < 9 && facts.pool.length > 0;
230
+ if (result)
231
+ log(
232
+ `CONSTRAINT refillTable: TRUE (table: ${facts.table.length}, pool: ${facts.pool.length})`,
233
+ );
234
+ return result;
235
+ },
236
+ require: (facts) => {
237
+ const count = Math.min(9 - facts.table.length, facts.pool.length);
238
+ log(`CONSTRAINT refillTable: producing REFILL_TABLE count=${count}`);
239
+ return { type: "REFILL_TABLE", count };
240
+ },
241
+ },
242
+
243
+ // No moves left -> game over
244
+ noMovesLeft: {
245
+ priority: 190,
246
+ when: (facts) => {
247
+ if (facts.gameOver) return false;
248
+ if (facts.table.length === 0) return false;
249
+ if (facts.pool.length > 0) return false;
250
+ const nums = facts.table.map((t: Tile) => t.value);
251
+ for (let i = 0; i < nums.length; i++) {
252
+ for (let j = i + 1; j < nums.length; j++) {
253
+ if (nums[i] + nums[j] === 10) return false;
254
+ }
255
+ }
256
+ log("CONSTRAINT noMovesLeft: TRUE");
257
+ return true;
258
+ },
259
+ require: (facts) => ({
260
+ type: "END_GAME",
261
+ reason: `Game over! Removed ${facts.removed.length} of 36 tiles.`,
262
+ }),
263
+ },
264
+
265
+ // Win condition
266
+ allCleared: {
267
+ priority: 200,
268
+ when: (facts) => {
269
+ const result =
270
+ !facts.gameOver &&
271
+ facts.table.length === 0 &&
272
+ facts.pool.length === 0;
273
+ if (result) log("CONSTRAINT allCleared: TRUE");
274
+ return result;
275
+ },
276
+ require: (facts) => ({
277
+ type: "END_GAME",
278
+ reason: `You win! Cleared all tiles in ${facts.moveCount} moves!`,
279
+ }),
280
+ },
281
+ },
282
+
283
+ // ============================================================================
284
+ // Resolvers - same multi-fact mutation pattern as eleven-up
285
+ // ============================================================================
286
+ resolvers: {
287
+ removeTiles: {
288
+ requirement: "REMOVE_TILES",
289
+ resolve: async (req, context) => {
290
+ log("RESOLVER removeTiles: START");
291
+ const tilesToRemove = context.facts.table.filter((tile: Tile) =>
292
+ req.tileIds.includes(tile.id),
293
+ );
294
+
295
+ // Multiple fact mutations
296
+ log("RESOLVER removeTiles: setting table");
297
+ context.facts.table = context.facts.table.filter(
298
+ (tile: Tile) => !req.tileIds.includes(tile.id),
299
+ );
300
+ log("RESOLVER removeTiles: setting removed");
301
+ context.facts.removed = [...context.facts.removed, ...tilesToRemove];
302
+ log("RESOLVER removeTiles: clearing selected");
303
+ context.facts.selected = [];
304
+ log("RESOLVER removeTiles: incrementing moveCount");
305
+ context.facts.moveCount++;
306
+ log("RESOLVER removeTiles: setting message");
307
+ context.facts.message = `Removed ${tilesToRemove[0].value} + ${tilesToRemove[1].value} = 10!`;
308
+ log("RESOLVER removeTiles: DONE");
309
+ },
310
+ },
311
+
312
+ refillTable: {
313
+ requirement: "REFILL_TABLE",
314
+ resolve: async (req, context) => {
315
+ log(`RESOLVER refillTable: START (count: ${req.count})`);
316
+ const newTiles = context.facts.pool.slice(0, req.count);
317
+ context.facts.pool = context.facts.pool.slice(req.count);
318
+ context.facts.table = [...context.facts.table, ...newTiles];
319
+ log(
320
+ `RESOLVER refillTable: DONE (table now: ${context.facts.table.length})`,
321
+ );
322
+ },
323
+ },
324
+
325
+ endGame: {
326
+ requirement: "END_GAME",
327
+ resolve: async (req, context) => {
328
+ log(`RESOLVER endGame: ${req.reason}`);
329
+ context.facts.gameOver = true;
330
+ context.facts.message = req.reason;
331
+ },
332
+ },
333
+ },
334
+ });
335
+
336
+ // ============================================================================
337
+ // System
338
+ // ============================================================================
339
+
340
+ const system = createSystem({
341
+ module: numberMatch,
342
+ plugins: [devtoolsPlugin({ name: "number-match" })],
343
+ debug: { timeTravel: true, runHistory: true },
344
+ });
345
+ system.start();
346
+
347
+ // ============================================================================
348
+ // DOM References
349
+ // ============================================================================
350
+
351
+ // Stats
352
+
353
+ // Timeline
354
+
355
+ // ============================================================================
356
+ // Render
357
+ // ============================================================================
358
+
359
+ function escapeHtml(text: string): string {
360
+
361
+ return div.innerHTML;
362
+ }
363
+
364
+
365
+ // ============================================================================
366
+ // Subscribe
367
+ // ============================================================================
368
+
369
+ system.subscribe(
370
+ [
371
+ "table",
372
+ "selected",
373
+ "pool",
374
+ "removed",
375
+ "moveCount",
376
+ "message",
377
+ "gameOver",
378
+ "poolCount",
379
+ "removedCount",
380
+ "selectedTiles",
381
+ "hasValidMoves",
382
+ ],
383
+ render,
384
+ );
385
+
386
+ // Button handlers
387
+
388
+
389
+ // Initial render
390
+ render();
391
+ log("Game started. Select two numbers that add to 10.");
392
+
393
+ // Signal to tests that initialization is complete