@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,827 @@
1
+ /**
2
+ * Static Analysis Orchestrator
3
+ * Runs unified multi-dimensional analysis for comprehensive static code analysis
4
+ *
5
+ * Uses Claude Agent SDK (per TDD v1.9.3) with optimized single-call approach:
6
+ * one agent query per file analyzing all 4 dimensions (cost, latency, throughput, reliability)
7
+ *
8
+ * Architecture: Claude Agent SDK = Engine, TypeScript = Glue (per TDD §1)
9
+ *
10
+ * Prompts are loaded from YAML config files (prompts/*.yaml) for consistency
11
+ * between CLI and API surfaces.
12
+ *
13
+ * SYNC NOTE: This file is SYNCED FROM peakinfer-site (private repo).
14
+ * Source: peakinfer-site/lib/agents/static-orchestrator.ts
15
+ * DO NOT modify directly - changes must be made in peakinfer-site first.
16
+ */
17
+ import { query } from '@anthropic-ai/claude-agent-sdk';
18
+ import { EventEmitter } from 'events';
19
+ // Increase max listeners to handle parallel file analysis without warnings
20
+ // Each query() call adds exit listeners; with many files we exceed the default of 10
21
+ // Set to 500 to support large codebases (300+ files)
22
+ const originalMaxListeners = EventEmitter.defaultMaxListeners;
23
+ EventEmitter.defaultMaxListeners = 500;
24
+ process.setMaxListeners(500);
25
+ import { getUnifiedAnalyzerPrompt, formatUserMessage } from './prompts/loader.js';
26
+ // =============================================================================
27
+ // LOAD PROMPT FROM CONFIG
28
+ // =============================================================================
29
+ // Always load fresh from YAML config (no caching for serverless)
30
+ function getPrompt() {
31
+ return getUnifiedAnalyzerPrompt();
32
+ }
33
+ // =============================================================================
34
+ // HELPERS
35
+ // =============================================================================
36
+ /**
37
+ * Generate stable inference point ID using file:line format.
38
+ * This ensures IDs are consistent across runs for the same code location.
39
+ * Falls back to random ID only if no location info is available.
40
+ */
41
+ function generatePointId(filePath, line) {
42
+ if (filePath && line) {
43
+ // Use stable file:line format (PRD requirement for consistent IDs)
44
+ return `${filePath}:${line}`;
45
+ }
46
+ // Fallback for rare cases where location is unknown
47
+ return 'pt_' + Math.random().toString(36).substring(2, 10);
48
+ }
49
+ /**
50
+ * Extract complete JSON object from text by matching brackets.
51
+ * This is more robust than regex which can match incomplete JSON.
52
+ * Returns null if the extracted JSON is too short or malformed.
53
+ *
54
+ * Handles common LLM issues:
55
+ * - Skips code snippets that aren't analysis results
56
+ * - Validates that response looks like analysis JSON (has "inference_points")
57
+ */
58
+ function extractJSON(text) {
59
+ // Look for JSON that starts with {"inference_points" - the expected response format
60
+ // This avoids picking up code snippets that happen to start with {
61
+ const jsonStartPattern = /\{\s*"inference_points"/;
62
+ const match = text.match(jsonStartPattern);
63
+ if (!match || match.index === undefined) {
64
+ // Fallback: try to find any JSON object, but validate later
65
+ const fallbackStart = text.indexOf('{"');
66
+ if (fallbackStart === -1)
67
+ return null;
68
+ const extracted = extractJSONFromPosition(text, fallbackStart);
69
+ if (extracted && isValidAnalysisJSON(extracted)) {
70
+ return extracted;
71
+ }
72
+ return null;
73
+ }
74
+ return extractJSONFromPosition(text, match.index);
75
+ }
76
+ /**
77
+ * Extract JSON starting from a given position using bracket matching.
78
+ */
79
+ function extractJSONFromPosition(text, start) {
80
+ let depth = 0;
81
+ let inString = false;
82
+ let escape = false;
83
+ for (let i = start; i < text.length; i++) {
84
+ const char = text[i];
85
+ if (escape) {
86
+ escape = false;
87
+ continue;
88
+ }
89
+ if (char === '\\' && inString) {
90
+ escape = true;
91
+ continue;
92
+ }
93
+ if (char === '"' && !escape) {
94
+ inString = !inString;
95
+ continue;
96
+ }
97
+ if (inString)
98
+ continue;
99
+ if (char === '{')
100
+ depth++;
101
+ if (char === '}') {
102
+ depth--;
103
+ if (depth === 0) {
104
+ const extracted = text.substring(start, i + 1);
105
+ // Sanity check: valid analysis JSON should be at least 50 chars
106
+ if (extracted.length < 50) {
107
+ return null;
108
+ }
109
+ return extracted;
110
+ }
111
+ }
112
+ }
113
+ return null;
114
+ }
115
+ /**
116
+ * Check if extracted text looks like valid analysis JSON.
117
+ * Rejects code snippets and placeholder responses.
118
+ */
119
+ function isValidAnalysisJSON(text) {
120
+ // Reject if it contains placeholder syntax like [...] or {...}
121
+ if (/\[\.\.\.\]|\{\.\.\.\}/.test(text)) {
122
+ return false;
123
+ }
124
+ // Reject if it looks like code (unquoted keys with single quotes or variable names)
125
+ // Valid JSON has "key": not key: or 'key':
126
+ if (/[{,]\s*[a-zA-Z_][a-zA-Z0-9_]*\s*:/.test(text)) {
127
+ // Check if this is actually unquoted keys (not inside a string)
128
+ // Simple heuristic: if we see word: followed by non-string value, it's likely code
129
+ if (/[{,]\s*[a-zA-Z_]\w*\s*:\s*[a-zA-Z_]/.test(text)) {
130
+ return false;
131
+ }
132
+ }
133
+ // Must contain "inference_points" for it to be a valid response
134
+ if (!text.includes('"inference_points"')) {
135
+ return false;
136
+ }
137
+ return true;
138
+ }
139
+ function detectLanguage(filePath) {
140
+ const ext = filePath.split('.').pop()?.toLowerCase() || '';
141
+ const languageMap = {
142
+ 'py': 'python',
143
+ 'js': 'javascript',
144
+ 'ts': 'typescript',
145
+ 'tsx': 'typescript',
146
+ 'jsx': 'javascript',
147
+ 'go': 'go',
148
+ 'rs': 'rust',
149
+ 'java': 'java',
150
+ 'rb': 'ruby',
151
+ 'php': 'php',
152
+ };
153
+ return languageMap[ext] || 'unknown';
154
+ }
155
+ // =============================================================================
156
+ // UNIFIED SINGLE-CALL ANALYSIS (Claude Agent SDK)
157
+ // =============================================================================
158
+ /**
159
+ * Extract text content from Claude Agent SDK messages
160
+ */
161
+ function extractTextFromMessages(messages) {
162
+ let text = '';
163
+ for (const msg of messages) {
164
+ if (msg.type === 'assistant' && msg.message?.content) {
165
+ for (const block of msg.message.content) {
166
+ if (block.type === 'text') {
167
+ text += block.text;
168
+ }
169
+ }
170
+ }
171
+ }
172
+ return text;
173
+ }
174
+ async function runUnifiedAnalysis(filePath, content, language, _anthropicKey // Kept for signature compatibility, SDK uses env var
175
+ ) {
176
+ // Load prompt from config file
177
+ const configPrompt = getPrompt();
178
+ // Format user message using template from config
179
+ const userMessage = configPrompt.userTemplate
180
+ ? formatUserMessage(configPrompt.userTemplate, {
181
+ language,
182
+ file_path: filePath,
183
+ content,
184
+ })
185
+ : `Analyze this ${language} file for LLM inference points and their performance characteristics:
186
+
187
+ File: ${filePath}
188
+
189
+ \`\`\`${language}
190
+ ${content}
191
+ \`\`\`
192
+
193
+ Find all LLM API calls (Anthropic Claude, Claude Agent SDK, self-hosted like TensorRT-LLM, vLLM, Triton, HTTP calls to inference endpoints, etc.) and analyze each one.`;
194
+ try {
195
+ // Use Claude Agent SDK query() function (per TDD v1.9.3)
196
+ const agentQuery = query({
197
+ prompt: userMessage,
198
+ options: {
199
+ systemPrompt: configPrompt.system,
200
+ model: 'claude-sonnet-4-20250514',
201
+ // Disable tools - we just want analysis output
202
+ tools: [],
203
+ // Use plan mode to avoid file operations
204
+ permissionMode: 'plan',
205
+ // Set working directory
206
+ cwd: process.cwd(),
207
+ },
208
+ });
209
+ // Collect all messages from the async generator
210
+ const messages = [];
211
+ for await (const message of agentQuery) {
212
+ messages.push(message);
213
+ }
214
+ // Extract text content from messages
215
+ const responseText = extractTextFromMessages(messages);
216
+ if (responseText) {
217
+ // Use robust JSON extraction instead of greedy regex
218
+ const jsonStr = extractJSON(responseText);
219
+ if (jsonStr) {
220
+ try {
221
+ const parsed = JSON.parse(jsonStr);
222
+ // Validate that we got a proper response structure
223
+ if (!parsed || typeof parsed !== 'object') {
224
+ // Silent: file probably doesn't have LLM calls
225
+ return null;
226
+ }
227
+ // Ensure inference_points array exists (even if empty)
228
+ if (!parsed.inference_points) {
229
+ parsed.inference_points = [];
230
+ }
231
+ // Ensure imports object exists
232
+ if (!parsed.imports) {
233
+ parsed.imports = { llm_providers: [], frameworks: [] };
234
+ }
235
+ // Ensure IDs are set using stable file:line format
236
+ for (const point of parsed.inference_points) {
237
+ if (!point.id) {
238
+ point.id = generatePointId(filePath, point.line);
239
+ }
240
+ }
241
+ return parsed;
242
+ }
243
+ catch (_parseError) {
244
+ // JSON extraction found something but it wasn't valid JSON
245
+ // This is expected for files without LLM calls - LLM may return code snippets
246
+ // Only log in verbose mode or if debugging is needed
247
+ return null;
248
+ }
249
+ }
250
+ // No valid JSON found - this is normal for files without LLM inference points
251
+ }
252
+ }
253
+ catch (error) {
254
+ // Only log actual SDK errors, not expected "no inference points" cases
255
+ const errorMsg = error instanceof Error ? error.message : String(error);
256
+ if (!errorMsg.includes('rate limit') && !errorMsg.includes('timeout')) {
257
+ // Silent for most errors - files without LLM calls are expected
258
+ }
259
+ else {
260
+ console.error('Claude Agent SDK error:', errorMsg);
261
+ }
262
+ }
263
+ return null;
264
+ }
265
+ // =============================================================================
266
+ // CONVERT UNIFIED TO LEGACY FORMAT
267
+ // =============================================================================
268
+ function convertUnifiedToLegacy(unified, filePath) {
269
+ const providers = unified.imports?.llm_providers || [];
270
+ const frameworks = unified.imports?.frameworks || [];
271
+ const imports = {
272
+ sdks: providers.map((p, i) => ({
273
+ name: p,
274
+ provider: p,
275
+ import_line: i + 1,
276
+ alias: null,
277
+ confidence: 0.9,
278
+ })),
279
+ frameworks: frameworks.map((f, i) => ({
280
+ name: f,
281
+ import_line: i + 1,
282
+ components: [],
283
+ confidence: 0.9,
284
+ })),
285
+ custom_wrappers: [],
286
+ infrastructure: [],
287
+ summary: {
288
+ has_llm_usage: providers.length > 0 || frameworks.length > 0,
289
+ primary_provider: providers[0] || null,
290
+ framework: frameworks[0] || null,
291
+ complexity: providers.length > 1 ? 'complex' : providers.length > 0 ? 'moderate' : 'simple',
292
+ },
293
+ };
294
+ const inferencePoints = unified.inference_points.map(p => ({
295
+ id: p.id,
296
+ line: p.line,
297
+ column: 0,
298
+ function_context: '',
299
+ class_context: null,
300
+ call_expression: '',
301
+ call_type: (p.call_type || 'direct'),
302
+ provider: { value: p.provider, source: 'hardcoded', confidence: 0.9 },
303
+ model: { value: p.model, source: 'hardcoded', confidence: 0.8 },
304
+ is_async: false,
305
+ in_loop: false,
306
+ loop_type: 'none',
307
+ estimated_calls: 'single',
308
+ needs_tracing: false,
309
+ confidence: 0.85,
310
+ }));
311
+ const callsites = {
312
+ inference_points: inferencePoints,
313
+ wrapper_definitions: [],
314
+ summary: {
315
+ total_inference_points: inferencePoints.length,
316
+ direct_calls: inferencePoints.filter(p => p.call_type === 'direct').length,
317
+ wrapped_calls: inferencePoints.filter(p => p.call_type === 'wrapper').length,
318
+ framework_calls: inferencePoints.filter(p => p.call_type === 'framework').length,
319
+ providers_detected: [...new Set(inferencePoints.map(p => p.provider.value))],
320
+ models_detected: [...new Set(inferencePoints.map(p => p.model.value).filter(Boolean))],
321
+ has_dynamic_routing: false,
322
+ },
323
+ };
324
+ const costProfiles = unified.inference_points.map(p => ({
325
+ inference_point_id: p.id,
326
+ line: p.line,
327
+ model_analysis: {
328
+ model: p.model || 'unknown',
329
+ tier: (p.cost_profile?.tier || 'unknown'),
330
+ pricing: { input_per_1m: 0, output_per_1m: 0 },
331
+ is_overqualified: false,
332
+ reason: null,
333
+ },
334
+ token_estimates: {
335
+ input: { min: 100, typical: 500, max: 2000, basis: 'estimate' },
336
+ output: { min: 50, typical: 200, max: 1000, basis: 'estimate' },
337
+ has_few_shot: false,
338
+ few_shot_tokens: 0,
339
+ has_rag_context: false,
340
+ rag_context_estimate: 0,
341
+ },
342
+ call_frequency: { pattern: 'single', multiplier: 1, loop_bound: 'bounded', estimated_calls_per_invocation: 1 },
343
+ cost_estimate: {
344
+ per_call_min: p.cost_profile?.estimated_cost_per_call || 0,
345
+ per_call_typical: p.cost_profile?.estimated_cost_per_call || 0,
346
+ per_call_max: p.cost_profile?.estimated_cost_per_call || 0,
347
+ currency: 'USD',
348
+ },
349
+ cost_risk: { level: 'low', factors: [], unbounded_growth: false, context_accumulation: false },
350
+ optimizations: (p.cost_profile?.optimizations || []).map(o => ({
351
+ type: o.type,
352
+ description: o.description,
353
+ current_cost: '',
354
+ optimized_cost: '',
355
+ savings_percent: o.savings_percent,
356
+ effort: 'medium',
357
+ sample_change: null,
358
+ })),
359
+ confidence: 0.85,
360
+ }));
361
+ const latencyProfiles = unified.inference_points.map(p => ({
362
+ inference_point_id: p.id,
363
+ line: p.line,
364
+ blocking_analysis: {
365
+ is_blocking: p.latency_profile?.is_blocking || false,
366
+ is_in_request_handler: false,
367
+ blocks_event_loop: p.latency_profile?.is_blocking || false,
368
+ handler_type: 'unknown',
369
+ user_facing: false,
370
+ },
371
+ streaming_analysis: {
372
+ streaming_enabled: p.latency_profile?.has_streaming || false,
373
+ should_enable_streaming: !p.latency_profile?.has_streaming,
374
+ reason: p.latency_profile?.has_streaming ? 'Streaming already enabled' : 'Enable streaming for better UX',
375
+ time_to_first_token_benefit: !p.latency_profile?.has_streaming ? '~200ms vs full wait' : null,
376
+ },
377
+ async_analysis: {
378
+ is_async: false,
379
+ uses_await: false,
380
+ could_be_async: true,
381
+ async_benefit: 'Enable concurrency',
382
+ },
383
+ parallel_analysis: {
384
+ has_parallel_potential: false,
385
+ independent_calls: 0,
386
+ current_pattern: 'sequential',
387
+ parallelizable_calls: [],
388
+ parallel_speedup_estimate: null,
389
+ },
390
+ chain_analysis: {
391
+ chain_depth: 1,
392
+ sequential_calls: 1,
393
+ total_latency_estimate: { min_ms: 500, typical_ms: 2000, max_ms: 5000 },
394
+ chain_pattern: 'single',
395
+ },
396
+ timeout_analysis: {
397
+ timeout_configured: p.reliability_profile?.has_timeout || false,
398
+ timeout_value_ms: null,
399
+ has_fallback_on_timeout: false,
400
+ timeout_risk: p.reliability_profile?.has_timeout ? 'low' : 'high',
401
+ },
402
+ latency_risk: { level: 'medium', factors: [], tail_latency_risk: true, unpredictable: false },
403
+ latency_estimate: {
404
+ min_ms: 500,
405
+ typical_ms: 2000,
406
+ p95_ms: p.latency_profile?.estimated_p95_ms || 5000,
407
+ max_ms: 15000,
408
+ basis: 'Model estimate',
409
+ },
410
+ optimizations: (p.latency_profile?.optimizations || []).map(o => ({
411
+ type: o.type,
412
+ description: o.description,
413
+ current_latency: '',
414
+ optimized_latency: '',
415
+ improvement_percent: o.improvement_percent,
416
+ effort: 'medium',
417
+ sample_change: null,
418
+ })),
419
+ confidence: 0.85,
420
+ }));
421
+ const throughputProfiles = unified.inference_points.map(p => ({
422
+ inference_point_id: p.id,
423
+ line: p.line,
424
+ concurrency_analysis: {
425
+ concurrency_limit: null,
426
+ limit_source: 'none',
427
+ limit_location: null,
428
+ is_global_limit: false,
429
+ recommended_limit: 10,
430
+ },
431
+ rate_limiting: {
432
+ has_rate_limiter: p.throughput_profile?.has_rate_limiting || false,
433
+ rate_limit_type: 'none',
434
+ requests_per_minute: null,
435
+ handles_429: false,
436
+ backoff_strategy: 'none',
437
+ },
438
+ batching_analysis: {
439
+ batching_enabled: p.throughput_profile?.has_batching || false,
440
+ batch_size: null,
441
+ could_batch: !p.throughput_profile?.has_batching,
442
+ batching_benefit: 'Reduce API calls',
443
+ batch_api_available: true,
444
+ },
445
+ queue_analysis: { uses_queue: false, queue_type: 'none', async_processing: false, worker_pattern: false },
446
+ scaling_analysis: {
447
+ horizontally_scalable: true,
448
+ bottlenecks: (p.throughput_profile?.bottlenecks || []).map(b => ({
449
+ type: 'shared_state',
450
+ location: filePath,
451
+ description: b.description,
452
+ severity: 'medium',
453
+ })),
454
+ stateless: true,
455
+ client_reuse: false,
456
+ },
457
+ capacity_estimate: { max_concurrent_calls: 10, estimated_rps: 10, limiting_factor: 'API quota' },
458
+ throughput_risk: {
459
+ level: 'medium',
460
+ factors: [],
461
+ will_hit_rate_limits: !p.throughput_profile?.has_rate_limiting,
462
+ scaling_blocked: false,
463
+ },
464
+ optimizations: (p.throughput_profile?.optimizations || []).map(o => ({
465
+ type: o.type,
466
+ description: o.description,
467
+ current_throughput: '',
468
+ optimized_throughput: '',
469
+ improvement: o.improvement,
470
+ effort: 'medium',
471
+ sample_change: null,
472
+ })),
473
+ confidence: 0.85,
474
+ }));
475
+ const reliabilityProfiles = unified.inference_points.map(p => ({
476
+ inference_point_id: p.id,
477
+ line: p.line,
478
+ error_handling: {
479
+ has_try_catch: p.reliability_profile?.has_error_handling || false,
480
+ caught_exceptions: [],
481
+ specific_llm_errors: false,
482
+ error_logged: false,
483
+ error_propagated: false,
484
+ silent_failure: false,
485
+ user_friendly_error: false,
486
+ },
487
+ retry_strategy: {
488
+ has_retry: p.reliability_profile?.has_retry || false,
489
+ retry_library: 'none',
490
+ max_retries: null,
491
+ backoff_type: 'none',
492
+ initial_delay_ms: null,
493
+ max_delay_ms: null,
494
+ retry_on: [],
495
+ jitter: false,
496
+ retry_budget_risk: 'none',
497
+ },
498
+ fallback_strategy: {
499
+ has_fallback: p.reliability_profile?.has_fallback || false,
500
+ fallback_type: 'none',
501
+ fallback_model: null,
502
+ fallback_provider: null,
503
+ graceful_degradation: false,
504
+ fallback_tested: 'unknown',
505
+ },
506
+ timeout_handling: {
507
+ timeout_configured: p.reliability_profile?.has_timeout || false,
508
+ timeout_ms: null,
509
+ timeout_source: 'none',
510
+ on_timeout: 'none',
511
+ },
512
+ circuit_breaker: {
513
+ has_circuit_breaker: false,
514
+ library: null,
515
+ failure_threshold: null,
516
+ recovery_time_ms: null,
517
+ },
518
+ validation: {
519
+ validates_response: false,
520
+ validates_json: false,
521
+ validates_schema: false,
522
+ handles_empty_response: false,
523
+ handles_truncated: false,
524
+ },
525
+ reliability_risk: {
526
+ level: p.reliability_profile?.has_error_handling ? 'moderate' : 'fragile',
527
+ factors: [],
528
+ single_point_of_failure: !p.reliability_profile?.has_fallback,
529
+ cascade_risk: false,
530
+ data_loss_risk: false,
531
+ },
532
+ anti_patterns: (p.reliability_profile?.anti_patterns || []).map(a => ({
533
+ pattern: a.type,
534
+ description: a.description,
535
+ location: filePath,
536
+ severity: 'medium',
537
+ })),
538
+ optimizations: (p.reliability_profile?.optimizations || []).map(o => ({
539
+ type: o.type,
540
+ description: o.description,
541
+ reliability_before: 'low',
542
+ reliability_after: 'high',
543
+ effort: 'medium',
544
+ priority: (o.priority || 'medium'),
545
+ sample_change: null,
546
+ })),
547
+ confidence: 0.85,
548
+ }));
549
+ // Convert insights
550
+ const insights = (unified.insights || []).map((i, idx) => ({
551
+ id: `insight_${Date.now()}_${idx}`,
552
+ severity: i.severity,
553
+ category: i.category,
554
+ headline: i.headline,
555
+ evidence: i.evidence,
556
+ location: filePath,
557
+ recommendation: i.recommendation,
558
+ source: 'llm',
559
+ }));
560
+ return { imports, callsites, costProfiles, latencyProfiles, throughputProfiles, reliabilityProfiles, insights };
561
+ }
562
+ export class StaticAnalysisOrchestrator {
563
+ anthropicKey;
564
+ constructor(anthropicKey) {
565
+ // BYOK mode: user provides their own API key
566
+ this.anthropicKey = anthropicKey || process.env.ANTHROPIC_API_KEY || '';
567
+ if (!this.anthropicKey) {
568
+ throw new Error('ANTHROPIC_API_KEY is required. Set it via environment variable or pass it to the constructor.');
569
+ }
570
+ }
571
+ async analyze(input, onProgress) {
572
+ const allImports = [];
573
+ const allCallsites = [];
574
+ const allCostAnalysis = [];
575
+ const allLatencyAnalysis = [];
576
+ const allThroughputAnalysis = [];
577
+ const allReliabilityAnalysis = [];
578
+ const allPerformanceProfiles = [];
579
+ const allInsights = [];
580
+ // Track progress for Claude Code-style TUI
581
+ const totalFiles = input.files.length;
582
+ let completedFiles = 0;
583
+ // Emit initial progress
584
+ onProgress?.({
585
+ phase: 'analyzing',
586
+ completed: 0,
587
+ total: totalFiles,
588
+ percent: 0,
589
+ });
590
+ // Concurrent analysis with limited parallelism to avoid overwhelming system
591
+ // Max 20 concurrent API calls - balances speed vs resource usage
592
+ const MAX_CONCURRENT = 20;
593
+ const fileAnalyses = [];
594
+ // Process files in batches
595
+ for (let i = 0; i < input.files.length; i += MAX_CONCURRENT) {
596
+ const batch = input.files.slice(i, i + MAX_CONCURRENT);
597
+ const batchResults = await Promise.all(batch.map(async (file) => {
598
+ const language = file.language || detectLanguage(file.path);
599
+ const unified = await runUnifiedAnalysis(file.path, file.content, language, this.anthropicKey);
600
+ // Emit progress after each file completes (Claude Code pattern)
601
+ completedFiles++;
602
+ const percent = Math.round((completedFiles / totalFiles) * 100);
603
+ onProgress?.({
604
+ phase: 'analyzing',
605
+ completed: completedFiles,
606
+ total: totalFiles,
607
+ currentFile: file.path.split('/').pop() || file.path,
608
+ percent,
609
+ });
610
+ if (unified && unified.inference_points?.length > 0) {
611
+ return { file, unified, language };
612
+ }
613
+ return null;
614
+ }));
615
+ fileAnalyses.push(...batchResults);
616
+ }
617
+ // Process results
618
+ for (const result of fileAnalyses) {
619
+ if (!result)
620
+ continue;
621
+ const { file, unified } = result;
622
+ const converted = convertUnifiedToLegacy(unified, file.path);
623
+ allImports.push(converted.imports);
624
+ allCallsites.push(converted.callsites);
625
+ allInsights.push(...converted.insights);
626
+ allCostAnalysis.push({
627
+ cost_profiles: converted.costProfiles,
628
+ summary: {
629
+ total_inference_points: converted.costProfiles.length,
630
+ estimated_cost_per_1k_calls: converted.costProfiles.reduce((sum, p) => sum + (p.cost_estimate?.per_call_typical || 0) * 1000, 0),
631
+ highest_cost_point: null,
632
+ optimization_potential_percent: 50,
633
+ },
634
+ });
635
+ allLatencyAnalysis.push({
636
+ latency_profiles: converted.latencyProfiles,
637
+ summary: {
638
+ total_inference_points: converted.latencyProfiles.length,
639
+ blocking_calls: converted.latencyProfiles.filter(p => p.blocking_analysis?.is_blocking).length,
640
+ streaming_enabled: converted.latencyProfiles.filter(p => p.streaming_analysis?.streaming_enabled).length,
641
+ parallelizable: 0,
642
+ estimated_p95_ms: Math.max(...converted.latencyProfiles.map(p => p.latency_estimate?.p95_ms || 0), 5000),
643
+ },
644
+ });
645
+ allThroughputAnalysis.push({
646
+ throughput_profiles: converted.throughputProfiles,
647
+ summary: {
648
+ total_inference_points: converted.throughputProfiles.length,
649
+ has_rate_limiting: converted.throughputProfiles.filter(p => p.rate_limiting?.has_rate_limiter).length,
650
+ has_batching: converted.throughputProfiles.filter(p => p.batching_analysis?.batching_enabled).length,
651
+ scaling_bottlenecks: converted.throughputProfiles.reduce((sum, p) => sum + (p.scaling_analysis?.bottlenecks?.length || 0), 0),
652
+ estimated_max_rps: null,
653
+ },
654
+ });
655
+ allReliabilityAnalysis.push({
656
+ reliability_profiles: converted.reliabilityProfiles,
657
+ summary: {
658
+ total_inference_points: converted.reliabilityProfiles.length,
659
+ has_error_handling: converted.reliabilityProfiles.filter(p => p.error_handling?.has_try_catch).length,
660
+ has_retry: converted.reliabilityProfiles.filter(p => p.retry_strategy?.has_retry).length,
661
+ has_fallback: converted.reliabilityProfiles.filter(p => p.fallback_strategy?.has_fallback).length,
662
+ anti_patterns_found: converted.reliabilityProfiles.reduce((sum, p) => sum + (p.anti_patterns?.length || 0), 0),
663
+ overall_reliability: 'moderate',
664
+ },
665
+ });
666
+ // Build performance profiles with issues from LLM
667
+ for (const point of converted.callsites.inference_points) {
668
+ // Find matching unified inference point to get issues
669
+ const unifiedPoint = unified.inference_points.find(up => up.id === point.id);
670
+ // Convert LLM-generated issues to Issue type
671
+ const issues = (unifiedPoint?.issues || []).map(issue => ({
672
+ type: issue.type,
673
+ severity: (issue.severity || 'warning'),
674
+ headline: issue.headline,
675
+ evidence: issue.evidence,
676
+ originalCode: issue.original_code || '',
677
+ suggestedFix: issue.suggested_fix || null,
678
+ aiAgentPrompt: issue.ai_agent_prompt || '',
679
+ }));
680
+ allPerformanceProfiles.push({
681
+ inference_point_id: point.id,
682
+ line: point.line,
683
+ file: file.path,
684
+ provider: point.provider.value,
685
+ model: point.model.value,
686
+ originalCode: unifiedPoint?.original_code || '',
687
+ issues,
688
+ cost: converted.costProfiles.find(p => p.inference_point_id === point.id) || null,
689
+ latency: converted.latencyProfiles.find(p => p.inference_point_id === point.id) || null,
690
+ throughput: converted.throughputProfiles.find(p => p.inference_point_id === point.id) || null,
691
+ reliability: converted.reliabilityProfiles.find(p => p.inference_point_id === point.id) || null,
692
+ });
693
+ }
694
+ }
695
+ // Aggregate optimizations
696
+ const allOptimizations = [];
697
+ for (const profile of allPerformanceProfiles) {
698
+ if (profile.cost?.optimizations) {
699
+ for (const opt of profile.cost.optimizations) {
700
+ allOptimizations.push({
701
+ dimension: 'cost',
702
+ inference_point_id: profile.inference_point_id,
703
+ file: profile.file,
704
+ line: profile.line,
705
+ type: opt.type,
706
+ description: opt.description,
707
+ impact: `${opt.savings_percent}% savings`,
708
+ effort: opt.effort,
709
+ priority: opt.savings_percent > 50 ? 'high' : opt.savings_percent > 20 ? 'medium' : 'low',
710
+ });
711
+ }
712
+ }
713
+ if (profile.latency?.optimizations) {
714
+ for (const opt of profile.latency.optimizations) {
715
+ allOptimizations.push({
716
+ dimension: 'latency',
717
+ inference_point_id: profile.inference_point_id,
718
+ file: profile.file,
719
+ line: profile.line,
720
+ type: opt.type,
721
+ description: opt.description,
722
+ impact: `${opt.improvement_percent}% improvement`,
723
+ effort: opt.effort,
724
+ priority: opt.improvement_percent > 50 ? 'high' : 'medium',
725
+ });
726
+ }
727
+ }
728
+ if (profile.throughput?.optimizations) {
729
+ for (const opt of profile.throughput.optimizations) {
730
+ allOptimizations.push({
731
+ dimension: 'throughput',
732
+ inference_point_id: profile.inference_point_id,
733
+ file: profile.file,
734
+ line: profile.line,
735
+ type: opt.type,
736
+ description: opt.description,
737
+ impact: opt.improvement,
738
+ effort: opt.effort,
739
+ priority: opt.type === 'add_rate_limiter' ? 'high' : 'medium',
740
+ });
741
+ }
742
+ }
743
+ if (profile.reliability?.optimizations) {
744
+ for (const opt of profile.reliability.optimizations) {
745
+ allOptimizations.push({
746
+ dimension: 'reliability',
747
+ inference_point_id: profile.inference_point_id,
748
+ file: profile.file,
749
+ line: profile.line,
750
+ type: opt.type,
751
+ description: opt.description,
752
+ impact: `${opt.reliability_before} → ${opt.reliability_after}`,
753
+ effort: opt.effort,
754
+ priority: opt.priority,
755
+ });
756
+ }
757
+ }
758
+ }
759
+ // Calculate summary
760
+ const allProviders = new Set();
761
+ const allModels = new Set();
762
+ for (const callsite of allCallsites) {
763
+ callsite.summary.providers_detected.forEach(p => allProviders.add(p));
764
+ callsite.summary.models_detected.forEach(m => allModels.add(m));
765
+ }
766
+ const totalCostPer1k = allCostAnalysis.reduce((sum, a) => sum + a.summary.estimated_cost_per_1k_calls, 0);
767
+ const maxP95 = Math.max(...allLatencyAnalysis.map(a => a.summary.estimated_p95_ms), 0);
768
+ // Determine overall reliability
769
+ const reliabilityLevels = allReliabilityAnalysis.map(a => a.summary.overall_reliability);
770
+ const reliabilityScore = reliabilityLevels.reduce((sum, level) => {
771
+ switch (level) {
772
+ case 'resilient': return sum + 4;
773
+ case 'robust': return sum + 3;
774
+ case 'moderate': return sum + 2;
775
+ case 'fragile': return sum + 1;
776
+ default: return sum;
777
+ }
778
+ }, 0);
779
+ const avgReliability = reliabilityLevels.length > 0 ? reliabilityScore / reliabilityLevels.length : 1;
780
+ let overallReliability = 'fragile';
781
+ if (avgReliability >= 3.5)
782
+ overallReliability = 'resilient';
783
+ else if (avgReliability >= 2.5)
784
+ overallReliability = 'robust';
785
+ else if (avgReliability >= 1.5)
786
+ overallReliability = 'moderate';
787
+ return {
788
+ imports: allImports,
789
+ callsites: allCallsites,
790
+ performance_profiles: allPerformanceProfiles,
791
+ cost_analysis: allCostAnalysis,
792
+ latency_analysis: allLatencyAnalysis,
793
+ throughput_analysis: allThroughputAnalysis,
794
+ reliability_analysis: allReliabilityAnalysis,
795
+ summary: {
796
+ total_files: input.files.length,
797
+ total_inference_points: allPerformanceProfiles.length,
798
+ providers: [...allProviders],
799
+ models: [...allModels],
800
+ estimated_cost_per_1k_calls: totalCostPer1k,
801
+ cost_risk_high: allCostAnalysis.reduce((sum, a) => sum + a.cost_profiles.filter(p => p.cost_risk.level === 'high' || p.cost_risk.level === 'critical').length, 0),
802
+ blocking_calls: allLatencyAnalysis.reduce((sum, a) => sum + a.summary.blocking_calls, 0),
803
+ streaming_enabled: allLatencyAnalysis.reduce((sum, a) => sum + a.summary.streaming_enabled, 0),
804
+ estimated_p95_ms: maxP95,
805
+ has_rate_limiting: allThroughputAnalysis.reduce((sum, a) => sum + a.summary.has_rate_limiting, 0),
806
+ scaling_bottlenecks: allThroughputAnalysis.reduce((sum, a) => sum + a.summary.scaling_bottlenecks, 0),
807
+ has_error_handling: allReliabilityAnalysis.reduce((sum, a) => sum + a.summary.has_error_handling, 0),
808
+ has_retry: allReliabilityAnalysis.reduce((sum, a) => sum + a.summary.has_retry, 0),
809
+ has_fallback: allReliabilityAnalysis.reduce((sum, a) => sum + a.summary.has_fallback, 0),
810
+ anti_patterns_found: allReliabilityAnalysis.reduce((sum, a) => sum + a.summary.anti_patterns_found, 0),
811
+ overall_reliability: overallReliability,
812
+ total_optimizations: allOptimizations.length,
813
+ critical_optimizations: allOptimizations.filter(o => o.priority === 'critical' || o.priority === 'high').length,
814
+ },
815
+ all_optimizations: allOptimizations,
816
+ insights: allInsights,
817
+ };
818
+ }
819
+ }
820
+ // =============================================================================
821
+ // CONVENIENCE FUNCTION
822
+ // =============================================================================
823
+ export async function runStaticAnalysis(input, anthropicKey) {
824
+ const orchestrator = new StaticAnalysisOrchestrator(anthropicKey);
825
+ return orchestrator.analyze(input);
826
+ }
827
+ //# sourceMappingURL=orchestrator.js.map