@peakinfer/cli 1.0.133

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 (367) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/.env.example +6 -0
  3. package/.github/workflows/peakinfer.yml +64 -0
  4. package/CHANGELOG.md +31 -0
  5. package/LICENSE +190 -0
  6. package/README.md +335 -0
  7. package/data/inferencemax.json +274 -0
  8. package/dist/agent-analyzer.d.ts +45 -0
  9. package/dist/agent-analyzer.d.ts.map +1 -0
  10. package/dist/agent-analyzer.js +374 -0
  11. package/dist/agent-analyzer.js.map +1 -0
  12. package/dist/agent.d.ts +76 -0
  13. package/dist/agent.d.ts.map +1 -0
  14. package/dist/agent.js +965 -0
  15. package/dist/agent.js.map +1 -0
  16. package/dist/agents/correlation-analyzer.d.ts +34 -0
  17. package/dist/agents/correlation-analyzer.d.ts.map +1 -0
  18. package/dist/agents/correlation-analyzer.js +261 -0
  19. package/dist/agents/correlation-analyzer.js.map +1 -0
  20. package/dist/agents/index.d.ts +91 -0
  21. package/dist/agents/index.d.ts.map +1 -0
  22. package/dist/agents/index.js +111 -0
  23. package/dist/agents/index.js.map +1 -0
  24. package/dist/agents/runtime-analyzer.d.ts +38 -0
  25. package/dist/agents/runtime-analyzer.d.ts.map +1 -0
  26. package/dist/agents/runtime-analyzer.js +244 -0
  27. package/dist/agents/runtime-analyzer.js.map +1 -0
  28. package/dist/analysis-types.d.ts +500 -0
  29. package/dist/analysis-types.d.ts.map +1 -0
  30. package/dist/analysis-types.js +11 -0
  31. package/dist/analysis-types.js.map +1 -0
  32. package/dist/analytics.d.ts +25 -0
  33. package/dist/analytics.d.ts.map +1 -0
  34. package/dist/analytics.js +94 -0
  35. package/dist/analytics.js.map +1 -0
  36. package/dist/analyzer.d.ts +48 -0
  37. package/dist/analyzer.d.ts.map +1 -0
  38. package/dist/analyzer.js +547 -0
  39. package/dist/analyzer.js.map +1 -0
  40. package/dist/artifacts.d.ts +44 -0
  41. package/dist/artifacts.d.ts.map +1 -0
  42. package/dist/artifacts.js +165 -0
  43. package/dist/artifacts.js.map +1 -0
  44. package/dist/benchmarks/index.d.ts +88 -0
  45. package/dist/benchmarks/index.d.ts.map +1 -0
  46. package/dist/benchmarks/index.js +205 -0
  47. package/dist/benchmarks/index.js.map +1 -0
  48. package/dist/cli.d.ts +3 -0
  49. package/dist/cli.d.ts.map +1 -0
  50. package/dist/cli.js +427 -0
  51. package/dist/cli.js.map +1 -0
  52. package/dist/commands/ci.d.ts +19 -0
  53. package/dist/commands/ci.d.ts.map +1 -0
  54. package/dist/commands/ci.js +253 -0
  55. package/dist/commands/ci.js.map +1 -0
  56. package/dist/commands/config.d.ts +16 -0
  57. package/dist/commands/config.d.ts.map +1 -0
  58. package/dist/commands/config.js +249 -0
  59. package/dist/commands/config.js.map +1 -0
  60. package/dist/commands/demo.d.ts +15 -0
  61. package/dist/commands/demo.d.ts.map +1 -0
  62. package/dist/commands/demo.js +106 -0
  63. package/dist/commands/demo.js.map +1 -0
  64. package/dist/commands/export.d.ts +14 -0
  65. package/dist/commands/export.d.ts.map +1 -0
  66. package/dist/commands/export.js +209 -0
  67. package/dist/commands/export.js.map +1 -0
  68. package/dist/commands/history.d.ts +15 -0
  69. package/dist/commands/history.d.ts.map +1 -0
  70. package/dist/commands/history.js +389 -0
  71. package/dist/commands/history.js.map +1 -0
  72. package/dist/commands/template.d.ts +14 -0
  73. package/dist/commands/template.d.ts.map +1 -0
  74. package/dist/commands/template.js +341 -0
  75. package/dist/commands/template.js.map +1 -0
  76. package/dist/commands/validate-map.d.ts +12 -0
  77. package/dist/commands/validate-map.d.ts.map +1 -0
  78. package/dist/commands/validate-map.js +274 -0
  79. package/dist/commands/validate-map.js.map +1 -0
  80. package/dist/commands/whatif.d.ts +17 -0
  81. package/dist/commands/whatif.d.ts.map +1 -0
  82. package/dist/commands/whatif.js +206 -0
  83. package/dist/commands/whatif.js.map +1 -0
  84. package/dist/comparison.d.ts +38 -0
  85. package/dist/comparison.d.ts.map +1 -0
  86. package/dist/comparison.js +223 -0
  87. package/dist/comparison.js.map +1 -0
  88. package/dist/config.d.ts +42 -0
  89. package/dist/config.d.ts.map +1 -0
  90. package/dist/config.js +158 -0
  91. package/dist/config.js.map +1 -0
  92. package/dist/connectors/helicone.d.ts +9 -0
  93. package/dist/connectors/helicone.d.ts.map +1 -0
  94. package/dist/connectors/helicone.js +106 -0
  95. package/dist/connectors/helicone.js.map +1 -0
  96. package/dist/connectors/index.d.ts +37 -0
  97. package/dist/connectors/index.d.ts.map +1 -0
  98. package/dist/connectors/index.js +65 -0
  99. package/dist/connectors/index.js.map +1 -0
  100. package/dist/connectors/langsmith.d.ts +9 -0
  101. package/dist/connectors/langsmith.d.ts.map +1 -0
  102. package/dist/connectors/langsmith.js +122 -0
  103. package/dist/connectors/langsmith.js.map +1 -0
  104. package/dist/connectors/types.d.ts +83 -0
  105. package/dist/connectors/types.d.ts.map +1 -0
  106. package/dist/connectors/types.js +98 -0
  107. package/dist/connectors/types.js.map +1 -0
  108. package/dist/cost-estimator.d.ts +46 -0
  109. package/dist/cost-estimator.d.ts.map +1 -0
  110. package/dist/cost-estimator.js +104 -0
  111. package/dist/cost-estimator.js.map +1 -0
  112. package/dist/costs.d.ts +57 -0
  113. package/dist/costs.d.ts.map +1 -0
  114. package/dist/costs.js +251 -0
  115. package/dist/costs.js.map +1 -0
  116. package/dist/counterfactuals.d.ts +29 -0
  117. package/dist/counterfactuals.d.ts.map +1 -0
  118. package/dist/counterfactuals.js +448 -0
  119. package/dist/counterfactuals.js.map +1 -0
  120. package/dist/enhancement-prompts.d.ts +41 -0
  121. package/dist/enhancement-prompts.d.ts.map +1 -0
  122. package/dist/enhancement-prompts.js +88 -0
  123. package/dist/enhancement-prompts.js.map +1 -0
  124. package/dist/envelopes.d.ts +20 -0
  125. package/dist/envelopes.d.ts.map +1 -0
  126. package/dist/envelopes.js +790 -0
  127. package/dist/envelopes.js.map +1 -0
  128. package/dist/format-normalizer.d.ts +71 -0
  129. package/dist/format-normalizer.d.ts.map +1 -0
  130. package/dist/format-normalizer.js +1331 -0
  131. package/dist/format-normalizer.js.map +1 -0
  132. package/dist/history.d.ts +79 -0
  133. package/dist/history.d.ts.map +1 -0
  134. package/dist/history.js +313 -0
  135. package/dist/history.js.map +1 -0
  136. package/dist/html.d.ts +11 -0
  137. package/dist/html.d.ts.map +1 -0
  138. package/dist/html.js +463 -0
  139. package/dist/html.js.map +1 -0
  140. package/dist/impact.d.ts +42 -0
  141. package/dist/impact.d.ts.map +1 -0
  142. package/dist/impact.js +443 -0
  143. package/dist/impact.js.map +1 -0
  144. package/dist/index.d.ts +26 -0
  145. package/dist/index.d.ts.map +1 -0
  146. package/dist/index.js +34 -0
  147. package/dist/index.js.map +1 -0
  148. package/dist/insights.d.ts +5 -0
  149. package/dist/insights.d.ts.map +1 -0
  150. package/dist/insights.js +271 -0
  151. package/dist/insights.js.map +1 -0
  152. package/dist/joiner.d.ts +9 -0
  153. package/dist/joiner.d.ts.map +1 -0
  154. package/dist/joiner.js +247 -0
  155. package/dist/joiner.js.map +1 -0
  156. package/dist/orchestrator.d.ts +34 -0
  157. package/dist/orchestrator.d.ts.map +1 -0
  158. package/dist/orchestrator.js +827 -0
  159. package/dist/orchestrator.js.map +1 -0
  160. package/dist/pdf.d.ts +26 -0
  161. package/dist/pdf.d.ts.map +1 -0
  162. package/dist/pdf.js +84 -0
  163. package/dist/pdf.js.map +1 -0
  164. package/dist/prediction.d.ts +33 -0
  165. package/dist/prediction.d.ts.map +1 -0
  166. package/dist/prediction.js +316 -0
  167. package/dist/prediction.js.map +1 -0
  168. package/dist/prompts/loader.d.ts +38 -0
  169. package/dist/prompts/loader.d.ts.map +1 -0
  170. package/dist/prompts/loader.js +60 -0
  171. package/dist/prompts/loader.js.map +1 -0
  172. package/dist/renderer.d.ts +64 -0
  173. package/dist/renderer.d.ts.map +1 -0
  174. package/dist/renderer.js +923 -0
  175. package/dist/renderer.js.map +1 -0
  176. package/dist/runid.d.ts +57 -0
  177. package/dist/runid.d.ts.map +1 -0
  178. package/dist/runid.js +199 -0
  179. package/dist/runid.js.map +1 -0
  180. package/dist/runtime.d.ts +29 -0
  181. package/dist/runtime.d.ts.map +1 -0
  182. package/dist/runtime.js +366 -0
  183. package/dist/runtime.js.map +1 -0
  184. package/dist/scanner.d.ts +11 -0
  185. package/dist/scanner.d.ts.map +1 -0
  186. package/dist/scanner.js +426 -0
  187. package/dist/scanner.js.map +1 -0
  188. package/dist/templates.d.ts +120 -0
  189. package/dist/templates.d.ts.map +1 -0
  190. package/dist/templates.js +429 -0
  191. package/dist/templates.js.map +1 -0
  192. package/dist/tools/index.d.ts +153 -0
  193. package/dist/tools/index.d.ts.map +1 -0
  194. package/dist/tools/index.js +177 -0
  195. package/dist/tools/index.js.map +1 -0
  196. package/dist/types.d.ts +3647 -0
  197. package/dist/types.d.ts.map +1 -0
  198. package/dist/types.js +703 -0
  199. package/dist/types.js.map +1 -0
  200. package/dist/version.d.ts +7 -0
  201. package/dist/version.d.ts.map +1 -0
  202. package/dist/version.js +23 -0
  203. package/dist/version.js.map +1 -0
  204. package/docs/demo-guide.md +423 -0
  205. package/docs/events-format.md +295 -0
  206. package/docs/inferencemap-spec.md +344 -0
  207. package/docs/migration-v2.md +293 -0
  208. package/fixtures/demo/precomputed.json +142 -0
  209. package/fixtures/demo-project/README.md +52 -0
  210. package/fixtures/demo-project/ai-service.ts +65 -0
  211. package/fixtures/demo-project/sample-events.jsonl +15 -0
  212. package/fixtures/demo-project/src/ai-service.ts +128 -0
  213. package/fixtures/demo-project/src/llm-client.ts +155 -0
  214. package/package.json +65 -0
  215. package/prompts/agent-analyzer.yaml +47 -0
  216. package/prompts/ci-gate.yaml +98 -0
  217. package/prompts/correlation-analyzer.yaml +178 -0
  218. package/prompts/format-normalizer.yaml +46 -0
  219. package/prompts/peak-performance.yaml +180 -0
  220. package/prompts/pr-comment.yaml +111 -0
  221. package/prompts/runtime-analyzer.yaml +189 -0
  222. package/prompts/unified-analyzer.yaml +241 -0
  223. package/schemas/inference-map.v0.1.json +215 -0
  224. package/scripts/benchmark.ts +394 -0
  225. package/scripts/demo-v1.5.sh +158 -0
  226. package/scripts/sync-from-site.sh +197 -0
  227. package/scripts/validate-sync.sh +178 -0
  228. package/src/agent-analyzer.ts +481 -0
  229. package/src/agent.ts +1232 -0
  230. package/src/agents/correlation-analyzer.ts +353 -0
  231. package/src/agents/index.ts +235 -0
  232. package/src/agents/runtime-analyzer.ts +343 -0
  233. package/src/analysis-types.ts +558 -0
  234. package/src/analytics.ts +100 -0
  235. package/src/analyzer.ts +692 -0
  236. package/src/artifacts.ts +218 -0
  237. package/src/benchmarks/index.ts +309 -0
  238. package/src/cli.ts +503 -0
  239. package/src/commands/ci.ts +336 -0
  240. package/src/commands/config.ts +288 -0
  241. package/src/commands/demo.ts +175 -0
  242. package/src/commands/export.ts +297 -0
  243. package/src/commands/history.ts +425 -0
  244. package/src/commands/template.ts +385 -0
  245. package/src/commands/validate-map.ts +324 -0
  246. package/src/commands/whatif.ts +272 -0
  247. package/src/comparison.ts +283 -0
  248. package/src/config.ts +188 -0
  249. package/src/connectors/helicone.ts +164 -0
  250. package/src/connectors/index.ts +93 -0
  251. package/src/connectors/langsmith.ts +179 -0
  252. package/src/connectors/types.ts +180 -0
  253. package/src/cost-estimator.ts +146 -0
  254. package/src/costs.ts +347 -0
  255. package/src/counterfactuals.ts +516 -0
  256. package/src/enhancement-prompts.ts +118 -0
  257. package/src/envelopes.ts +814 -0
  258. package/src/format-normalizer.ts +1486 -0
  259. package/src/history.ts +400 -0
  260. package/src/html.ts +512 -0
  261. package/src/impact.ts +522 -0
  262. package/src/index.ts +83 -0
  263. package/src/insights.ts +341 -0
  264. package/src/joiner.ts +289 -0
  265. package/src/orchestrator.ts +1015 -0
  266. package/src/pdf.ts +110 -0
  267. package/src/prediction.ts +392 -0
  268. package/src/prompts/loader.ts +88 -0
  269. package/src/renderer.ts +1045 -0
  270. package/src/runid.ts +261 -0
  271. package/src/runtime.ts +450 -0
  272. package/src/scanner.ts +508 -0
  273. package/src/templates.ts +561 -0
  274. package/src/tools/index.ts +214 -0
  275. package/src/types.ts +873 -0
  276. package/src/version.ts +24 -0
  277. package/templates/context-accumulation.yaml +23 -0
  278. package/templates/cost-concentration.yaml +20 -0
  279. package/templates/dead-code.yaml +20 -0
  280. package/templates/latency-explainer.yaml +23 -0
  281. package/templates/optimizations/ab-testing-framework.yaml +74 -0
  282. package/templates/optimizations/api-gateway-optimization.yaml +81 -0
  283. package/templates/optimizations/api-model-routing-strategy.yaml +126 -0
  284. package/templates/optimizations/auto-scaling-optimization.yaml +85 -0
  285. package/templates/optimizations/batch-utilization-diagnostic.yaml +142 -0
  286. package/templates/optimizations/comprehensive-apm.yaml +76 -0
  287. package/templates/optimizations/context-window-optimization.yaml +91 -0
  288. package/templates/optimizations/cost-sensitive-batch-processing.yaml +77 -0
  289. package/templates/optimizations/distributed-training-optimization.yaml +77 -0
  290. package/templates/optimizations/document-analysis-edge.yaml +77 -0
  291. package/templates/optimizations/document-pipeline-optimization.yaml +78 -0
  292. package/templates/optimizations/domain-specific-distillation.yaml +78 -0
  293. package/templates/optimizations/error-handling-optimization.yaml +76 -0
  294. package/templates/optimizations/gptq-4bit-quantization.yaml +96 -0
  295. package/templates/optimizations/long-context-memory-management.yaml +78 -0
  296. package/templates/optimizations/max-tokens-optimization.yaml +76 -0
  297. package/templates/optimizations/memory-bandwidth-optimization.yaml +73 -0
  298. package/templates/optimizations/multi-framework-resilience.yaml +75 -0
  299. package/templates/optimizations/multi-tenant-optimization.yaml +75 -0
  300. package/templates/optimizations/prompt-caching-optimization.yaml +143 -0
  301. package/templates/optimizations/pytorch-to-onnx-migration.yaml +109 -0
  302. package/templates/optimizations/quality-monitoring.yaml +74 -0
  303. package/templates/optimizations/realtime-budget-controls.yaml +74 -0
  304. package/templates/optimizations/realtime-latency-optimization.yaml +74 -0
  305. package/templates/optimizations/sglang-concurrency-optimization.yaml +78 -0
  306. package/templates/optimizations/smart-model-routing.yaml +96 -0
  307. package/templates/optimizations/streaming-batch-selection.yaml +167 -0
  308. package/templates/optimizations/system-prompt-optimization.yaml +75 -0
  309. package/templates/optimizations/tensorrt-llm-performance.yaml +77 -0
  310. package/templates/optimizations/vllm-high-throughput-optimization.yaml +93 -0
  311. package/templates/optimizations/vllm-migration-memory-bound.yaml +78 -0
  312. package/templates/overpowered-extraction.yaml +32 -0
  313. package/templates/overpowered-model.yaml +31 -0
  314. package/templates/prompt-bloat.yaml +24 -0
  315. package/templates/retry-explosion.yaml +28 -0
  316. package/templates/schema/insight.schema.json +113 -0
  317. package/templates/schema/optimization.schema.json +180 -0
  318. package/templates/streaming-drift.yaml +30 -0
  319. package/templates/throughput-gap.yaml +21 -0
  320. package/templates/token-underutilization.yaml +28 -0
  321. package/templates/untested-fallback.yaml +21 -0
  322. package/tests/accuracy/drift-detection.test.ts +184 -0
  323. package/tests/accuracy/false-positives.test.ts +166 -0
  324. package/tests/accuracy/templates.test.ts +205 -0
  325. package/tests/action/commands.test.ts +125 -0
  326. package/tests/action/comments.test.ts +347 -0
  327. package/tests/cli.test.ts +203 -0
  328. package/tests/comparison.test.ts +309 -0
  329. package/tests/correlation-analyzer.test.ts +534 -0
  330. package/tests/counterfactuals.test.ts +347 -0
  331. package/tests/fixtures/events/missing-id.jsonl +1 -0
  332. package/tests/fixtures/events/missing-input.jsonl +1 -0
  333. package/tests/fixtures/events/missing-latency.jsonl +1 -0
  334. package/tests/fixtures/events/missing-model.jsonl +1 -0
  335. package/tests/fixtures/events/missing-output.jsonl +1 -0
  336. package/tests/fixtures/events/missing-provider.jsonl +1 -0
  337. package/tests/fixtures/events/missing-ts.jsonl +1 -0
  338. package/tests/fixtures/events/valid.csv +3 -0
  339. package/tests/fixtures/events/valid.json +1 -0
  340. package/tests/fixtures/events/valid.jsonl +2 -0
  341. package/tests/fixtures/events/with-callsite.jsonl +1 -0
  342. package/tests/fixtures/events/with-intent.jsonl +1 -0
  343. package/tests/fixtures/events/wrong-type.jsonl +1 -0
  344. package/tests/fixtures/repos/empty/.gitkeep +0 -0
  345. package/tests/fixtures/repos/hybrid-router/router.py +35 -0
  346. package/tests/fixtures/repos/saas-anthropic/agent.ts +27 -0
  347. package/tests/fixtures/repos/saas-openai/assistant.js +33 -0
  348. package/tests/fixtures/repos/saas-openai/client.py +26 -0
  349. package/tests/fixtures/repos/self-hosted-vllm/inference.py +22 -0
  350. package/tests/github-action.test.ts +292 -0
  351. package/tests/insights.test.ts +878 -0
  352. package/tests/joiner.test.ts +168 -0
  353. package/tests/performance/action-latency.test.ts +132 -0
  354. package/tests/performance/benchmark.test.ts +189 -0
  355. package/tests/performance/cli-latency.test.ts +102 -0
  356. package/tests/pr-comment.test.ts +313 -0
  357. package/tests/prediction.test.ts +296 -0
  358. package/tests/runtime-analyzer.test.ts +375 -0
  359. package/tests/runtime.test.ts +205 -0
  360. package/tests/scanner.test.ts +122 -0
  361. package/tests/template-conformance.test.ts +526 -0
  362. package/tests/unit/cost-calculator.test.ts +303 -0
  363. package/tests/unit/credits.test.ts +180 -0
  364. package/tests/unit/inference-map.test.ts +276 -0
  365. package/tests/unit/schema.test.ts +300 -0
  366. package/tsconfig.json +20 -0
  367. package/vitest.config.ts +14 -0
@@ -0,0 +1,122 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
+ import { scan } from '../src/scanner.js';
3
+ import { join } from 'path';
4
+
5
+ const FIXTURES_DIR = join(__dirname, 'fixtures', 'repos');
6
+
7
+ describe('scanner', () => {
8
+ describe('file discovery', () => {
9
+ it('finds Python files', async () => {
10
+ const result = await scan(join(FIXTURES_DIR, 'self-hosted-vllm'));
11
+ const pyFiles = result.files.filter(f => f.language === 'python');
12
+ expect(pyFiles.length).toBeGreaterThan(0);
13
+ });
14
+
15
+ it('finds TypeScript files', async () => {
16
+ const result = await scan(join(FIXTURES_DIR, 'saas-anthropic'));
17
+ const tsFiles = result.files.filter(f => f.language === 'typescript');
18
+ expect(tsFiles.length).toBeGreaterThan(0);
19
+ });
20
+
21
+ it('finds JavaScript files', async () => {
22
+ const result = await scan(join(FIXTURES_DIR, 'saas-openai'));
23
+ const jsFiles = result.files.filter(f => f.language === 'javascript');
24
+ expect(jsFiles.length).toBeGreaterThan(0);
25
+ });
26
+ });
27
+
28
+ describe('ignores', () => {
29
+ it('ignores node_modules by default', async () => {
30
+ const result = await scan(join(FIXTURES_DIR, 'saas-openai'));
31
+ const nodeModules = result.files.filter(f => f.path.includes('node_modules'));
32
+ expect(nodeModules.length).toBe(0);
33
+ });
34
+
35
+ it('ignores dist by default', async () => {
36
+ const result = await scan(join(FIXTURES_DIR, 'saas-openai'));
37
+ const dist = result.files.filter(f => f.path.includes('/dist/'));
38
+ expect(dist.length).toBe(0);
39
+ });
40
+
41
+ it('ignores .git by default', async () => {
42
+ const result = await scan(join(FIXTURES_DIR, 'saas-openai'));
43
+ const git = result.files.filter(f => f.path.includes('.git/'));
44
+ expect(git.length).toBe(0);
45
+ });
46
+ });
47
+
48
+ describe('line counting', () => {
49
+ it('counts lines of code per file', async () => {
50
+ const result = await scan(join(FIXTURES_DIR, 'self-hosted-vllm'));
51
+ for (const file of result.files) {
52
+ expect(file.loc).toBeGreaterThan(0);
53
+ }
54
+ });
55
+ });
56
+
57
+ describe('language detection', () => {
58
+ it('detects language from .py extension', async () => {
59
+ const result = await scan(join(FIXTURES_DIR, 'self-hosted-vllm'));
60
+ const pyFile = result.files.find(f => f.path.endsWith('.py'));
61
+ expect(pyFile?.language).toBe('python');
62
+ });
63
+
64
+ it('detects language from .ts extension', async () => {
65
+ const result = await scan(join(FIXTURES_DIR, 'saas-anthropic'));
66
+ const tsFile = result.files.find(f => f.path.endsWith('.ts'));
67
+ expect(tsFile?.language).toBe('typescript');
68
+ });
69
+
70
+ it('detects language from .js extension', async () => {
71
+ const result = await scan(join(FIXTURES_DIR, 'saas-openai'));
72
+ const jsFile = result.files.find(f => f.path.endsWith('.js'));
73
+ expect(jsFile?.language).toBe('javascript');
74
+ });
75
+ });
76
+
77
+ describe('summary', () => {
78
+ it('returns correct totalFiles', async () => {
79
+ const result = await scan(join(FIXTURES_DIR, 'hybrid-router'));
80
+ expect(result.summary.totalFiles).toBe(result.files.length);
81
+ });
82
+
83
+ it('returns correct totalLoc', async () => {
84
+ const result = await scan(join(FIXTURES_DIR, 'hybrid-router'));
85
+ const sumLoc = result.files.reduce((sum, f) => sum + f.loc, 0);
86
+ expect(result.summary.totalLoc).toBe(sumLoc);
87
+ });
88
+
89
+ it('returns languages list', async () => {
90
+ const result = await scan(join(FIXTURES_DIR, 'hybrid-router'));
91
+ expect(result.summary.languages).toContain('python');
92
+ });
93
+ });
94
+
95
+ describe('edge cases', () => {
96
+ it('returns empty result for empty directory', async () => {
97
+ const result = await scan(join(FIXTURES_DIR, 'empty'));
98
+ expect(result.files.length).toBe(0);
99
+ expect(result.summary.totalFiles).toBe(0);
100
+ expect(result.summary.totalLoc).toBe(0);
101
+ });
102
+
103
+ it('throws for non-existent directory', async () => {
104
+ await expect(scan('/non/existent/path')).rejects.toThrow();
105
+ });
106
+ });
107
+
108
+ describe('result structure', () => {
109
+ it('includes root path', async () => {
110
+ const path = join(FIXTURES_DIR, 'hybrid-router');
111
+ const result = await scan(path);
112
+ expect(result.root).toBe(path);
113
+ });
114
+
115
+ it('files have relative paths', async () => {
116
+ const result = await scan(join(FIXTURES_DIR, 'hybrid-router'));
117
+ for (const file of result.files) {
118
+ expect(file.path.startsWith('/')).toBe(false);
119
+ }
120
+ });
121
+ });
122
+ });
@@ -0,0 +1,526 @@
1
+ /**
2
+ * Template Conformance Tests
3
+ *
4
+ * Validates that LLM responses from RuntimeAnalyzerAgent and CorrelationAnalyzerAgent
5
+ * conform to the expected output format defined in their YAML prompts.
6
+ *
7
+ * Run with API key for real LLM testing:
8
+ * source .env && npx vitest run tests/template-conformance.test.ts
9
+ */
10
+
11
+ import { describe, it, expect, beforeAll } from 'vitest';
12
+ import { z } from 'zod';
13
+ import { RuntimeAnalyzerAgent, type RuntimeAnalyzerInput } from '../src/agents/runtime-analyzer.js';
14
+ import { CorrelationAnalyzerAgent, type CorrelationAnalyzerInput } from '../src/agents/correlation-analyzer.js';
15
+ import { setTestPricing } from '../src/costs.js';
16
+ import type { Callsite, InferenceEvent, RuntimeSummary } from '../src/types.js';
17
+
18
+ // =============================================================================
19
+ // EXPECTED SCHEMAS (from prompts/*.yaml output_format sections)
20
+ // =============================================================================
21
+
22
+ /**
23
+ * RuntimeAnalyzerAgent expected output schema
24
+ * From: prompts/runtime-analyzer.yaml <output_format>
25
+ */
26
+ const RuntimeInsightSchema = z.object({
27
+ severity: z.enum(['critical', 'warning', 'info']),
28
+ category: z.enum(['cost', 'latency', 'reliability', 'throughput', 'waste']),
29
+ headline: z.string().min(1),
30
+ evidence: z.string().min(1),
31
+ recommendation: z.string().optional(),
32
+ impact: z.object({
33
+ layer: z.enum(['application', 'model', 'runtime', 'infrastructure']),
34
+ impactType: z.enum(['cost', 'latency', 'throughput']),
35
+ estimatedImpactPercent: z.number().min(0).max(100),
36
+ effort: z.enum(['low', 'medium', 'high']),
37
+ }).optional(),
38
+ });
39
+
40
+ const RuntimeDetectedPatternsSchema = z.object({
41
+ applicationType: z.enum(['rag', 'agent', 'batch', 'chat', 'pipeline', 'unknown']),
42
+ multiModelPipeline: z.boolean(),
43
+ streamingDetected: z.boolean(),
44
+ batchingDetected: z.boolean(),
45
+ cachingDetected: z.boolean(),
46
+ });
47
+
48
+ const RuntimeSummaryOutputSchema = z.object({
49
+ totalCalls: z.number(),
50
+ totalTokens: z.number(),
51
+ dominantProvider: z.string(),
52
+ dominantModel: z.string(),
53
+ estimatedDailyCostUSD: z.number(),
54
+ });
55
+
56
+ const RuntimeAnalyzerOutputSchema = z.object({
57
+ insights: z.array(RuntimeInsightSchema),
58
+ detectedPatterns: RuntimeDetectedPatternsSchema,
59
+ summary: RuntimeSummaryOutputSchema,
60
+ });
61
+
62
+ /**
63
+ * CorrelationAnalyzerAgent expected output schema
64
+ * From: prompts/correlation-analyzer.yaml <output_format>
65
+ */
66
+ const DriftSignalSchema = z.object({
67
+ type: z.enum(['codeOnly', 'runtimeOnly', 'mismatch', 'patternDrift']),
68
+ provider: z.string().optional(),
69
+ model: z.string().optional(),
70
+ callsiteId: z.string().optional(),
71
+ message: z.string(),
72
+ });
73
+
74
+ const CorrelationSummarySchema = z.object({
75
+ totalCodeCallsites: z.number(),
76
+ totalRuntimeModels: z.number(),
77
+ matched: z.number(),
78
+ codeOnly: z.number(),
79
+ runtimeOnly: z.number(),
80
+ mismatched: z.number(),
81
+ });
82
+
83
+ const CorrelationInsightSchema = z.object({
84
+ id: z.string().optional(),
85
+ severity: z.enum(['critical', 'warning', 'info']),
86
+ category: z.enum(['cost', 'latency', 'drift', 'reliability', 'waste', 'throughput', 'security', 'best-practice']),
87
+ headline: z.string().min(1),
88
+ evidence: z.string().min(1),
89
+ location: z.string().optional(),
90
+ recommendation: z.string().optional(),
91
+ source: z.enum(['template', 'llm']).optional(),
92
+ impact: z.object({
93
+ layer: z.enum(['application', 'model', 'runtime', 'infrastructure']),
94
+ impactType: z.enum(['cost', 'latency', 'throughput']),
95
+ estimatedImpactPercent: z.number().min(0).max(100),
96
+ effort: z.enum(['low', 'medium', 'high']),
97
+ confidence: z.number().min(0).max(1).optional(),
98
+ }).optional(),
99
+ });
100
+
101
+ const CorrelationAnalyzerOutputSchema = z.object({
102
+ insights: z.array(CorrelationInsightSchema),
103
+ driftSignals: z.array(DriftSignalSchema),
104
+ correlationSummary: CorrelationSummarySchema,
105
+ alignmentScore: z.number().min(0).max(1),
106
+ overallAssessment: z.string().min(1),
107
+ });
108
+
109
+ // =============================================================================
110
+ // TEST FIXTURES
111
+ // =============================================================================
112
+
113
+ beforeAll(() => {
114
+ setTestPricing({
115
+ 'gpt-4o': { input: 5.0, output: 15.0 },
116
+ 'gpt-4o-mini': { input: 0.15, output: 0.6 },
117
+ 'claude-3-opus': { input: 15.0, output: 75.0 },
118
+ 'text-embedding-3-large': { input: 0.13, output: 0.0 },
119
+ });
120
+ });
121
+
122
+ const makeEvent = (overrides: Partial<InferenceEvent> = {}): InferenceEvent => ({
123
+ id: `evt_${Math.random().toString(36).slice(2, 9)}`,
124
+ ts: '2024-01-15T10:30:00Z',
125
+ provider: 'openai',
126
+ model: 'gpt-4o',
127
+ input_tokens: 500,
128
+ output_tokens: 200,
129
+ latency_ms: 1200,
130
+ ...overrides,
131
+ });
132
+
133
+ const makeCallsite = (overrides: Partial<Callsite> = {}): Callsite => ({
134
+ id: `cs_${Math.random().toString(36).slice(2, 9)}`,
135
+ file: 'src/api/chat.ts',
136
+ line: 42,
137
+ provider: 'openai',
138
+ model: 'gpt-4o',
139
+ framework: null,
140
+ runtime: null,
141
+ patterns: { streaming: true },
142
+ confidence: 0.95,
143
+ ...overrides,
144
+ });
145
+
146
+ const makeRuntimeSummary = (): RuntimeSummary => ({
147
+ totalEvents: 100,
148
+ byProvider: {
149
+ openai: { calls: 80, tokens_in: 40000, tokens_out: 16000, latency_p50: 1000, latency_p95: 2500, latency_p99: 4000 },
150
+ anthropic: { calls: 20, tokens_in: 10000, tokens_out: 4000, latency_p50: 1200, latency_p95: 2800, latency_p99: 4500 },
151
+ },
152
+ byModel: {
153
+ 'gpt-4o': { calls: 60, tokens_in: 30000, tokens_out: 12000, latency_p50: 1000, latency_p95: 2500, latency_p99: 4000 },
154
+ 'gpt-4o-mini': { calls: 20, tokens_in: 10000, tokens_out: 4000, latency_p50: 400, latency_p95: 800, latency_p99: 1200 },
155
+ 'claude-3-opus': { calls: 20, tokens_in: 10000, tokens_out: 4000, latency_p50: 1200, latency_p95: 2800, latency_p99: 4500 },
156
+ },
157
+ global: { p50: 1000, p95: 2500, p99: 4000 },
158
+ });
159
+
160
+ // =============================================================================
161
+ // RUNTIME ANALYZER TEMPLATE CONFORMANCE TESTS
162
+ // =============================================================================
163
+
164
+ describe('RuntimeAnalyzerAgent Template Conformance', () => {
165
+ describe('output schema validation', () => {
166
+ it('returns output conforming to template schema', async () => {
167
+ const input: RuntimeAnalyzerInput = {
168
+ events: Array.from({ length: 20 }, (_, i) => makeEvent({
169
+ id: `evt_${i}`,
170
+ model: i < 15 ? 'gpt-4o' : 'gpt-4o-mini',
171
+ input_tokens: 500 + (i * 50),
172
+ output_tokens: 100 + (i * 10),
173
+ latency_ms: 800 + (i * 100),
174
+ })),
175
+ runtimeSummary: makeRuntimeSummary(),
176
+ };
177
+
178
+ const { result } = await RuntimeAnalyzerAgent.execute(input);
179
+
180
+ // Validate against schema
181
+ const validation = RuntimeAnalyzerOutputSchema.safeParse(result);
182
+
183
+ if (!validation.success) {
184
+ console.error('Schema validation errors:', JSON.stringify(validation.error.issues, null, 2));
185
+ }
186
+
187
+ expect(validation.success).toBe(true);
188
+ });
189
+
190
+ it('insights have all required fields from template', async () => {
191
+ const input: RuntimeAnalyzerInput = {
192
+ events: [
193
+ makeEvent({ model: 'gpt-4o', input_tokens: 10000, output_tokens: 100 }), // Prompt bloat
194
+ ],
195
+ runtimeSummary: makeRuntimeSummary(),
196
+ };
197
+
198
+ const { result } = await RuntimeAnalyzerAgent.execute(input);
199
+
200
+ for (const insight of result.insights) {
201
+ // Required fields from <output_format>
202
+ expect(insight).toHaveProperty('severity');
203
+ expect(insight).toHaveProperty('category');
204
+ expect(insight).toHaveProperty('headline');
205
+ expect(insight).toHaveProperty('evidence');
206
+
207
+ // Enum validation
208
+ expect(['critical', 'warning', 'info']).toContain(insight.severity);
209
+ expect(['cost', 'latency', 'reliability', 'throughput', 'waste']).toContain(insight.category);
210
+
211
+ // Non-empty strings
212
+ expect(insight.headline.length).toBeGreaterThan(0);
213
+ expect(insight.evidence.length).toBeGreaterThan(0);
214
+ }
215
+ });
216
+
217
+ it('detected_patterns matches template enum values', async () => {
218
+ const input: RuntimeAnalyzerInput = {
219
+ events: [makeEvent({ streaming: true, batch_id: 'b1' })],
220
+ runtimeSummary: makeRuntimeSummary(),
221
+ };
222
+
223
+ const { result } = await RuntimeAnalyzerAgent.execute(input);
224
+
225
+ // application_type enum from template
226
+ const validAppTypes = ['rag', 'agent', 'batch', 'chat', 'pipeline', 'unknown'];
227
+ expect(validAppTypes).toContain(result.detectedPatterns.applicationType);
228
+
229
+ // Boolean fields
230
+ expect(typeof result.detectedPatterns.multiModelPipeline).toBe('boolean');
231
+ expect(typeof result.detectedPatterns.streamingDetected).toBe('boolean');
232
+ expect(typeof result.detectedPatterns.batchingDetected).toBe('boolean');
233
+ expect(typeof result.detectedPatterns.cachingDetected).toBe('boolean');
234
+ });
235
+
236
+ it('impact estimates follow template constraints', async () => {
237
+ const input: RuntimeAnalyzerInput = {
238
+ events: Array.from({ length: 10 }, () => makeEvent()),
239
+ runtimeSummary: makeRuntimeSummary(),
240
+ };
241
+
242
+ const { result } = await RuntimeAnalyzerAgent.execute(input);
243
+
244
+ for (const insight of result.insights) {
245
+ if (insight.impact) {
246
+ // layer enum from template
247
+ expect(['application', 'model', 'runtime', 'infrastructure']).toContain(insight.impact.layer);
248
+
249
+ // impactType enum
250
+ expect(['cost', 'latency', 'throughput']).toContain(insight.impact.impactType);
251
+
252
+ // estimatedImpactPercent range (0-100)
253
+ expect(insight.impact.estimatedImpactPercent).toBeGreaterThanOrEqual(0);
254
+ expect(insight.impact.estimatedImpactPercent).toBeLessThanOrEqual(100);
255
+
256
+ // effort enum
257
+ expect(['low', 'medium', 'high']).toContain(insight.impact.effort);
258
+ }
259
+ }
260
+ });
261
+
262
+ it('respects max 10 insights constraint from template', async () => {
263
+ const input: RuntimeAnalyzerInput = {
264
+ events: Array.from({ length: 100 }, (_, i) => makeEvent({
265
+ id: `evt_${i}`,
266
+ model: ['gpt-4o', 'gpt-4o-mini', 'claude-3-opus'][i % 3],
267
+ })),
268
+ runtimeSummary: makeRuntimeSummary(),
269
+ };
270
+
271
+ const { result } = await RuntimeAnalyzerAgent.execute(input);
272
+
273
+ // Template says "Maximum 10 insights, ranked by impact"
274
+ expect(result.insights.length).toBeLessThanOrEqual(10);
275
+ });
276
+ });
277
+
278
+ describe('semantic validation', () => {
279
+ it('insights reference actual data from input', async () => {
280
+ const input: RuntimeAnalyzerInput = {
281
+ events: [
282
+ makeEvent({ model: 'gpt-4o', latency_ms: 5000 }),
283
+ makeEvent({ model: 'gpt-4o', latency_ms: 5500 }),
284
+ ],
285
+ runtimeSummary: {
286
+ totalEvents: 2,
287
+ byProvider: { openai: { calls: 2, tokens_in: 1000, tokens_out: 400, latency_p50: 5250, latency_p95: 5500, latency_p99: 5500 } },
288
+ byModel: { 'gpt-4o': { calls: 2, tokens_in: 1000, tokens_out: 400, latency_p50: 5250, latency_p95: 5500, latency_p99: 5500 } },
289
+ global: { p50: 5250, p95: 5500, p99: 5500 },
290
+ },
291
+ };
292
+
293
+ const { result } = await RuntimeAnalyzerAgent.execute(input);
294
+
295
+ // Summary should reflect actual data
296
+ expect(result.summary.dominantModel).toContain('gpt');
297
+ expect(result.summary.totalCalls).toBeGreaterThanOrEqual(0);
298
+ });
299
+ });
300
+ });
301
+
302
+ // =============================================================================
303
+ // CORRELATION ANALYZER TEMPLATE CONFORMANCE TESTS
304
+ // =============================================================================
305
+
306
+ describe('CorrelationAnalyzerAgent Template Conformance', () => {
307
+ describe('output schema validation', () => {
308
+ it('returns output conforming to template schema', async () => {
309
+ const input: CorrelationAnalyzerInput = {
310
+ callsites: [
311
+ makeCallsite({ provider: 'openai', model: 'gpt-4o' }),
312
+ makeCallsite({ provider: 'anthropic', model: 'claude-3-opus' }),
313
+ ],
314
+ events: [makeEvent({ provider: 'openai', model: 'gpt-4o-mini' })],
315
+ runtimeSummary: makeRuntimeSummary(),
316
+ };
317
+
318
+ const { result } = await CorrelationAnalyzerAgent.execute(input);
319
+
320
+ // Validate against schema
321
+ const validation = CorrelationAnalyzerOutputSchema.safeParse(result);
322
+
323
+ if (!validation.success) {
324
+ console.error('Schema validation errors:', JSON.stringify(validation.error.issues, null, 2));
325
+ }
326
+
327
+ expect(validation.success).toBe(true);
328
+ });
329
+
330
+ it('drift signals have correct type enum from template', async () => {
331
+ const input: CorrelationAnalyzerInput = {
332
+ callsites: [makeCallsite({ provider: 'anthropic', model: 'claude-3-opus' })],
333
+ events: [makeEvent({ provider: 'openai', model: 'gpt-4o' })],
334
+ runtimeSummary: makeRuntimeSummary(),
335
+ };
336
+
337
+ const { result } = await CorrelationAnalyzerAgent.execute(input);
338
+
339
+ // Template defines: codeOnly|runtimeOnly|modelMismatch|patternMismatch|providerMismatch
340
+ // Our code normalizes to: codeOnly|runtimeOnly|mismatch|patternDrift
341
+ const validTypes = ['codeOnly', 'runtimeOnly', 'mismatch', 'patternDrift', 'modelMismatch', 'patternMismatch', 'providerMismatch'];
342
+
343
+ for (const signal of result.driftSignals) {
344
+ expect(validTypes).toContain(signal.type);
345
+ }
346
+ });
347
+
348
+ it('alignment_score is between 0.0 and 1.0 as per template', async () => {
349
+ const input: CorrelationAnalyzerInput = {
350
+ callsites: [makeCallsite()],
351
+ events: [makeEvent()],
352
+ runtimeSummary: makeRuntimeSummary(),
353
+ };
354
+
355
+ const { result } = await CorrelationAnalyzerAgent.execute(input);
356
+
357
+ expect(result.alignmentScore).toBeGreaterThanOrEqual(0.0);
358
+ expect(result.alignmentScore).toBeLessThanOrEqual(1.0);
359
+ });
360
+
361
+ it('correlation_summary has all fields from template', async () => {
362
+ const input: CorrelationAnalyzerInput = {
363
+ callsites: [makeCallsite(), makeCallsite()],
364
+ events: [makeEvent()],
365
+ runtimeSummary: makeRuntimeSummary(),
366
+ };
367
+
368
+ const { result } = await CorrelationAnalyzerAgent.execute(input);
369
+
370
+ // Required fields from template
371
+ expect(result.correlationSummary).toHaveProperty('totalCodeCallsites');
372
+ expect(result.correlationSummary).toHaveProperty('totalRuntimeModels');
373
+ expect(result.correlationSummary).toHaveProperty('matched');
374
+ expect(result.correlationSummary).toHaveProperty('codeOnly');
375
+ expect(result.correlationSummary).toHaveProperty('runtimeOnly');
376
+ expect(result.correlationSummary).toHaveProperty('mismatched');
377
+
378
+ // All should be numbers
379
+ expect(typeof result.correlationSummary.totalCodeCallsites).toBe('number');
380
+ expect(typeof result.correlationSummary.matched).toBe('number');
381
+ });
382
+
383
+ it('overall_assessment is non-empty string as per template', async () => {
384
+ const input: CorrelationAnalyzerInput = {
385
+ callsites: [makeCallsite()],
386
+ events: [makeEvent()],
387
+ runtimeSummary: makeRuntimeSummary(),
388
+ };
389
+
390
+ const { result } = await CorrelationAnalyzerAgent.execute(input);
391
+
392
+ expect(typeof result.overallAssessment).toBe('string');
393
+ expect(result.overallAssessment.length).toBeGreaterThan(0);
394
+ });
395
+
396
+ it('respects max 15 drift signals constraint from template', async () => {
397
+ const input: CorrelationAnalyzerInput = {
398
+ callsites: Array.from({ length: 20 }, (_, i) =>
399
+ makeCallsite({ id: `cs_${i}`, model: `model-${i}` })
400
+ ),
401
+ events: Array.from({ length: 20 }, (_, i) =>
402
+ makeEvent({ id: `evt_${i}`, model: `other-model-${i}` })
403
+ ),
404
+ runtimeSummary: makeRuntimeSummary(),
405
+ };
406
+
407
+ const { result } = await CorrelationAnalyzerAgent.execute(input);
408
+
409
+ // Template says "Maximum 15 drift signals, prioritized by severity"
410
+ expect(result.driftSignals.length).toBeLessThanOrEqual(15);
411
+ });
412
+ });
413
+
414
+ describe('semantic validation', () => {
415
+ it('detects model mismatch correctly', async () => {
416
+ // Code says gpt-4o, runtime shows gpt-4o-mini
417
+ const input: CorrelationAnalyzerInput = {
418
+ callsites: [makeCallsite({ provider: 'openai', model: 'gpt-4o' })],
419
+ events: [makeEvent({ provider: 'openai', model: 'gpt-4o-mini' })],
420
+ runtimeSummary: {
421
+ totalEvents: 1,
422
+ byProvider: { openai: { calls: 1, tokens_in: 500, tokens_out: 200, latency_p50: 400, latency_p95: 400, latency_p99: 400 } },
423
+ byModel: { 'gpt-4o-mini': { calls: 1, tokens_in: 500, tokens_out: 200, latency_p50: 400, latency_p95: 400, latency_p99: 400 } },
424
+ global: { p50: 400, p95: 400, p99: 400 },
425
+ },
426
+ };
427
+
428
+ const { result } = await CorrelationAnalyzerAgent.execute(input);
429
+
430
+ // Should detect drift (either as mismatch, codeOnly + runtimeOnly, or low alignment)
431
+ const hasDrift =
432
+ result.driftSignals.length > 0 ||
433
+ result.correlationSummary.mismatched > 0 ||
434
+ result.correlationSummary.codeOnly > 0 ||
435
+ result.correlationSummary.runtimeOnly > 0 ||
436
+ result.alignmentScore < 1.0;
437
+
438
+ expect(hasDrift).toBe(true);
439
+ });
440
+
441
+ it('perfect match has high alignment score', async () => {
442
+ // Same provider:model in code and runtime
443
+ const input: CorrelationAnalyzerInput = {
444
+ callsites: [makeCallsite({ provider: 'openai', model: 'gpt-4o' })],
445
+ events: [makeEvent({ provider: 'openai', model: 'gpt-4o' })],
446
+ runtimeSummary: {
447
+ totalEvents: 1,
448
+ byProvider: { openai: { calls: 1, tokens_in: 500, tokens_out: 200, latency_p50: 1200, latency_p95: 1200, latency_p99: 1200 } },
449
+ byModel: { 'gpt-4o': { calls: 1, tokens_in: 500, tokens_out: 200, latency_p50: 1200, latency_p95: 1200, latency_p99: 1200 } },
450
+ global: { p50: 1200, p95: 1200, p99: 1200 },
451
+ },
452
+ };
453
+
454
+ const { result } = await CorrelationAnalyzerAgent.execute(input);
455
+
456
+ // Perfect match should have high alignment
457
+ expect(result.alignmentScore).toBeGreaterThanOrEqual(0.5);
458
+ });
459
+ });
460
+ });
461
+
462
+ // =============================================================================
463
+ // CROSS-AGENT CONSISTENCY TESTS
464
+ // =============================================================================
465
+
466
+ describe('Cross-Agent Template Consistency', () => {
467
+ it('both agents use consistent severity levels', async () => {
468
+ const runtimeInput: RuntimeAnalyzerInput = {
469
+ events: [makeEvent()],
470
+ runtimeSummary: makeRuntimeSummary(),
471
+ };
472
+
473
+ const correlationInput: CorrelationAnalyzerInput = {
474
+ callsites: [makeCallsite()],
475
+ events: [makeEvent()],
476
+ runtimeSummary: makeRuntimeSummary(),
477
+ };
478
+
479
+ const [runtimeResult, correlationResult] = await Promise.all([
480
+ RuntimeAnalyzerAgent.execute(runtimeInput),
481
+ CorrelationAnalyzerAgent.execute(correlationInput),
482
+ ]);
483
+
484
+ const validSeverities = ['critical', 'warning', 'info'];
485
+
486
+ for (const insight of runtimeResult.result.insights) {
487
+ expect(validSeverities).toContain(insight.severity);
488
+ }
489
+
490
+ for (const insight of correlationResult.result.insights) {
491
+ expect(validSeverities).toContain(insight.severity);
492
+ }
493
+ });
494
+
495
+ it('both agents use consistent impact layer enum', async () => {
496
+ const runtimeInput: RuntimeAnalyzerInput = {
497
+ events: Array.from({ length: 10 }, () => makeEvent()),
498
+ runtimeSummary: makeRuntimeSummary(),
499
+ };
500
+
501
+ const correlationInput: CorrelationAnalyzerInput = {
502
+ callsites: [makeCallsite({ model: 'claude-3-opus' })],
503
+ events: [makeEvent({ model: 'gpt-4o' })],
504
+ runtimeSummary: makeRuntimeSummary(),
505
+ };
506
+
507
+ const [runtimeResult, correlationResult] = await Promise.all([
508
+ RuntimeAnalyzerAgent.execute(runtimeInput),
509
+ CorrelationAnalyzerAgent.execute(correlationInput),
510
+ ]);
511
+
512
+ const validLayers = ['application', 'model', 'runtime', 'infrastructure'];
513
+
514
+ for (const insight of runtimeResult.result.insights) {
515
+ if (insight.impact) {
516
+ expect(validLayers).toContain(insight.impact.layer);
517
+ }
518
+ }
519
+
520
+ for (const insight of correlationResult.result.insights) {
521
+ if (insight.impact) {
522
+ expect(validLayers).toContain(insight.impact.layer);
523
+ }
524
+ }
525
+ });
526
+ });