@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.
- package/.claude-plugin/plugin.json +6 -0
- package/LICENSE +26 -0
- package/README.md +113 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
- package/skills/README.md +9 -0
- package/skills/building-ai-agents/SKILL.md +414 -0
- package/skills/building-ai-agents/ai-adapters.md +257 -0
- package/skills/building-ai-agents/ai-agents-streaming.md +307 -0
- package/skills/building-ai-agents/ai-communication.md +304 -0
- package/skills/building-ai-agents/api-skeleton.md +5 -0
- package/skills/building-ai-agents/examples.md +849 -0
- package/skills/building-ai-orchestrators/SKILL.md +401 -0
- package/skills/building-ai-orchestrators/ai-multi-agent.md +375 -0
- package/skills/building-ai-orchestrators/ai-orchestrator.md +466 -0
- package/skills/building-ai-orchestrators/ai-tasks.md +268 -0
- package/skills/building-ai-orchestrators/api-skeleton.md +5 -0
- package/skills/building-ai-orchestrators/examples.md +1609 -0
- package/skills/building-directive-systems/SKILL.md +499 -0
- package/skills/building-directive-systems/api-skeleton.md +5 -0
- package/skills/building-directive-systems/examples.md +4149 -0
- package/skills/building-directive-systems/multi-module.md +327 -0
- package/skills/building-directive-systems/plugins.md +361 -0
- package/skills/building-directive-systems/react-adapter.md +376 -0
- package/skills/building-directive-systems/system-api.md +463 -0
- package/skills/getting-started-with-directive/SKILL.md +198 -0
- package/skills/getting-started-with-directive/api-skeleton.md +5 -0
- package/skills/getting-started-with-directive/core-patterns.md +240 -0
- package/skills/getting-started-with-directive/examples.md +87 -0
- package/skills/getting-started-with-directive/sitemap.md +202 -0
- package/skills/hardening-ai-systems/SKILL.md +377 -0
- package/skills/hardening-ai-systems/ai-budget-resilience.md +408 -0
- package/skills/hardening-ai-systems/ai-guardrails-memory.md +370 -0
- package/skills/hardening-ai-systems/ai-security.md +400 -0
- package/skills/hardening-ai-systems/api-skeleton.md +5 -0
- package/skills/hardening-ai-systems/examples.md +628 -0
- package/skills/migrating-to-directive/SKILL.md +249 -0
- package/skills/migrating-to-directive/anti-patterns.md +382 -0
- package/skills/migrating-to-directive/api-skeleton.md +5 -0
- package/skills/migrating-to-directive/core-patterns.md +240 -0
- package/skills/migrating-to-directive/examples.md +511 -0
- package/skills/migrating-to-directive/schema-types.md +273 -0
- package/skills/reviewing-directive-code/SKILL.md +232 -0
- package/skills/reviewing-directive-code/anti-patterns.md +382 -0
- package/skills/reviewing-directive-code/api-skeleton.md +5 -0
- package/skills/reviewing-directive-code/core-patterns.md +240 -0
- package/skills/reviewing-directive-code/examples.md +463 -0
- package/skills/reviewing-directive-code/naming.md +321 -0
- package/skills/scaffolding-directive-modules/SKILL.md +218 -0
- package/skills/scaffolding-directive-modules/api-skeleton.md +5 -0
- package/skills/scaffolding-directive-modules/core-patterns.md +240 -0
- package/skills/scaffolding-directive-modules/examples.md +1386 -0
- package/skills/scaffolding-directive-modules/naming.md +321 -0
- package/skills/scaffolding-directive-modules/schema-types.md +273 -0
- package/skills/testing-ai-systems/SKILL.md +398 -0
- package/skills/testing-ai-systems/ai-debug-observability.md +329 -0
- package/skills/testing-ai-systems/ai-mcp-rag.md +293 -0
- package/skills/testing-ai-systems/ai-testing-evals.md +485 -0
- package/skills/testing-ai-systems/api-skeleton.md +5 -0
- package/skills/testing-ai-systems/examples.md +1261 -0
- package/skills/testing-directive-code/SKILL.md +479 -0
- package/skills/testing-directive-code/api-skeleton.md +5 -0
- package/skills/testing-directive-code/examples.md +156 -0
- package/skills/testing-directive-code/history.md +351 -0
- package/skills/testing-directive-code/testing.md +412 -0
- package/skills/writing-directive-constraints/SKILL.md +486 -0
- package/skills/writing-directive-constraints/api-skeleton.md +5 -0
- package/skills/writing-directive-constraints/constraints.md +274 -0
- package/skills/writing-directive-constraints/error-boundaries.md +330 -0
- package/skills/writing-directive-constraints/examples.md +1361 -0
- package/skills/writing-directive-constraints/resolvers.md +367 -0
- package/skills/writing-directive-modules/SKILL.md +342 -0
- package/skills/writing-directive-modules/anti-patterns.md +382 -0
- package/skills/writing-directive-modules/api-skeleton.md +5 -0
- package/skills/writing-directive-modules/core-patterns.md +240 -0
- package/skills/writing-directive-modules/examples.md +1639 -0
- package/skills/writing-directive-modules/naming.md +321 -0
- package/skills/writing-directive-modules/schema-types.md +273 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: testing-ai-systems
|
|
3
|
+
description: "Test AI orchestrators without real LLM API calls using createMockRunner, write quality evaluations for agent output, add debug observability with tracing and metrics, integrate MCP tool servers, and wire RAG pipelines. Use when writing unit tests for agents, setting up CI evaluation suites, debugging orchestrator behavior, or connecting external tool sources."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Testing AI Systems
|
|
7
|
+
|
|
8
|
+
## Prerequisites
|
|
9
|
+
|
|
10
|
+
This skill applies when the project uses `@directive-run/ai`. If not found in `package.json`, suggest installing it: `npm install @directive-run/ai`.
|
|
11
|
+
|
|
12
|
+
## When Claude Should Use This Skill
|
|
13
|
+
|
|
14
|
+
## Auto-Invoke Triggers
|
|
15
|
+
- User asks how to test agents or orchestrators without calling real APIs
|
|
16
|
+
- User mentions `createMockRunner` or wants to mock LLM responses
|
|
17
|
+
- User wants to run evaluation suites or measure output quality
|
|
18
|
+
- User asks about tracing, logging, or debugging orchestrator execution
|
|
19
|
+
- User mentions MCP (Model Context Protocol) or tool servers
|
|
20
|
+
- User wants to add RAG (retrieval-augmented generation) to an agent
|
|
21
|
+
|
|
22
|
+
## Exclusions
|
|
23
|
+
- Do NOT invoke for production guardrails or security – use `hardening-ai-systems`
|
|
24
|
+
- Do NOT invoke for orchestrator setup – use `building-ai-orchestrators`
|
|
25
|
+
- Do NOT invoke for provider runner config – use `building-ai-agents`
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quick Reference
|
|
30
|
+
|
|
31
|
+
## Decision Tree: Testing Approach
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
What are you testing?
|
|
35
|
+
├── Single resolver behavior → createMockRunner + unit test
|
|
36
|
+
├── Full orchestrator flow → createTestOrchestrator
|
|
37
|
+
├── Multi-agent pipeline → createTestMultiAgentOrchestrator
|
|
38
|
+
├── Output quality (LLM-as-judge) → createEvaluation + assertOutputQuality
|
|
39
|
+
├── Regression suite in CI → evaluation suite with recorded fixtures
|
|
40
|
+
└── Live debug of production → enableTracing + observability plugins
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## createMockRunner – Test Without API Calls
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { createMockRunner } from "@directive-run/ai/testing";
|
|
49
|
+
|
|
50
|
+
// Static responses – returned in sequence
|
|
51
|
+
const mockRunner = createMockRunner({
|
|
52
|
+
responses: [
|
|
53
|
+
{ text: "First response.", usage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 } },
|
|
54
|
+
{ text: "Second response.", usage: { inputTokens: 8, outputTokens: 4, totalTokens: 12 } },
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Dynamic – choose response based on prompt
|
|
59
|
+
const dynamicRunner = createMockRunner({
|
|
60
|
+
handler: async (options) => {
|
|
61
|
+
if (options.prompt.includes("capital")) {
|
|
62
|
+
return { text: "Paris", usage: { inputTokens: 5, outputTokens: 2, totalTokens: 7 } };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return { text: "I don't know.", usage: { inputTokens: 5, outputTokens: 4, totalTokens: 9 } };
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Simulate failures for error-path tests
|
|
70
|
+
const failingRunner = createMockRunner({
|
|
71
|
+
handler: async () => {
|
|
72
|
+
throw new Error("rate_limit: Too many requests");
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Unit Testing a Resolver
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { createAgentOrchestrator } from "@directive-run/ai";
|
|
81
|
+
import { createMockRunner } from "@directive-run/ai/testing";
|
|
82
|
+
import { t } from "@directive-run/core";
|
|
83
|
+
import { describe, it, expect } from "vitest";
|
|
84
|
+
|
|
85
|
+
// Build orchestrator with injected runner – enables swapping real/mock
|
|
86
|
+
function buildOrchestrator(runner: AgentRunner) {
|
|
87
|
+
return createAgentOrchestrator({
|
|
88
|
+
runner,
|
|
89
|
+
factsSchema: {
|
|
90
|
+
input: t.string(),
|
|
91
|
+
output: t.string().optional(),
|
|
92
|
+
status: t.string<"idle" | "done" | "error">(),
|
|
93
|
+
errorMessage: t.string().optional(),
|
|
94
|
+
},
|
|
95
|
+
init: (facts) => {
|
|
96
|
+
facts.status = "idle";
|
|
97
|
+
},
|
|
98
|
+
constraints: {
|
|
99
|
+
process: {
|
|
100
|
+
when: (facts) => facts.status === "idle" && !!facts.input,
|
|
101
|
+
require: { type: "PROCESS" },
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
resolvers: {
|
|
105
|
+
process: {
|
|
106
|
+
requirement: "PROCESS",
|
|
107
|
+
resolve: async (req, context) => {
|
|
108
|
+
try {
|
|
109
|
+
const result = await context.runner.run({ prompt: context.facts.input });
|
|
110
|
+
context.facts.output = result.text;
|
|
111
|
+
context.facts.status = "done";
|
|
112
|
+
} catch (error) {
|
|
113
|
+
context.facts.status = "error";
|
|
114
|
+
context.facts.errorMessage = (error as Error).message;
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
describe("process resolver", () => {
|
|
123
|
+
it("sets output on success", async () => {
|
|
124
|
+
const orchestrator = buildOrchestrator(
|
|
125
|
+
createMockRunner({ responses: [{ text: "Result.", usage: { inputTokens: 5, outputTokens: 2, totalTokens: 7 } }] })
|
|
126
|
+
);
|
|
127
|
+
const result = await orchestrator.run({ input: "test input" });
|
|
128
|
+
|
|
129
|
+
expect(result.facts.status).toBe("done");
|
|
130
|
+
expect(result.facts.output).toBe("Result.");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("captures errors in facts", async () => {
|
|
134
|
+
const orchestrator = buildOrchestrator(
|
|
135
|
+
createMockRunner({ handler: async () => { throw new Error("Provider down"); } })
|
|
136
|
+
);
|
|
137
|
+
const result = await orchestrator.run({ input: "test" });
|
|
138
|
+
|
|
139
|
+
expect(result.facts.status).toBe("error");
|
|
140
|
+
expect(result.facts.errorMessage).toContain("Provider down");
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Testing Multi-Agent Orchestrators
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { createTestMultiAgentOrchestrator, assertMultiAgentState } from "@directive-run/ai/testing";
|
|
149
|
+
|
|
150
|
+
describe("research-write pipeline", () => {
|
|
151
|
+
it("passes research to writer via coordinator facts", async () => {
|
|
152
|
+
const testOrchestrator = createTestMultiAgentOrchestrator({
|
|
153
|
+
agentRunners: {
|
|
154
|
+
researcher: createMockRunner({
|
|
155
|
+
responses: [{ text: "Finding: qubits enable quantum advantage.", usage: { inputTokens: 10, outputTokens: 8, totalTokens: 18 } }],
|
|
156
|
+
}),
|
|
157
|
+
writer: createMockRunner({
|
|
158
|
+
responses: [{ text: "Qubits are the foundation of quantum computing.", usage: { inputTokens: 20, outputTokens: 10, totalTokens: 30 } }],
|
|
159
|
+
}),
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const result = await testOrchestrator.run({ topic: "quantum computing" });
|
|
164
|
+
|
|
165
|
+
assertMultiAgentState(result, {
|
|
166
|
+
"researcher.researchComplete": true,
|
|
167
|
+
"coordinator.phase": "done",
|
|
168
|
+
});
|
|
169
|
+
expect(result.facts.finalOutput).toContain("qubit");
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Evaluation Framework
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { createEvaluation, assertOutputQuality } from "@directive-run/ai/testing";
|
|
180
|
+
|
|
181
|
+
const summaryEval = createEvaluation({
|
|
182
|
+
name: "summarization-quality",
|
|
183
|
+
cases: [
|
|
184
|
+
{
|
|
185
|
+
id: "basic-summary",
|
|
186
|
+
input: { text: "A very long article about climate change..." },
|
|
187
|
+
expected: {
|
|
188
|
+
contains: ["climate", "temperature"],
|
|
189
|
+
maxWords: 50,
|
|
190
|
+
notContains: ["Lorem ipsum"],
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe("summarization evals", () => {
|
|
197
|
+
it("meets quality criteria", async () => {
|
|
198
|
+
for (const evalCase of summaryEval.cases) {
|
|
199
|
+
const result = await orchestrator.run(evalCase.input);
|
|
200
|
+
assertOutputQuality(result.facts.output, evalCase.expected);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### LLM-as-Judge
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { createLLMJudge } from "@directive-run/ai/testing";
|
|
210
|
+
|
|
211
|
+
const judge = createLLMJudge({
|
|
212
|
+
runner: createAnthropicRunner({ model: "claude-haiku-4-5", apiKey: process.env.ANTHROPIC_API_KEY }),
|
|
213
|
+
criteria: ["Is the response factually accurate?", "Is the response concise?"],
|
|
214
|
+
scoringScale: { min: 1, max: 5 },
|
|
215
|
+
passingScore: 3.5,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("produces quality output", async () => {
|
|
219
|
+
const result = await orchestrator.run({ input: "Explain photosynthesis." });
|
|
220
|
+
const judgment = await judge.evaluate(result.facts.output);
|
|
221
|
+
|
|
222
|
+
expect(judgment.passed).toBe(true);
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Debug Observability
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import { createTracingPlugin, createLoggingPlugin, createMetricsPlugin } from "@directive-run/ai/plugins";
|
|
232
|
+
import { createInspector } from "@directive-run/ai/testing";
|
|
233
|
+
|
|
234
|
+
// Tracing – spans around every LLM call and resolver
|
|
235
|
+
const tracer = createTracingPlugin({
|
|
236
|
+
exportTo: "console", // "console" | "opentelemetry" | "custom"
|
|
237
|
+
onSpanEnd: (span) => console.log(`[${span.name}] ${span.duration}ms – ${span.status}`),
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Structured logging for LLM calls, guardrail violations, budget warnings
|
|
241
|
+
const logger = createLoggingPlugin({
|
|
242
|
+
level: "info",
|
|
243
|
+
format: "json",
|
|
244
|
+
include: ["runner_calls", "guardrail_violations", "budget_warnings"],
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Metrics – auto-collects: ai.tokens.*, ai.latency.runner, ai.guardrail.violations
|
|
248
|
+
const metrics = createMetricsPlugin({
|
|
249
|
+
onMetric: (metric) => myMetricsClient.gauge(metric.name, metric.value, metric.tags),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Inspector – assert on calls and events in tests
|
|
253
|
+
const inspector = createInspector();
|
|
254
|
+
|
|
255
|
+
const orchestrator = createAgentOrchestrator({
|
|
256
|
+
runner: createMockRunner({ responses: [/* ... */] }),
|
|
257
|
+
plugins: [inspector],
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
await orchestrator.run({ input: "test" });
|
|
261
|
+
|
|
262
|
+
const calls = inspector.getRunnerCalls();
|
|
263
|
+
expect(calls).toHaveLength(1);
|
|
264
|
+
expect(calls[0].prompt).toContain("test");
|
|
265
|
+
|
|
266
|
+
const events = inspector.getEvents();
|
|
267
|
+
expect(events.some((e) => e.type === "requirement_met")).toBe(true);
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## MCP Integration
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import { createMCPToolProvider } from "@directive-run/ai/mcp";
|
|
276
|
+
import { createMockMCPProvider } from "@directive-run/ai/testing";
|
|
277
|
+
|
|
278
|
+
// Production: connect to real MCP tool server
|
|
279
|
+
const mcpProvider = createMCPToolProvider({
|
|
280
|
+
transport: "stdio", // "stdio" | "sse" | "http"
|
|
281
|
+
command: "npx",
|
|
282
|
+
args: ["@my-org/mcp-tools"],
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Testing: mock the tool provider
|
|
286
|
+
const mockMCP = createMockMCPProvider({
|
|
287
|
+
tools: {
|
|
288
|
+
web_search: async (_params) => ({
|
|
289
|
+
results: ["Mocked result 1", "Mocked result 2"],
|
|
290
|
+
}),
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Tools available in resolvers via context.tools.getAll()
|
|
295
|
+
resolvers: {
|
|
296
|
+
search: {
|
|
297
|
+
requirement: "SEARCH",
|
|
298
|
+
resolve: async (req, context) => {
|
|
299
|
+
const result = await context.runner.run({
|
|
300
|
+
prompt: context.facts.query,
|
|
301
|
+
tools: context.tools.getAll(),
|
|
302
|
+
});
|
|
303
|
+
context.facts.result = result.text;
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## RAG Pipeline Integration
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
import { createRAGProvider } from "@directive-run/ai/rag";
|
|
315
|
+
import { createMockRAGProvider } from "@directive-run/ai/testing";
|
|
316
|
+
|
|
317
|
+
// Production: your retrieval logic
|
|
318
|
+
const ragProvider = createRAGProvider({
|
|
319
|
+
retrieve: async (query, options) => {
|
|
320
|
+
const chunks = await vectorDB.similaritySearch(query, {
|
|
321
|
+
limit: options.topK ?? 5,
|
|
322
|
+
minScore: options.minScore ?? 0.7,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
return chunks.map((chunk) => ({
|
|
326
|
+
content: chunk.text,
|
|
327
|
+
metadata: chunk.metadata,
|
|
328
|
+
score: chunk.score,
|
|
329
|
+
}));
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Testing: mock retrieval results
|
|
334
|
+
const mockRAG = createMockRAGProvider({
|
|
335
|
+
results: [
|
|
336
|
+
{ content: "Paris is the capital of France.", score: 0.95, metadata: {} },
|
|
337
|
+
],
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Use in resolver
|
|
341
|
+
resolvers: {
|
|
342
|
+
answer: {
|
|
343
|
+
requirement: "ANSWER",
|
|
344
|
+
resolve: async (req, context) => {
|
|
345
|
+
const sources = await context.rag.retrieve(context.facts.question, { topK: 3 });
|
|
346
|
+
const contextText = sources.map((s) => s.content).join("\n\n");
|
|
347
|
+
|
|
348
|
+
const result = await context.runner.run({
|
|
349
|
+
prompt: `Sources:\n${contextText}\n\nQuestion: ${context.facts.question}`,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
context.facts.answer = result.text;
|
|
353
|
+
context.facts.sourceCount = sources.length;
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Critical Anti-Patterns
|
|
362
|
+
|
|
363
|
+
## Using real API keys in CI tests
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
// WRONG – slow, expensive, flaky, exposes keys in logs
|
|
367
|
+
const orchestrator = createAgentOrchestrator({
|
|
368
|
+
runner: createAnthropicRunner({ apiKey: process.env.ANTHROPIC_API_KEY, model: "claude-opus-4-6" }),
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// CORRECT – always inject mock runners in tests
|
|
372
|
+
const orchestrator = buildOrchestrator(
|
|
373
|
+
createMockRunner({ responses: [{ text: "ok", usage: { inputTokens: 2, outputTokens: 1, totalTokens: 3 } }] })
|
|
374
|
+
);
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Testing implementation instead of behavior
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
// WRONG – brittle, breaks if impl changes
|
|
381
|
+
const spy = vi.spyOn(runner, "run");
|
|
382
|
+
expect(spy).toHaveBeenCalledOnce();
|
|
383
|
+
|
|
384
|
+
// CORRECT – test observable facts
|
|
385
|
+
expect(result.facts.status).toBe("done");
|
|
386
|
+
expect(result.facts.output).toBeTruthy();
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Resolver parameter naming
|
|
390
|
+
Always use `(req, context)` – never `(req, ctx)` or `(request, context)`.
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Reference Files
|
|
395
|
+
|
|
396
|
+
- `ai-testing-evals.md` – Full testing API, createMockRunner options, evaluation suite patterns
|
|
397
|
+
- `ai-debug-observability.md` – Tracing plugins, logging config, metrics collection, inspector API
|
|
398
|
+
- `ai-mcp-rag.md` – MCP transport options, tool provider interface, RAG retrieval config
|