@directive-run/knowledge 1.14.0 → 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 (43) hide show
  1. package/README.md +50 -15
  2. package/ai/ai-adapters.md +7 -0
  3. package/ai/ai-agents-streaming.md +187 -149
  4. package/ai/ai-budget-resilience.md +305 -132
  5. package/ai/ai-communication.md +220 -197
  6. package/ai/ai-debug-observability.md +259 -173
  7. package/ai/ai-guardrails-memory.md +191 -153
  8. package/ai/ai-mcp-rag.md +204 -199
  9. package/ai/ai-multi-agent.md +254 -153
  10. package/ai/ai-orchestrator.md +353 -114
  11. package/ai/ai-security.md +287 -180
  12. package/ai/ai-tasks.md +8 -1
  13. package/ai/ai-testing-evals.md +363 -256
  14. package/core/anti-patterns.md +18 -2
  15. package/core/constraints.md +13 -2
  16. package/core/core-patterns.md +12 -0
  17. package/core/error-boundaries.md +8 -0
  18. package/core/history.md +7 -0
  19. package/core/multi-module.md +15 -3
  20. package/core/naming.md +128 -90
  21. package/core/plugins.md +7 -0
  22. package/core/react-adapter.md +256 -174
  23. package/core/resolvers.md +10 -0
  24. package/core/schema-types.md +8 -0
  25. package/core/system-api.md +10 -0
  26. package/core/testing.md +257 -143
  27. package/examples/checkers.ts +15 -16
  28. package/examples/contact-form.ts +2 -2
  29. package/examples/counter-react.ts +1 -1
  30. package/examples/counter-svelte.ts +1 -1
  31. package/examples/counter-vue.ts +1 -1
  32. package/examples/counter.ts +1 -1
  33. package/examples/data-triggers.ts +4 -4
  34. package/examples/feature-flags.ts +2 -2
  35. package/examples/form-wizard.ts +2 -2
  36. package/examples/newsletter.ts +2 -2
  37. package/examples/server.ts +2 -2
  38. package/examples/shopping-cart.ts +1 -1
  39. package/examples/topic-guard.ts +1 -1
  40. package/package.json +3 -3
  41. package/sitemap.md +17 -11
  42. package/examples/debounce-constraints.ts +0 -95
  43. package/examples/multi-module.ts +0 -58
package/core/testing.md CHANGED
@@ -1,245 +1,286 @@
1
1
  # Testing
2
2
 
3
+ > Covers `@directive-run/core/testing` — `createTestSystem`, mock resolvers, assertion helpers, coverage tracker, test observer.
4
+
3
5
  Testing utilities for Directive modules and systems. Import from `@directive-run/core/testing`.
4
6
 
5
- ## Decision Tree: "How should I test this?"
7
+ ## Decision tree
6
8
 
7
9
  ```
8
10
  What are you testing?
9
- ├── A single module in isolation → createTestSystem(module)
10
- ├── Multiple modules together createTestSystemFromModules({ a, b })
11
- ├── A constraint fires correctly Set facts, check requirements
12
- ├── A resolver mutates facts Mock the resolver, dispatch, assert facts
13
- ├── A derivation computes correctly Set facts, read derived value
14
- ├── Async settling behaviorsettleWithFakeTimers(system, vi)
15
- └── An effect runs Set facts, assert side effects
11
+ ├── A single module in isolation → createTestSystem({ module })
12
+ ├── Multiple modules together createTestSystem({ modules: {...} })
13
+ ├── A constraint fires correctly set facts, system.assertRequirement(type)
14
+ ├── A resolver mutates facts mocks.resolvers[TYPE], dispatch, await waitForIdle, assertFactSet
15
+ ├── A derivation computes correctly set facts, read system.derive.x
16
+ ├── Async settling with retry/timeouts await system.waitForIdle() (uses real timers)
17
+ ├── Coverage tracking createCoverageTracker(system)
18
+ └── Observation event capture → createTestObserver(system)
16
19
  ```
17
20
 
18
- ## Creating Test Systems
21
+ ## Creating a test system
22
+
23
+ `createTestSystem` takes an options OBJECT — the same shape as `createSystem` — and adds an optional `mocks.resolvers` map keyed by requirement type. There is no separate `createTestSystemFromModules` factory; the same overload handles both single-module (`module:`) and namespaced (`modules:`) systems.
19
24
 
20
25
  ```typescript
21
- import {
22
- createTestSystem,
23
- createTestSystemFromModules,
24
- mockResolver,
25
- flushMicrotasks,
26
- settleWithFakeTimers,
27
- assertFact,
28
- assertDerivation,
29
- assertRequirement,
30
- } from "@directive-run/core/testing";
31
-
32
- // Single module – same API as createSystem, with testing defaults
33
- // (time-travel off, no plugins, synchronous settling)
34
- const system = createTestSystem(myModule);
26
+ import { createTestSystem } from "@directive-run/core/testing";
35
27
 
36
- // With options
37
- const system = createTestSystem(myModule, {
38
- initialFacts: { count: 5, phase: "loading" },
39
- mockResolvers: [mockResolver("FETCH_USER", async (req, context) => {
40
- context.facts.user = { id: req.userId, name: "Test User" };
41
- })],
28
+ // Single module
29
+ const system = createTestSystem({
30
+ module: counterModule,
31
+ });
32
+
33
+ // Namespaced (multi-module)
34
+ const system = createTestSystem({
35
+ modules: { auth: authModule, cart: cartModule },
36
+ });
37
+
38
+ // With mocks — keyed by requirement type, value is a MockResolverOptions
39
+ const system = createTestSystem({
40
+ module: userModule,
41
+ mocks: {
42
+ resolvers: {
43
+ FETCH_USER: {
44
+ resolve: (req, context) => {
45
+ context.facts.user = { id: req.userId, name: "Test User" };
46
+ },
47
+ },
48
+ },
49
+ },
42
50
  });
51
+ ```
52
+
53
+ A `TestSystem` extends the production `SingleModuleSystem<S>` / `NamespacedSystem<Modules>` with extra fields for observability:
54
+
55
+ ```typescript
56
+ system.waitForIdle(maxWait?); // wait for all in-flight resolvers (default 5000ms; throws on timeout)
57
+ system.eventHistory; // every dispatched event
58
+ system.resolverCalls; // Map<requirementType, Requirement[]>
59
+ system.allRequirements; // every requirement generated (both resolved and pending)
60
+ system.getFactsHistory(); // FactChangeRecord[] since start / last reset
61
+ system.resetFactsHistory();
62
+ ```
63
+
64
+ ## Assertions are METHODS on the test system
65
+
66
+ Assertion helpers live on the test-system instance, not as top-level imports. `assertFact` / `assertDerivation` / `assertRequirement` as top-level imports do not exist.
43
67
 
44
- // Multi-module
45
- const system = createTestSystemFromModules(
46
- { auth: authModule, cart: cartModule },
47
- { mockResolvers: [mockResolver("AUTHENTICATE", async (req, context) => {
48
- context.facts.auth.token = "test-token";
49
- })] },
50
- );
68
+ ```typescript
69
+ system.assertRequirement("FETCH_USER"); // a requirement of this type was generated
70
+ system.assertResolverCalled("FETCH_USER", 1); // the resolver was called N times
71
+ system.assertFactSet("phase", "loaded"); // the fact was set to a specific value
72
+ system.assertFactChanges("count", 3); // the fact was changed N times
51
73
  ```
52
74
 
53
- ## Testing Constraints
75
+ For derivation assertions, just read the derivation value and use your test framework's expect / equality helpers:
76
+
77
+ ```typescript
78
+ import { expect } from "vitest";
79
+ expect(system.derive.isOverBudget).toBe(true);
80
+ ```
54
81
 
55
- Set facts to trigger the constraint's `when()`, then assert the requirement was emitted.
82
+ ## Testing constraints
56
83
 
57
84
  ```typescript
58
85
  import { describe, it, expect } from "vitest";
59
- import { createTestSystem, assertRequirement } from "@directive-run/core/testing";
86
+ import { createTestSystem } from "@directive-run/core/testing";
60
87
 
61
88
  describe("fetchWhenAuth constraint", () => {
62
89
  it("emits FETCH_USER when authenticated without profile", () => {
63
- const system = createTestSystem(userModule);
90
+ const system = createTestSystem({ module: userModule });
64
91
 
65
- // Set facts to satisfy the constraint's when()
66
92
  system.facts.isAuthenticated = true;
67
93
  system.facts.profile = null;
68
94
 
69
- // Assert the requirement was emitted
70
- assertRequirement(system, "FETCH_USER");
95
+ system.assertRequirement("FETCH_USER");
71
96
  });
72
97
 
73
- it("does NOT emit when already has profile", () => {
74
- const system = createTestSystem(userModule, {
98
+ it("does NOT emit when already has a profile", () => {
99
+ const system = createTestSystem({
100
+ module: userModule,
75
101
  initialFacts: {
76
102
  isAuthenticated: true,
77
103
  profile: { id: "1", name: "Alice" },
78
104
  },
79
105
  });
80
106
 
81
- // No requirement should exist
82
107
  const inspection = system.inspect();
83
- const fetchReqs = inspection.requirements.filter(
84
- (r) => r.type === "FETCH_USER",
85
- );
108
+ const fetchReqs = inspection.unmet.filter((r) => r.type === "FETCH_USER");
86
109
  expect(fetchReqs).toHaveLength(0);
87
110
  });
88
111
  });
89
112
  ```
90
113
 
91
- ## Testing Resolvers
114
+ `SystemInspection` exposes `unmet`, `inflight`, and other typed accessors — there is no flat `inspection.requirements` field. Filter the right list (`unmet` for pending requirements, `inflight` for in-progress).
92
115
 
93
- Mock the resolver, trigger a requirement, and assert fact mutations.
116
+ ## Testing resolvers
94
117
 
95
118
  ```typescript
96
119
  describe("fetchUser resolver", () => {
97
120
  it("stores fetched user in facts", async () => {
98
- const system = createTestSystem(userModule, {
99
- mockResolvers: [
100
- mockResolver("FETCH_USER", async (req, context) => {
101
- // Simulate API response
102
- context.facts.user = { id: req.userId, name: "Mocked User" };
103
- context.facts.phase = "loaded";
104
- }),
105
- ],
121
+ const system = createTestSystem({
122
+ module: userModule,
123
+ mocks: {
124
+ resolvers: {
125
+ FETCH_USER: {
126
+ resolve: (req, context) => {
127
+ context.facts.user = { id: req.userId, name: "Mocked User" };
128
+ context.facts.phase = "loaded";
129
+ },
130
+ },
131
+ },
132
+ },
106
133
  });
107
134
 
108
135
  // Trigger the constraint that emits FETCH_USER
109
136
  system.facts.isAuthenticated = true;
110
137
  system.facts.user = null;
111
138
 
112
- // Wait for resolver to complete
113
- await system.settle();
139
+ // Wait for the resolver to finish (default 5s timeout)
140
+ await system.waitForIdle();
114
141
 
115
- assertFact(system, "user", { id: expect.any(String), name: "Mocked User" });
116
- assertFact(system, "phase", "loaded");
142
+ system.assertFactSet("user", { id: expect.anything(), name: "Mocked User" });
143
+ system.assertFactSet("phase", "loaded");
117
144
  });
118
145
  });
119
146
  ```
120
147
 
121
- ## Testing Derivations
148
+ `mocks.resolvers[TYPE]` accepts a `MockResolverOptions` — the most common shape is `{ resolve: (req, context) => {…} }`. You can also pass full mock-resolver behaviors (`onCall`, `respondWith`, etc.) — see `MockResolverOptions` for the full surface.
122
149
 
123
- Set facts, then read the derived value.
150
+ For programmatic control of when a mock resolves (e.g. holding a request to test loading states), use the `mockResolver(type)` factory:
124
151
 
125
152
  ```typescript
126
- describe("isOverBudget derivation", () => {
127
- it("returns true when total exceeds budget", () => {
128
- const system = createTestSystem(budgetModule, {
129
- initialFacts: { total: 150, budget: 100 },
130
- });
153
+ import { mockResolver } from "@directive-run/core/testing";
131
154
 
132
- assertDerivation(system, "isOverBudget", true);
133
- });
155
+ const mock = mockResolver("FETCH_USER");
134
156
 
135
- it("returns false when under budget", () => {
136
- const system = createTestSystem(budgetModule, {
137
- initialFacts: { total: 50, budget: 100 },
138
- });
157
+ const system = createTestSystem({
158
+ module: userModule,
159
+ mocks: {
160
+ resolvers: { FETCH_USER: { resolve: mock.handler } },
161
+ },
162
+ });
139
163
 
140
- assertDerivation(system, "isOverBudget", false);
141
- });
164
+ system.facts.isAuthenticated = true;
165
+
166
+ // Now mock.pending contains the in-flight request — resolve it manually
167
+ expect(mock.calls).toHaveLength(1);
168
+ mock.pending[0].resolve({ id: "u1", name: "Alice" });
169
+
170
+ await system.waitForIdle();
171
+ ```
172
+
173
+ ## Testing derivations
142
174
 
175
+ Set facts, read the derived value via `system.derive`.
176
+
177
+ ```typescript
178
+ describe("isOverBudget derivation", () => {
143
179
  it("recomputes when facts change", () => {
144
- const system = createTestSystem(budgetModule, {
180
+ const system = createTestSystem({
181
+ module: budgetModule,
145
182
  initialFacts: { total: 50, budget: 100 },
146
183
  });
147
184
 
148
- assertDerivation(system, "isOverBudget", false);
185
+ expect(system.derive.isOverBudget).toBe(false);
149
186
 
150
187
  system.facts.total = 200;
151
188
 
152
- assertDerivation(system, "isOverBudget", true);
189
+ expect(system.derive.isOverBudget).toBe(true);
153
190
  });
154
191
  });
155
192
  ```
156
193
 
157
- ## Async Testing with Fake Timers
194
+ ## Async testing with fake timers
158
195
 
159
- Use `settleWithFakeTimers` when resolvers have retry delays or timeouts.
196
+ `waitForIdle` uses real timers by default. For tests that depend on retry backoff / TTLs / timers without burning wall-clock seconds, use `createFakeTimers()` (a vitest/jest-compatible controller) or your test framework's fake-timer integration.
160
197
 
161
198
  ```typescript
162
- import { describe, it, vi } from "vitest";
163
- import { createTestSystem, settleWithFakeTimers } from "@directive-run/core/testing";
199
+ import { describe, it, expect, vi } from "vitest";
200
+ import { createTestSystem } from "@directive-run/core/testing";
164
201
 
165
202
  describe("retry behavior", () => {
166
203
  it("retries on failure with exponential backoff", async () => {
167
204
  vi.useFakeTimers();
168
205
  let attempts = 0;
169
206
 
170
- const system = createTestSystem(myModule, {
171
- mockResolvers: [
172
- mockResolver("FETCH_DATA", async (req, context) => {
173
- attempts += 1;
174
- if (attempts < 3) {
175
- throw new Error("Temporary failure");
176
- }
177
- context.facts.data = "success";
178
- }),
179
- ],
207
+ const system = createTestSystem({
208
+ module: myModule,
209
+ mocks: {
210
+ resolvers: {
211
+ FETCH_DATA: {
212
+ resolve: async (req, context) => {
213
+ attempts += 1;
214
+ if (attempts < 3) {
215
+ throw new Error("Temporary failure");
216
+ }
217
+ context.facts.data = "success";
218
+ },
219
+ },
220
+ },
221
+ },
180
222
  });
181
223
 
182
224
  system.facts.needsData = true;
183
225
 
184
- // Advances fake timers through retry delays and settles
185
- await settleWithFakeTimers(system, vi);
226
+ // Advance through retry delays, then drain
227
+ await vi.runAllTimersAsync();
228
+ await system.waitForIdle();
186
229
 
187
230
  expect(attempts).toBe(3);
188
- assertFact(system, "data", "success");
231
+ system.assertFactSet("data", "success");
189
232
 
190
233
  vi.useRealTimers();
191
234
  });
192
235
  });
193
236
  ```
194
237
 
195
- ## Flushing Microtasks
238
+ ## Microtask flush
196
239
 
197
- Use `flushMicrotasks()` when you need to process pending promises without fully settling.
240
+ For checking intermediate state without fully settling, use `flushMicrotasks()`.
198
241
 
199
242
  ```typescript
243
+ import { flushMicrotasks } from "@directive-run/core/testing";
244
+
200
245
  it("processes intermediate state", async () => {
201
- const system = createTestSystem(myModule);
246
+ const system = createTestSystem({ module: myModule });
202
247
 
203
248
  system.facts.trigger = true;
204
249
 
205
- // Flush pending microtasks without waiting for full settlement
250
+ // Process one round of microtasks without waiting for resolvers
206
251
  await flushMicrotasks();
207
252
 
208
- // Check intermediate state
209
- assertFact(system, "phase", "loading");
253
+ system.assertFactSet("phase", "loading");
210
254
 
211
- // Now settle fully
212
- await system.settle();
255
+ await system.waitForIdle();
213
256
 
214
- assertFact(system, "phase", "done");
257
+ system.assertFactSet("phase", "done");
215
258
  });
216
259
  ```
217
260
 
218
- ## Coverage Tracking
219
-
220
- Track which constraints, resolvers, effects, and derivations are exercised:
261
+ ## Coverage tracking
221
262
 
222
263
  ```typescript
223
264
  import { createCoverageTracker } from "@directive-run/core/testing";
224
265
 
225
- const { run, report } = createCoverageTracker(system);
266
+ const tracker = createCoverageTracker(system);
226
267
 
227
- await run(async () => {
268
+ await tracker.run(async () => {
228
269
  system.facts.userId = 123;
229
- await system.settle();
270
+ await system.waitForIdle();
230
271
  });
231
272
 
232
- const coverage = report();
233
- // coverage.constraintCoverage — 0-1 (percentage hit)
234
- // coverage.resolverCoverage — 0-1
235
- // coverage.effectCoverage — 0-1
236
- // coverage.derivationCoverage — 0-1
237
- // coverage.constraintsMissed — Set<string> of IDs never triggered
273
+ const coverage = tracker.report();
274
+ // coverage.constraintCoverage — 0-1
275
+ // coverage.resolverCoverage — 0-1
276
+ // coverage.effectCoverage — 0-1
277
+ // coverage.derivationCoverage — 0-1
278
+ // coverage.constraintsMissed — Set<string> of ids never triggered
238
279
  ```
239
280
 
240
- ## Test Observer
281
+ ## Test observer
241
282
 
242
- Collect observation events for assertion-based testing:
283
+ Capture observation events for assertion-based testing:
243
284
 
244
285
  ```typescript
245
286
  import { createTestObserver } from "@directive-run/core/testing";
@@ -247,52 +288,125 @@ import { createTestObserver } from "@directive-run/core/testing";
247
288
  const observer = createTestObserver(system);
248
289
 
249
290
  system.facts.count = 5;
250
- await system.settle();
291
+ await system.waitForIdle();
251
292
 
252
293
  const evals = observer.ofType("constraint.evaluate");
253
294
  expect(evals).toHaveLength(1);
254
295
 
255
- observer.clear(); // Reset
256
- observer.dispose(); // Stop observing
296
+ observer.clear(); // reset captured events
297
+ observer.dispose(); // stop capturing
257
298
  ```
258
299
 
259
- ## Common Mistakes
300
+ ## Anti-patterns
260
301
 
261
- ### Testing real resolvers instead of mocking
302
+ ### `createTestSystem(myModule)` (module as positional arg)
262
303
 
263
304
  ```typescript
264
- // WRONG tests hit real APIs, slow and flaky
305
+ // WRONG createTestSystem takes an options object, not a positional module
265
306
  const system = createTestSystem(myModule);
266
- system.facts.needsFetch = true;
267
- await system.settle(); // Makes real HTTP call
268
307
 
269
- // CORRECT mock the resolver
308
+ // CORRECT wrap in { module: ... }
309
+ const system = createTestSystem({ module: myModule });
310
+ ```
311
+
312
+ ### `createTestSystemFromModules({ a, b })`
313
+
314
+ ```typescript
315
+ // WRONG — there is no separate factory; the same createTestSystem handles both forms
316
+ const system = createTestSystemFromModules({ auth, cart });
317
+
318
+ // CORRECT — pass `modules:`
319
+ const system = createTestSystem({ modules: { auth, cart } });
320
+ ```
321
+
322
+ ### Importing assertions as top-level helpers
323
+
324
+ ```typescript
325
+ // WRONG — assertFact / assertDerivation / assertRequirement are not top-level exports
326
+ import { assertFact, assertDerivation, assertRequirement } from "@directive-run/core/testing";
327
+ assertRequirement(system, "FETCH_USER");
328
+ assertFact(system, "phase", "loaded");
329
+
330
+ // CORRECT — they live on the test system instance
331
+ system.assertRequirement("FETCH_USER");
332
+ system.assertFactSet("phase", "loaded");
333
+
334
+ // For derivation reads, use expect() directly
335
+ expect(system.derive.isOverBudget).toBe(true);
336
+ ```
337
+
338
+ ### `mockResolvers: [mockResolver(TYPE, fn)]` (positional array)
339
+
340
+ ```typescript
341
+ // WRONG — mocks are keyed by type, not an array; and mockResolver only takes the type
270
342
  const system = createTestSystem(myModule, {
271
- mockResolvers: [mockResolver("FETCH", async (req, context) => {
272
- context.facts.data = { mocked: true };
273
- })],
343
+ mockResolvers: [
344
+ mockResolver("FETCH_USER", async (req, context) => { /* ... */ }),
345
+ ],
346
+ });
347
+
348
+ // CORRECT — mocks.resolvers is a Record<type, MockResolverOptions>
349
+ const system = createTestSystem({
350
+ module: myModule,
351
+ mocks: {
352
+ resolvers: {
353
+ FETCH_USER: {
354
+ resolve: async (req, context) => { /* ... */ },
355
+ },
356
+ },
357
+ },
274
358
  });
275
359
  ```
276
360
 
277
- ### Forgetting to settle before asserting async results
361
+ ### `inspection.requirements`
278
362
 
279
363
  ```typescript
280
- // WRONG resolver hasn't completed yet
281
- system.facts.trigger = true;
282
- assertFact(system, "result", "done"); // Fails!
364
+ // WRONG SystemInspection has `unmet` and `inflight`, not a flat `requirements`
365
+ inspection.requirements.filter((r) => r.type === "FETCH_USER")
366
+
367
+ // CORRECT — filter the right list
368
+ inspection.unmet.filter((r) => r.type === "FETCH_USER") // pending
369
+ inspection.inflight.filter((r) => r.type === "FETCH_USER") // in-progress
370
+ ```
371
+
372
+ ### Calling `system.settle()` instead of `system.waitForIdle()`
283
373
 
284
- // CORRECT wait for resolution
285
- system.facts.trigger = true;
374
+ `system.settle(maxWait?)` exists on production systems. Test systems inherit it, but the recommended assertion-friendly drain on a `TestSystem` is `waitForIdle` — it shares the same semantics and integrates with the resolver-call tracking used by `assertResolverCalled`.
375
+
376
+ ```typescript
377
+ // FINE — both work, but waitForIdle is the testing-flavored verb
286
378
  await system.settle();
287
- assertFact(system, "result", "done");
379
+ await system.waitForIdle();
288
380
  ```
289
381
 
290
- ### Using ctx instead of context in mock resolvers
382
+ ### `ctx` instead of `context`
291
383
 
292
384
  ```typescript
293
385
  // WRONG
294
- mockResolver("FETCH", async (req, ctx) => { /* ... */ }),
386
+ resolve: (req, ctx) => { ctx.facts.x = 1; }
295
387
 
296
388
  // CORRECT
297
- mockResolver("FETCH", async (req, context) => { /* ... */ }),
389
+ resolve: (req, context) => { context.facts.x = 1; }
298
390
  ```
391
+
392
+ ## Quick reference
393
+
394
+ | API | Path | Purpose |
395
+ |---|---|---|
396
+ | `createTestSystem(options)` | `@directive-run/core/testing` | Single overload; pass `module:` or `modules:` |
397
+ | `mockResolver(type)` | `@directive-run/core/testing` | Programmatic mock with `calls` / `pending` / `handler` |
398
+ | `createFakeTimers()` | `@directive-run/core/testing` | Vitest/Jest-compatible timer controller |
399
+ | `flushMicrotasks()` | `@directive-run/core/testing` | Advance one microtask round |
400
+ | `createCoverageTracker(system)` | `@directive-run/core/testing` | `run(fn)` + `report()` for constraint/resolver/effect/derivation hit rate |
401
+ | `createTestObserver(system)` | `@directive-run/core/testing` | Capture observation events for assertions |
402
+ | `system.assertRequirement(type)` | instance method | Assert a requirement was generated |
403
+ | `system.assertResolverCalled(type, n?)` | instance method | Assert a resolver was called |
404
+ | `system.assertFactSet(key, value?)` | instance method | Assert a fact value |
405
+ | `system.assertFactChanges(key, n)` | instance method | Assert the change count for a fact |
406
+ | `system.waitForIdle(maxWait?)` | instance method | Wait for in-flight resolvers (default 5000ms) |
407
+
408
+ ## See also
409
+
410
+ - [`resolvers.md`](./resolvers.md) — the resolver shape `mocks.resolvers` replaces and the retry/cancel semantics tests need to know about
411
+ - [`core-patterns.md`](./core-patterns.md) — the module shape `createTestSystem({ module })` is testing
412
+ - [`history.md`](./history.md) — recorded snapshots for replay-based testing patterns
@@ -23,7 +23,6 @@
23
23
 
24
24
  import {
25
25
  type AgentLike,
26
- CircuitBreakerOpenError,
27
26
  type InputGuardrailData,
28
27
  type NamedGuardrail,
29
28
  type RunResult,
@@ -31,26 +30,26 @@ import {
31
30
  createAgentOrchestrator,
32
31
  createLengthStreamingGuardrail,
33
32
  createMessageBus,
34
- createMultiAgentOrchestrator,
35
- createOutputSchemaGuardrail,
36
33
  createSemanticCache,
37
34
  createSlidingWindowStrategy,
38
35
  createStreamingRunner,
39
36
  createTestEmbedder,
40
37
  estimateCost,
41
- parallel,
42
38
  } from "@directive-run/ai";
43
39
  import type { CacheStats } from "@directive-run/ai";
40
+ import { createOutputSchemaGuardrail } from "@directive-run/ai/guardrails";
44
41
  import {
42
+ createMultiAgentOrchestrator,
43
+ parallel,
44
+ } from "@directive-run/ai/multi-agent";
45
+ import {
46
+ CircuitBreakerOpenError,
45
47
  type CircuitState,
48
+ createAgentMetrics,
46
49
  createCircuitBreaker,
47
50
  createOTLPExporter,
48
- } from "@directive-run/core/plugins";
49
- // createObservability is alpha (not in bundle) — direct source import
50
- import {
51
- createAgentMetrics,
52
51
  createObservability,
53
- } from "../../../packages/core/src/plugins/observability.lab.js";
52
+ } from "@directive-run/core/plugins";
54
53
  import {
55
54
  analysisAgent,
56
55
  chatAgent,
@@ -103,7 +102,7 @@ export interface CheckersAI {
103
102
  cacheStats: CacheStats;
104
103
  busMessageCount: number;
105
104
  };
106
- dispose(): void;
105
+ destroy(): void;
107
106
  /** Escape hatch for dashboard rendering */
108
107
  readonly observability: ReturnType<typeof createObservability> | null;
109
108
  }
@@ -323,7 +322,7 @@ export function createCheckersAI(): CheckersAI {
323
322
 
324
323
  function buildMoveInput(
325
324
  board: Board,
326
- player: Player,
325
+ _player: Player,
327
326
  legalMoves: Move[],
328
327
  humanMoveDesc?: string,
329
328
  ): string {
@@ -564,7 +563,7 @@ export function createCheckersAI(): CheckersAI {
564
563
  };
565
564
  }
566
565
 
567
- function dispose(): void {
566
+ function destroy(): void {
568
567
  clearInterval(otlpInterval);
569
568
  // Flush OTLP one final time
570
569
  try {
@@ -574,9 +573,9 @@ export function createCheckersAI(): CheckersAI {
574
573
  } catch {
575
574
  // Best-effort flush on dispose
576
575
  }
577
- orchestrator.dispose();
578
- multi.dispose();
579
- obs.dispose();
576
+ orchestrator.destroy();
577
+ multi.destroy();
578
+ void obs.destroy();
580
579
  }
581
580
 
582
581
  return {
@@ -584,7 +583,7 @@ export function createCheckersAI(): CheckersAI {
584
583
  sendChat,
585
584
  reset,
586
585
  getState,
587
- dispose,
586
+ destroy,
588
587
  get observability() {
589
588
  return obs;
590
589
  },
@@ -260,7 +260,7 @@ const contactForm = createModule("contact-form", {
260
260
  resolvers: {
261
261
  sendMessage: {
262
262
  requirement: "SEND_MESSAGE",
263
- resolve: async (req, context) => {
263
+ resolve: async (_req, context) => {
264
264
  log(
265
265
  `Sending: ${context.facts.name} <${context.facts.email}> [${context.facts.subject}]`,
266
266
  );
@@ -285,7 +285,7 @@ const contactForm = createModule("contact-form", {
285
285
 
286
286
  resetAfterDelay: {
287
287
  requirement: "RESET_AFTER_DELAY",
288
- resolve: async (req, context) => {
288
+ resolve: async (_req, context) => {
289
289
  log("Auto-resetting in 3 seconds...");
290
290
  await new Promise((resolve) => setTimeout(resolve, 3000));
291
291
  context.facts.name = "";
@@ -49,7 +49,7 @@ export const counterModule = createModule("counter", {
49
49
  resolvers: {
50
50
  clamp: {
51
51
  requirement: "CLAMP_TO_ZERO",
52
- resolve: async (req, context) => {
52
+ resolve: async (_req, context) => {
53
53
  context.facts.count = 0;
54
54
  },
55
55
  },