@directive-run/claude-plugin 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 (83) hide show
  1. package/.claude-plugin/plugin.json +6 -0
  2. package/LICENSE +26 -0
  3. package/README.md +113 -0
  4. package/dist/index.cjs +2 -0
  5. package/dist/index.cjs.map +1 -0
  6. package/dist/index.d.cts +66 -0
  7. package/dist/index.d.ts +66 -0
  8. package/dist/index.js +2 -0
  9. package/dist/index.js.map +1 -0
  10. package/package.json +64 -0
  11. package/skills/README.md +9 -0
  12. package/skills/building-ai-agents/SKILL.md +414 -0
  13. package/skills/building-ai-agents/ai-adapters.md +257 -0
  14. package/skills/building-ai-agents/ai-agents-streaming.md +307 -0
  15. package/skills/building-ai-agents/ai-communication.md +304 -0
  16. package/skills/building-ai-agents/api-skeleton.md +5 -0
  17. package/skills/building-ai-agents/examples.md +849 -0
  18. package/skills/building-ai-orchestrators/SKILL.md +401 -0
  19. package/skills/building-ai-orchestrators/ai-multi-agent.md +375 -0
  20. package/skills/building-ai-orchestrators/ai-orchestrator.md +466 -0
  21. package/skills/building-ai-orchestrators/ai-tasks.md +268 -0
  22. package/skills/building-ai-orchestrators/api-skeleton.md +5 -0
  23. package/skills/building-ai-orchestrators/examples.md +1609 -0
  24. package/skills/building-directive-systems/SKILL.md +499 -0
  25. package/skills/building-directive-systems/api-skeleton.md +5 -0
  26. package/skills/building-directive-systems/examples.md +4149 -0
  27. package/skills/building-directive-systems/multi-module.md +327 -0
  28. package/skills/building-directive-systems/plugins.md +361 -0
  29. package/skills/building-directive-systems/react-adapter.md +376 -0
  30. package/skills/building-directive-systems/system-api.md +463 -0
  31. package/skills/getting-started-with-directive/SKILL.md +198 -0
  32. package/skills/getting-started-with-directive/api-skeleton.md +5 -0
  33. package/skills/getting-started-with-directive/core-patterns.md +240 -0
  34. package/skills/getting-started-with-directive/examples.md +87 -0
  35. package/skills/getting-started-with-directive/sitemap.md +202 -0
  36. package/skills/hardening-ai-systems/SKILL.md +377 -0
  37. package/skills/hardening-ai-systems/ai-budget-resilience.md +408 -0
  38. package/skills/hardening-ai-systems/ai-guardrails-memory.md +370 -0
  39. package/skills/hardening-ai-systems/ai-security.md +400 -0
  40. package/skills/hardening-ai-systems/api-skeleton.md +5 -0
  41. package/skills/hardening-ai-systems/examples.md +628 -0
  42. package/skills/migrating-to-directive/SKILL.md +249 -0
  43. package/skills/migrating-to-directive/anti-patterns.md +382 -0
  44. package/skills/migrating-to-directive/api-skeleton.md +5 -0
  45. package/skills/migrating-to-directive/core-patterns.md +240 -0
  46. package/skills/migrating-to-directive/examples.md +511 -0
  47. package/skills/migrating-to-directive/schema-types.md +273 -0
  48. package/skills/reviewing-directive-code/SKILL.md +232 -0
  49. package/skills/reviewing-directive-code/anti-patterns.md +382 -0
  50. package/skills/reviewing-directive-code/api-skeleton.md +5 -0
  51. package/skills/reviewing-directive-code/core-patterns.md +240 -0
  52. package/skills/reviewing-directive-code/examples.md +463 -0
  53. package/skills/reviewing-directive-code/naming.md +321 -0
  54. package/skills/scaffolding-directive-modules/SKILL.md +218 -0
  55. package/skills/scaffolding-directive-modules/api-skeleton.md +5 -0
  56. package/skills/scaffolding-directive-modules/core-patterns.md +240 -0
  57. package/skills/scaffolding-directive-modules/examples.md +1386 -0
  58. package/skills/scaffolding-directive-modules/naming.md +321 -0
  59. package/skills/scaffolding-directive-modules/schema-types.md +273 -0
  60. package/skills/testing-ai-systems/SKILL.md +398 -0
  61. package/skills/testing-ai-systems/ai-debug-observability.md +329 -0
  62. package/skills/testing-ai-systems/ai-mcp-rag.md +293 -0
  63. package/skills/testing-ai-systems/ai-testing-evals.md +485 -0
  64. package/skills/testing-ai-systems/api-skeleton.md +5 -0
  65. package/skills/testing-ai-systems/examples.md +1261 -0
  66. package/skills/testing-directive-code/SKILL.md +479 -0
  67. package/skills/testing-directive-code/api-skeleton.md +5 -0
  68. package/skills/testing-directive-code/examples.md +156 -0
  69. package/skills/testing-directive-code/history.md +351 -0
  70. package/skills/testing-directive-code/testing.md +412 -0
  71. package/skills/writing-directive-constraints/SKILL.md +486 -0
  72. package/skills/writing-directive-constraints/api-skeleton.md +5 -0
  73. package/skills/writing-directive-constraints/constraints.md +274 -0
  74. package/skills/writing-directive-constraints/error-boundaries.md +330 -0
  75. package/skills/writing-directive-constraints/examples.md +1361 -0
  76. package/skills/writing-directive-constraints/resolvers.md +367 -0
  77. package/skills/writing-directive-modules/SKILL.md +342 -0
  78. package/skills/writing-directive-modules/anti-patterns.md +382 -0
  79. package/skills/writing-directive-modules/api-skeleton.md +5 -0
  80. package/skills/writing-directive-modules/core-patterns.md +240 -0
  81. package/skills/writing-directive-modules/examples.md +1639 -0
  82. package/skills/writing-directive-modules/naming.md +321 -0
  83. package/skills/writing-directive-modules/schema-types.md +273 -0
@@ -0,0 +1,412 @@
1
+ # Testing
2
+
3
+ > Covers `@directive-run/core/testing` — `createTestSystem`, mock resolvers, assertion helpers, coverage tracker, test observer.
4
+
5
+ Testing utilities for Directive modules and systems. Import from `@directive-run/core/testing`.
6
+
7
+ ## Decision tree
8
+
9
+ ```
10
+ What are you testing?
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)
19
+ ```
20
+
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.
24
+
25
+ ```typescript
26
+ import { createTestSystem } from "@directive-run/core/testing";
27
+
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
+ },
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.
67
+
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
73
+ ```
74
+
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
+ ```
81
+
82
+ ## Testing constraints
83
+
84
+ ```typescript
85
+ import { describe, it, expect } from "vitest";
86
+ import { createTestSystem } from "@directive-run/core/testing";
87
+
88
+ describe("fetchWhenAuth constraint", () => {
89
+ it("emits FETCH_USER when authenticated without profile", () => {
90
+ const system = createTestSystem({ module: userModule });
91
+
92
+ system.facts.isAuthenticated = true;
93
+ system.facts.profile = null;
94
+
95
+ system.assertRequirement("FETCH_USER");
96
+ });
97
+
98
+ it("does NOT emit when already has a profile", () => {
99
+ const system = createTestSystem({
100
+ module: userModule,
101
+ initialFacts: {
102
+ isAuthenticated: true,
103
+ profile: { id: "1", name: "Alice" },
104
+ },
105
+ });
106
+
107
+ const inspection = system.inspect();
108
+ const fetchReqs = inspection.unmet.filter((r) => r.type === "FETCH_USER");
109
+ expect(fetchReqs).toHaveLength(0);
110
+ });
111
+ });
112
+ ```
113
+
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).
115
+
116
+ ## Testing resolvers
117
+
118
+ ```typescript
119
+ describe("fetchUser resolver", () => {
120
+ it("stores fetched user in facts", async () => {
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
+ },
133
+ });
134
+
135
+ // Trigger the constraint that emits FETCH_USER
136
+ system.facts.isAuthenticated = true;
137
+ system.facts.user = null;
138
+
139
+ // Wait for the resolver to finish (default 5s timeout)
140
+ await system.waitForIdle();
141
+
142
+ system.assertFactSet("user", { id: expect.anything(), name: "Mocked User" });
143
+ system.assertFactSet("phase", "loaded");
144
+ });
145
+ });
146
+ ```
147
+
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.
149
+
150
+ For programmatic control of when a mock resolves (e.g. holding a request to test loading states), use the `mockResolver(type)` factory:
151
+
152
+ ```typescript
153
+ import { mockResolver } from "@directive-run/core/testing";
154
+
155
+ const mock = mockResolver("FETCH_USER");
156
+
157
+ const system = createTestSystem({
158
+ module: userModule,
159
+ mocks: {
160
+ resolvers: { FETCH_USER: { resolve: mock.handler } },
161
+ },
162
+ });
163
+
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
174
+
175
+ Set facts, read the derived value via `system.derive`.
176
+
177
+ ```typescript
178
+ describe("isOverBudget derivation", () => {
179
+ it("recomputes when facts change", () => {
180
+ const system = createTestSystem({
181
+ module: budgetModule,
182
+ initialFacts: { total: 50, budget: 100 },
183
+ });
184
+
185
+ expect(system.derive.isOverBudget).toBe(false);
186
+
187
+ system.facts.total = 200;
188
+
189
+ expect(system.derive.isOverBudget).toBe(true);
190
+ });
191
+ });
192
+ ```
193
+
194
+ ## Async testing with fake timers
195
+
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.
197
+
198
+ ```typescript
199
+ import { describe, it, expect, vi } from "vitest";
200
+ import { createTestSystem } from "@directive-run/core/testing";
201
+
202
+ describe("retry behavior", () => {
203
+ it("retries on failure with exponential backoff", async () => {
204
+ vi.useFakeTimers();
205
+ let attempts = 0;
206
+
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
+ },
222
+ });
223
+
224
+ system.facts.needsData = true;
225
+
226
+ // Advance through retry delays, then drain
227
+ await vi.runAllTimersAsync();
228
+ await system.waitForIdle();
229
+
230
+ expect(attempts).toBe(3);
231
+ system.assertFactSet("data", "success");
232
+
233
+ vi.useRealTimers();
234
+ });
235
+ });
236
+ ```
237
+
238
+ ## Microtask flush
239
+
240
+ For checking intermediate state without fully settling, use `flushMicrotasks()`.
241
+
242
+ ```typescript
243
+ import { flushMicrotasks } from "@directive-run/core/testing";
244
+
245
+ it("processes intermediate state", async () => {
246
+ const system = createTestSystem({ module: myModule });
247
+
248
+ system.facts.trigger = true;
249
+
250
+ // Process one round of microtasks without waiting for resolvers
251
+ await flushMicrotasks();
252
+
253
+ system.assertFactSet("phase", "loading");
254
+
255
+ await system.waitForIdle();
256
+
257
+ system.assertFactSet("phase", "done");
258
+ });
259
+ ```
260
+
261
+ ## Coverage tracking
262
+
263
+ ```typescript
264
+ import { createCoverageTracker } from "@directive-run/core/testing";
265
+
266
+ const tracker = createCoverageTracker(system);
267
+
268
+ await tracker.run(async () => {
269
+ system.facts.userId = 123;
270
+ await system.waitForIdle();
271
+ });
272
+
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
279
+ ```
280
+
281
+ ## Test observer
282
+
283
+ Capture observation events for assertion-based testing:
284
+
285
+ ```typescript
286
+ import { createTestObserver } from "@directive-run/core/testing";
287
+
288
+ const observer = createTestObserver(system);
289
+
290
+ system.facts.count = 5;
291
+ await system.waitForIdle();
292
+
293
+ const evals = observer.ofType("constraint.evaluate");
294
+ expect(evals).toHaveLength(1);
295
+
296
+ observer.clear(); // reset captured events
297
+ observer.dispose(); // stop capturing
298
+ ```
299
+
300
+ ## Anti-patterns
301
+
302
+ ### `createTestSystem(myModule)` (module as positional arg)
303
+
304
+ ```typescript
305
+ // WRONG — createTestSystem takes an options object, not a positional module
306
+ const system = createTestSystem(myModule);
307
+
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
342
+ const system = createTestSystem(myModule, {
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
+ },
358
+ });
359
+ ```
360
+
361
+ ### `inspection.requirements`
362
+
363
+ ```typescript
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()`
373
+
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
378
+ await system.settle();
379
+ await system.waitForIdle();
380
+ ```
381
+
382
+ ### `ctx` instead of `context`
383
+
384
+ ```typescript
385
+ // WRONG
386
+ resolve: (req, ctx) => { ctx.facts.x = 1; }
387
+
388
+ // CORRECT
389
+ resolve: (req, context) => { context.facts.x = 1; }
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