@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,125 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parseCommand } from '../../src/action/commands.js';
3
+
4
+ // =============================================================================
5
+ // COMMAND PARSING TESTS
6
+ // =============================================================================
7
+
8
+ describe('parseCommand', () => {
9
+ describe('/peakinfer command', () => {
10
+ it('parses /peakinfer as rerun', () => {
11
+ const cmd = parseCommand('/peakinfer');
12
+ expect(cmd).toEqual({ type: 'rerun' });
13
+ });
14
+
15
+ it('parses /peakinfer rerun as rerun', () => {
16
+ const cmd = parseCommand('/peakinfer rerun');
17
+ expect(cmd).toEqual({ type: 'rerun' });
18
+ });
19
+
20
+ it('is case insensitive', () => {
21
+ const cmd = parseCommand('/PEAKINFER');
22
+ expect(cmd).toEqual({ type: 'rerun' });
23
+ });
24
+
25
+ it('trims whitespace', () => {
26
+ const cmd = parseCommand(' /peakinfer ');
27
+ expect(cmd).toEqual({ type: 'rerun' });
28
+ });
29
+ });
30
+
31
+ describe('/fix command', () => {
32
+ it('parses /fix <id> correctly', () => {
33
+ const cmd = parseCommand('/fix 1');
34
+ expect(cmd).toEqual({ type: 'fix', issueId: 1 });
35
+ });
36
+
37
+ it('parses /fix with larger ids', () => {
38
+ const cmd = parseCommand('/fix 123');
39
+ expect(cmd).toEqual({ type: 'fix', issueId: 123 });
40
+ });
41
+
42
+ it('parses /peakinfer fix <id>', () => {
43
+ const cmd = parseCommand('/peakinfer fix 5');
44
+ expect(cmd).toEqual({ type: 'fix', issueId: 5 });
45
+ });
46
+
47
+ it('parses /fix all as fix-all', () => {
48
+ const cmd = parseCommand('/fix all');
49
+ expect(cmd).toEqual({ type: 'fix-all' });
50
+ });
51
+
52
+ it('parses /peakinfer fix all', () => {
53
+ const cmd = parseCommand('/peakinfer fix all');
54
+ expect(cmd).toEqual({ type: 'fix-all' });
55
+ });
56
+ });
57
+
58
+ describe('/dismiss command', () => {
59
+ it('parses /dismiss <id> correctly', () => {
60
+ const cmd = parseCommand('/dismiss 2');
61
+ expect(cmd).toEqual({ type: 'dismiss', issueId: 2 });
62
+ });
63
+
64
+ it('parses /peakinfer dismiss <id>', () => {
65
+ const cmd = parseCommand('/peakinfer dismiss 7');
66
+ expect(cmd).toEqual({ type: 'dismiss', issueId: 7 });
67
+ });
68
+ });
69
+
70
+ describe('non-commands', () => {
71
+ it('returns null for regular comments', () => {
72
+ expect(parseCommand('Great work on this PR!')).toBeNull();
73
+ });
74
+
75
+ it('returns null for partial commands', () => {
76
+ expect(parseCommand('/fix')).toBeNull();
77
+ expect(parseCommand('/dismiss')).toBeNull();
78
+ });
79
+
80
+ it('returns null for invalid ids', () => {
81
+ expect(parseCommand('/fix abc')).toBeNull();
82
+ expect(parseCommand('/dismiss xyz')).toBeNull();
83
+ });
84
+
85
+ it('returns null for empty string', () => {
86
+ expect(parseCommand('')).toBeNull();
87
+ });
88
+
89
+ it('returns null for comments mentioning peakinfer', () => {
90
+ expect(parseCommand('can you run peakinfer again?')).toBeNull();
91
+ });
92
+ });
93
+ });
94
+
95
+ // =============================================================================
96
+ // COMMAND SCENARIOS
97
+ // =============================================================================
98
+
99
+ describe('Command Scenarios', () => {
100
+ it('user wants to re-run analysis after fixing issues', () => {
101
+ // User pushes fixes, then comments to re-analyze
102
+ const cmd = parseCommand('/peakinfer');
103
+ expect(cmd?.type).toBe('rerun');
104
+ });
105
+
106
+ it('user wants to accept a specific fix', () => {
107
+ // User reviews the top issue and accepts it
108
+ const cmd = parseCommand('/fix 1');
109
+ expect(cmd?.type).toBe('fix');
110
+ expect(cmd?.issueId).toBe(1);
111
+ });
112
+
113
+ it('user wants to dismiss an issue they disagree with', () => {
114
+ // User thinks the issue is a false positive
115
+ const cmd = parseCommand('/dismiss 3');
116
+ expect(cmd?.type).toBe('dismiss');
117
+ expect(cmd?.issueId).toBe(3);
118
+ });
119
+
120
+ it('user wants to accept all available fixes', () => {
121
+ // User trusts all suggestions and wants to apply them
122
+ const cmd = parseCommand('/fix all');
123
+ expect(cmd?.type).toBe('fix-all');
124
+ });
125
+ });
@@ -0,0 +1,347 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generatePRComment, generateExhaustedComment } from '../../src/action/comments.js';
3
+ import type { Insight } from '../../src/types.js';
4
+
5
+ // =============================================================================
6
+ // HELPER: Create mock insight
7
+ // =============================================================================
8
+
9
+ function createInsight(overrides: Partial<Insight> = {}): Insight {
10
+ return {
11
+ templateId: overrides.templateId || 'test-template',
12
+ headline: overrides.headline || 'Test issue headline',
13
+ evidence: overrides.evidence || 'Test evidence',
14
+ severity: overrides.severity || 'warning',
15
+ category: overrides.category || 'cost',
16
+ location: overrides.location || 'src/test.ts:10',
17
+ recommendation: overrides.recommendation,
18
+ };
19
+ }
20
+
21
+ // =============================================================================
22
+ // VERDICT TESTS
23
+ // =============================================================================
24
+
25
+ describe('Verdict Logic', () => {
26
+ it('returns "Safe to Merge" when no issues', () => {
27
+ const comment = generatePRComment({
28
+ results: {
29
+ inferenceMap: {
30
+ callsites: [],
31
+ summary: { totalCallsites: 5, providers: ['openai'], models: ['gpt-4'] },
32
+ },
33
+ },
34
+ baseline: null,
35
+ status: 'pass',
36
+ regressions: [],
37
+ newIssues: [],
38
+ changedFiles: [],
39
+ });
40
+
41
+ expect(comment).toContain('✅ Safe to Merge');
42
+ expect(comment).toContain('No issues found');
43
+ expect(comment).toContain('5 inference points');
44
+ });
45
+
46
+ it('returns "Mostly Good" for 1-5 warnings', () => {
47
+ const comment = generatePRComment({
48
+ results: {
49
+ inferenceMap: {
50
+ callsites: [],
51
+ summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
52
+ },
53
+ },
54
+ baseline: null,
55
+ status: 'warning',
56
+ regressions: [],
57
+ newIssues: [
58
+ createInsight({ severity: 'warning', headline: 'Warning 1' }),
59
+ createInsight({ severity: 'warning', headline: 'Warning 2' }),
60
+ ],
61
+ changedFiles: [],
62
+ });
63
+
64
+ expect(comment).toContain('🟢 Mostly Good');
65
+ expect(comment).toContain('2 optional improvements');
66
+ });
67
+
68
+ it('returns "Review Recommended" for 1 critical issue', () => {
69
+ const comment = generatePRComment({
70
+ results: {
71
+ inferenceMap: {
72
+ callsites: [],
73
+ summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
74
+ },
75
+ },
76
+ baseline: null,
77
+ status: 'warning',
78
+ regressions: [],
79
+ newIssues: [
80
+ createInsight({ severity: 'critical', headline: 'Critical issue' }),
81
+ ],
82
+ changedFiles: [],
83
+ });
84
+
85
+ expect(comment).toContain('🟡 Review Recommended');
86
+ expect(comment).toContain('1 issue needs attention');
87
+ });
88
+
89
+ it('returns "Review Recommended" for >5 warnings', () => {
90
+ const warnings = Array(7).fill(null).map((_, i) =>
91
+ createInsight({ severity: 'warning', headline: `Warning ${i + 1}` })
92
+ );
93
+
94
+ const comment = generatePRComment({
95
+ results: {
96
+ inferenceMap: {
97
+ callsites: [],
98
+ summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
99
+ },
100
+ },
101
+ baseline: null,
102
+ status: 'warning',
103
+ regressions: [],
104
+ newIssues: warnings,
105
+ changedFiles: [],
106
+ });
107
+
108
+ expect(comment).toContain('🟡 Review Recommended');
109
+ expect(comment).toContain('7 improvements suggested');
110
+ });
111
+
112
+ it('returns "Changes Requested" for ≥2 critical issues', () => {
113
+ const comment = generatePRComment({
114
+ results: {
115
+ inferenceMap: {
116
+ callsites: [],
117
+ summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
118
+ },
119
+ },
120
+ baseline: null,
121
+ status: 'fail',
122
+ regressions: [],
123
+ newIssues: [
124
+ createInsight({ severity: 'critical', headline: 'Critical 1' }),
125
+ createInsight({ severity: 'critical', headline: 'Critical 2' }),
126
+ createInsight({ severity: 'critical', headline: 'Critical 3' }),
127
+ ],
128
+ changedFiles: [],
129
+ });
130
+
131
+ expect(comment).toContain('🔴 Changes Requested');
132
+ expect(comment).toContain('3 issues need attention before merge');
133
+ });
134
+ });
135
+
136
+ // =============================================================================
137
+ // TOP ISSUE TESTS
138
+ // =============================================================================
139
+
140
+ describe('Top Issue Highlight', () => {
141
+ it('shows the highest severity issue first', () => {
142
+ const comment = generatePRComment({
143
+ results: {
144
+ inferenceMap: {
145
+ callsites: [],
146
+ summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
147
+ },
148
+ },
149
+ baseline: null,
150
+ status: 'warning',
151
+ regressions: [],
152
+ newIssues: [
153
+ createInsight({ severity: 'info', headline: 'Info issue' }),
154
+ createInsight({ severity: 'critical', headline: 'Critical issue', location: 'src/api.ts:45' }),
155
+ createInsight({ severity: 'warning', headline: 'Warning issue' }),
156
+ ],
157
+ changedFiles: [],
158
+ });
159
+
160
+ expect(comment).toContain('**Top Issue**');
161
+ expect(comment).toContain('Critical issue');
162
+ expect(comment).toContain('`src/api.ts:45`');
163
+ });
164
+
165
+ it('shows evidence as "Why it matters"', () => {
166
+ const comment = generatePRComment({
167
+ results: {
168
+ inferenceMap: {
169
+ callsites: [],
170
+ summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
171
+ },
172
+ },
173
+ baseline: null,
174
+ status: 'warning',
175
+ regressions: [],
176
+ newIssues: [
177
+ createInsight({
178
+ severity: 'critical',
179
+ headline: 'Missing error handling',
180
+ evidence: 'Unhandled failures will crash the service',
181
+ }),
182
+ ],
183
+ changedFiles: [],
184
+ });
185
+
186
+ expect(comment).toContain('**Why it matters**');
187
+ expect(comment).toContain('Unhandled failures will crash the service');
188
+ });
189
+ });
190
+
191
+ // =============================================================================
192
+ // COLLAPSIBLE DETAILS TESTS
193
+ // =============================================================================
194
+
195
+ describe('Collapsible Details', () => {
196
+ it('does not show details section for single issue', () => {
197
+ const comment = generatePRComment({
198
+ results: {
199
+ inferenceMap: {
200
+ callsites: [],
201
+ summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
202
+ },
203
+ },
204
+ baseline: null,
205
+ status: 'warning',
206
+ regressions: [],
207
+ newIssues: [
208
+ createInsight({ severity: 'critical', headline: 'Single issue' }),
209
+ ],
210
+ changedFiles: [],
211
+ });
212
+
213
+ expect(comment).not.toContain('<details>');
214
+ });
215
+
216
+ it('shows collapsible details for multiple issues', () => {
217
+ const comment = generatePRComment({
218
+ results: {
219
+ inferenceMap: {
220
+ callsites: [],
221
+ summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
222
+ },
223
+ },
224
+ baseline: null,
225
+ status: 'warning',
226
+ regressions: [],
227
+ newIssues: [
228
+ createInsight({ severity: 'critical', headline: 'Critical 1' }),
229
+ createInsight({ severity: 'warning', headline: 'Warning 1' }),
230
+ createInsight({ severity: 'warning', headline: 'Warning 2' }),
231
+ ],
232
+ changedFiles: [],
233
+ });
234
+
235
+ expect(comment).toContain('<details>');
236
+ expect(comment).toContain('See all 3 issues');
237
+ expect(comment).toContain('</details>');
238
+ });
239
+
240
+ it('groups issues by severity in details', () => {
241
+ const comment = generatePRComment({
242
+ results: {
243
+ inferenceMap: {
244
+ callsites: [],
245
+ summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
246
+ },
247
+ },
248
+ baseline: null,
249
+ status: 'warning',
250
+ regressions: [],
251
+ newIssues: [
252
+ createInsight({ severity: 'critical', headline: 'Critical 1' }),
253
+ createInsight({ severity: 'critical', headline: 'Critical 2' }),
254
+ createInsight({ severity: 'warning', headline: 'Warning 1' }),
255
+ createInsight({ severity: 'info', headline: 'Info 1' }),
256
+ ],
257
+ changedFiles: [],
258
+ });
259
+
260
+ // Critical shown as top issue, so details shows remaining:
261
+ // 1 critical, 1 warning, 1 info
262
+ expect(comment).toContain('**Critical**');
263
+ expect(comment).toContain('**Warning**');
264
+ expect(comment).toContain('**Info**');
265
+ });
266
+ });
267
+
268
+ // =============================================================================
269
+ // COMMANDS FOOTER TESTS
270
+ // =============================================================================
271
+
272
+ describe('Commands Footer', () => {
273
+ it('shows commands when issues exist', () => {
274
+ const comment = generatePRComment({
275
+ results: {
276
+ inferenceMap: {
277
+ callsites: [],
278
+ summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
279
+ },
280
+ },
281
+ baseline: null,
282
+ status: 'warning',
283
+ regressions: [],
284
+ newIssues: [
285
+ createInsight({ severity: 'warning', headline: 'Test issue' }),
286
+ ],
287
+ changedFiles: [],
288
+ });
289
+
290
+ expect(comment).toContain('/fix 1');
291
+ expect(comment).toContain('/dismiss 1');
292
+ expect(comment).toContain('/fix all');
293
+ expect(comment).toContain('/peakinfer');
294
+ });
295
+
296
+ it('does not show commands when no issues', () => {
297
+ const comment = generatePRComment({
298
+ results: {
299
+ inferenceMap: {
300
+ callsites: [],
301
+ summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
302
+ },
303
+ },
304
+ baseline: null,
305
+ status: 'pass',
306
+ regressions: [],
307
+ newIssues: [],
308
+ changedFiles: [],
309
+ });
310
+
311
+ expect(comment).not.toContain('/fix');
312
+ expect(comment).not.toContain('/dismiss');
313
+ });
314
+
315
+ it('always includes PeakInfer attribution', () => {
316
+ const comment = generatePRComment({
317
+ results: {
318
+ inferenceMap: {
319
+ callsites: [],
320
+ summary: { totalCallsites: 3, providers: ['openai'], models: ['gpt-4'] },
321
+ },
322
+ },
323
+ baseline: null,
324
+ status: 'pass',
325
+ regressions: [],
326
+ newIssues: [],
327
+ changedFiles: [],
328
+ });
329
+
330
+ expect(comment).toContain('Generated by');
331
+ expect(comment).toContain('PeakInfer');
332
+ });
333
+ });
334
+
335
+ // =============================================================================
336
+ // EXHAUSTED CREDITS TESTS
337
+ // =============================================================================
338
+
339
+ describe('Exhausted Credits Comment', () => {
340
+ it('shows usage and alternatives', () => {
341
+ const comment = generateExhaustedComment(300, 300);
342
+
343
+ expect(comment).toContain('Free Tier Limit Reached');
344
+ expect(comment).toContain('300/300');
345
+ expect(comment).toContain('npm i -g @kalmantic/peakinfer');
346
+ });
347
+ });
@@ -0,0 +1,203 @@
1
+ /**
2
+ * CLI Integration Tests
3
+ * Per Test Cases v1.9.3 - Critical Path Tests
4
+ */
5
+ import { describe, test, expect, beforeAll, afterAll } from 'vitest';
6
+ import { spawn, ChildProcess } from 'child_process';
7
+ import { join } from 'path';
8
+ import { existsSync, mkdirSync, writeFileSync, rmSync } from 'fs';
9
+
10
+ const CLI_PATH = join(__dirname, '..', 'dist', 'cli.js');
11
+ const FIXTURES_PATH = join(__dirname, 'fixtures');
12
+ const TEMP_DIR = join(__dirname, '.temp-cli-test');
13
+
14
+ interface CLIResult {
15
+ exitCode: number;
16
+ stdout: string;
17
+ stderr: string;
18
+ duration: number;
19
+ }
20
+
21
+ async function runCLI(args: string[], options?: { env?: Record<string, string>; timeout?: number }): Promise<CLIResult> {
22
+ return new Promise((resolve, reject) => {
23
+ const startTime = Date.now();
24
+ const timeout = options?.timeout || 60000;
25
+
26
+ const child = spawn('node', [CLI_PATH, ...args], {
27
+ env: { ...process.env, ...options?.env },
28
+ cwd: process.cwd(),
29
+ });
30
+
31
+ let stdout = '';
32
+ let stderr = '';
33
+
34
+ child.stdout.on('data', (data) => {
35
+ stdout += data.toString();
36
+ });
37
+
38
+ child.stderr.on('data', (data) => {
39
+ stderr += data.toString();
40
+ });
41
+
42
+ const timer = setTimeout(() => {
43
+ child.kill();
44
+ reject(new Error(`CLI timed out after ${timeout}ms`));
45
+ }, timeout);
46
+
47
+ child.on('close', (code) => {
48
+ clearTimeout(timer);
49
+ resolve({
50
+ exitCode: code || 0,
51
+ stdout,
52
+ stderr,
53
+ duration: Date.now() - startTime,
54
+ });
55
+ });
56
+
57
+ child.on('error', (err) => {
58
+ clearTimeout(timer);
59
+ reject(err);
60
+ });
61
+ });
62
+ }
63
+
64
+ describe('CLI demo command', () => {
65
+ test('Runs without arguments', async () => {
66
+ const result = await runCLI(['demo']);
67
+ expect(result.exitCode).toBe(0);
68
+ expect(result.stdout).toContain('DRIFT DETECTED');
69
+ });
70
+
71
+ test('Completes in under 30 seconds', async () => {
72
+ const result = await runCLI(['demo']);
73
+ expect(result.duration).toBeLessThan(30000);
74
+ });
75
+
76
+ test('Works offline with cached demo data', async () => {
77
+ const result = await runCLI(['demo'], {
78
+ env: {
79
+ ANTHROPIC_API_KEY: '', // No API key
80
+ },
81
+ });
82
+
83
+ expect(result.exitCode).toBe(0);
84
+ expect(result.stdout).toContain('inference points');
85
+ expect(result.stderr).not.toContain('API');
86
+ });
87
+
88
+ test('Shows magic moment (drift detection)', async () => {
89
+ const result = await runCLI(['demo']);
90
+ expect(result.stdout).toContain('YOUR CODE');
91
+ expect(result.stdout).toContain('RUNTIME');
92
+ });
93
+
94
+ test('Shows issues with severity', async () => {
95
+ const result = await runCLI(['demo']);
96
+ expect(result.stdout).toMatch(/CRITICAL|HIGH|MEDIUM|LOW/);
97
+ });
98
+
99
+ test('Shows CTA for next step', async () => {
100
+ const result = await runCLI(['demo']);
101
+ expect(result.stdout).toContain('peakinfer analyze');
102
+ });
103
+ });
104
+
105
+ describe('CLI help', () => {
106
+ test('Shows help with --help', async () => {
107
+ const result = await runCLI(['--help']);
108
+ expect(result.exitCode).toBe(0);
109
+ expect(result.stdout).toContain('analyze');
110
+ expect(result.stdout).toContain('demo');
111
+ });
112
+
113
+ test('Shows version with --version', async () => {
114
+ const result = await runCLI(['--version']);
115
+ expect(result.exitCode).toBe(0);
116
+ expect(result.stdout).toMatch(/\d+\.\d+\.\d+/);
117
+ });
118
+ });
119
+
120
+ describe('CLI analyze command', () => {
121
+ beforeAll(() => {
122
+ // Create temp directory with test file
123
+ if (!existsSync(TEMP_DIR)) {
124
+ mkdirSync(TEMP_DIR, { recursive: true });
125
+ }
126
+
127
+ // Create a simple test file
128
+ const testFile = `
129
+ import Anthropic from '@anthropic-ai/sdk';
130
+
131
+ const client = new Anthropic();
132
+
133
+ export async function chat(prompt: string) {
134
+ const response = await client.messages.create({
135
+ model: 'claude-sonnet-4-20250514',
136
+ max_tokens: 1000,
137
+ messages: [{ role: 'user', content: prompt }],
138
+ });
139
+ return response;
140
+ }
141
+ `;
142
+ writeFileSync(join(TEMP_DIR, 'test.ts'), testFile);
143
+ });
144
+
145
+ afterAll(() => {
146
+ // Cleanup
147
+ if (existsSync(TEMP_DIR)) {
148
+ rmSync(TEMP_DIR, { recursive: true, force: true });
149
+ }
150
+ });
151
+
152
+ test('Fails gracefully without API key', async () => {
153
+ const result = await runCLI(['analyze', TEMP_DIR], {
154
+ env: {
155
+ ANTHROPIC_API_KEY: '',
156
+ },
157
+ });
158
+ // Should exit with error or show helpful message
159
+ expect(result.stdout + result.stderr).toMatch(/API|key|ANTHROPIC/i);
160
+ });
161
+
162
+ test('Shows error for non-existent path', async () => {
163
+ const result = await runCLI(['analyze', '/non/existent/path']);
164
+ expect(result.exitCode).not.toBe(0);
165
+ expect(result.stderr).toContain('not found');
166
+ });
167
+
168
+ test('Accepts --verbose flag', async () => {
169
+ const result = await runCLI(['analyze', TEMP_DIR, '--verbose'], {
170
+ env: {
171
+ ANTHROPIC_API_KEY: '',
172
+ },
173
+ });
174
+ // Should not crash with verbose flag
175
+ expect(result.exitCode).toBeDefined();
176
+ });
177
+
178
+ test('Accepts --fixes flag', async () => {
179
+ const result = await runCLI(['analyze', TEMP_DIR, '--fixes'], {
180
+ env: {
181
+ ANTHROPIC_API_KEY: '',
182
+ },
183
+ });
184
+ // Should not crash with fixes flag
185
+ expect(result.exitCode).toBeDefined();
186
+ });
187
+ });
188
+
189
+ describe('CLI template commands', () => {
190
+ test('Lists templates', async () => {
191
+ const result = await runCLI(['template', 'list']);
192
+ expect(result.exitCode).toBe(0);
193
+ expect(result.stdout).toMatch(/template|insight|optimization/i);
194
+ });
195
+ });
196
+
197
+ describe('CLI history commands', () => {
198
+ test('Shows history', async () => {
199
+ const result = await runCLI(['history']);
200
+ expect(result.exitCode).toBe(0);
201
+ // May be empty but should not crash
202
+ });
203
+ });