@dotsetlabs/bellwether 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (403) hide show
  1. package/CHANGELOG.md +291 -0
  2. package/LICENSE +21 -0
  3. package/README.md +739 -0
  4. package/dist/auth/credentials.d.ts +64 -0
  5. package/dist/auth/credentials.js +218 -0
  6. package/dist/auth/index.d.ts +6 -0
  7. package/dist/auth/index.js +6 -0
  8. package/dist/auth/keychain.d.ts +64 -0
  9. package/dist/auth/keychain.js +268 -0
  10. package/dist/baseline/ab-testing.d.ts +80 -0
  11. package/dist/baseline/ab-testing.js +236 -0
  12. package/dist/baseline/ai-compatibility-scorer.d.ts +95 -0
  13. package/dist/baseline/ai-compatibility-scorer.js +606 -0
  14. package/dist/baseline/calibration.d.ts +77 -0
  15. package/dist/baseline/calibration.js +136 -0
  16. package/dist/baseline/category-matching.d.ts +85 -0
  17. package/dist/baseline/category-matching.js +289 -0
  18. package/dist/baseline/change-impact-analyzer.d.ts +98 -0
  19. package/dist/baseline/change-impact-analyzer.js +592 -0
  20. package/dist/baseline/comparator.d.ts +64 -0
  21. package/dist/baseline/comparator.js +916 -0
  22. package/dist/baseline/confidence.d.ts +55 -0
  23. package/dist/baseline/confidence.js +122 -0
  24. package/dist/baseline/converter.d.ts +61 -0
  25. package/dist/baseline/converter.js +585 -0
  26. package/dist/baseline/dependency-analyzer.d.ts +89 -0
  27. package/dist/baseline/dependency-analyzer.js +567 -0
  28. package/dist/baseline/deprecation-tracker.d.ts +133 -0
  29. package/dist/baseline/deprecation-tracker.js +322 -0
  30. package/dist/baseline/diff.d.ts +55 -0
  31. package/dist/baseline/diff.js +1584 -0
  32. package/dist/baseline/documentation-scorer.d.ts +205 -0
  33. package/dist/baseline/documentation-scorer.js +466 -0
  34. package/dist/baseline/embeddings.d.ts +118 -0
  35. package/dist/baseline/embeddings.js +251 -0
  36. package/dist/baseline/error-analyzer.d.ts +198 -0
  37. package/dist/baseline/error-analyzer.js +721 -0
  38. package/dist/baseline/evaluation/evaluator.d.ts +42 -0
  39. package/dist/baseline/evaluation/evaluator.js +323 -0
  40. package/dist/baseline/evaluation/expanded-dataset.d.ts +45 -0
  41. package/dist/baseline/evaluation/expanded-dataset.js +1164 -0
  42. package/dist/baseline/evaluation/golden-dataset.d.ts +58 -0
  43. package/dist/baseline/evaluation/golden-dataset.js +717 -0
  44. package/dist/baseline/evaluation/index.d.ts +15 -0
  45. package/dist/baseline/evaluation/index.js +15 -0
  46. package/dist/baseline/evaluation/types.d.ts +186 -0
  47. package/dist/baseline/evaluation/types.js +8 -0
  48. package/dist/baseline/external-dependency-detector.d.ts +181 -0
  49. package/dist/baseline/external-dependency-detector.js +524 -0
  50. package/dist/baseline/golden-output.d.ts +162 -0
  51. package/dist/baseline/golden-output.js +636 -0
  52. package/dist/baseline/health-scorer.d.ts +174 -0
  53. package/dist/baseline/health-scorer.js +451 -0
  54. package/dist/baseline/incremental-checker.d.ts +97 -0
  55. package/dist/baseline/incremental-checker.js +174 -0
  56. package/dist/baseline/index.d.ts +31 -0
  57. package/dist/baseline/index.js +42 -0
  58. package/dist/baseline/migration-generator.d.ts +137 -0
  59. package/dist/baseline/migration-generator.js +554 -0
  60. package/dist/baseline/migrations.d.ts +60 -0
  61. package/dist/baseline/migrations.js +197 -0
  62. package/dist/baseline/performance-tracker.d.ts +214 -0
  63. package/dist/baseline/performance-tracker.js +577 -0
  64. package/dist/baseline/pr-comment-generator.d.ts +117 -0
  65. package/dist/baseline/pr-comment-generator.js +546 -0
  66. package/dist/baseline/response-fingerprint.d.ts +127 -0
  67. package/dist/baseline/response-fingerprint.js +728 -0
  68. package/dist/baseline/response-schema-tracker.d.ts +129 -0
  69. package/dist/baseline/response-schema-tracker.js +420 -0
  70. package/dist/baseline/risk-scorer.d.ts +54 -0
  71. package/dist/baseline/risk-scorer.js +434 -0
  72. package/dist/baseline/saver.d.ts +89 -0
  73. package/dist/baseline/saver.js +554 -0
  74. package/dist/baseline/scenario-generator.d.ts +151 -0
  75. package/dist/baseline/scenario-generator.js +905 -0
  76. package/dist/baseline/schema-compare.d.ts +86 -0
  77. package/dist/baseline/schema-compare.js +557 -0
  78. package/dist/baseline/schema-evolution.d.ts +189 -0
  79. package/dist/baseline/schema-evolution.js +467 -0
  80. package/dist/baseline/semantic.d.ts +203 -0
  81. package/dist/baseline/semantic.js +908 -0
  82. package/dist/baseline/synonyms.d.ts +60 -0
  83. package/dist/baseline/synonyms.js +386 -0
  84. package/dist/baseline/telemetry.d.ts +165 -0
  85. package/dist/baseline/telemetry.js +294 -0
  86. package/dist/baseline/test-pruner.d.ts +120 -0
  87. package/dist/baseline/test-pruner.js +387 -0
  88. package/dist/baseline/types.d.ts +449 -0
  89. package/dist/baseline/types.js +5 -0
  90. package/dist/baseline/version.d.ts +138 -0
  91. package/dist/baseline/version.js +206 -0
  92. package/dist/cache/index.d.ts +5 -0
  93. package/dist/cache/index.js +5 -0
  94. package/dist/cache/response-cache.d.ts +151 -0
  95. package/dist/cache/response-cache.js +287 -0
  96. package/dist/ci/index.d.ts +60 -0
  97. package/dist/ci/index.js +342 -0
  98. package/dist/cli/commands/auth.d.ts +12 -0
  99. package/dist/cli/commands/auth.js +352 -0
  100. package/dist/cli/commands/badge.d.ts +3 -0
  101. package/dist/cli/commands/badge.js +74 -0
  102. package/dist/cli/commands/baseline-accept.d.ts +15 -0
  103. package/dist/cli/commands/baseline-accept.js +178 -0
  104. package/dist/cli/commands/baseline-migrate.d.ts +12 -0
  105. package/dist/cli/commands/baseline-migrate.js +164 -0
  106. package/dist/cli/commands/baseline.d.ts +14 -0
  107. package/dist/cli/commands/baseline.js +449 -0
  108. package/dist/cli/commands/beta.d.ts +10 -0
  109. package/dist/cli/commands/beta.js +231 -0
  110. package/dist/cli/commands/check.d.ts +11 -0
  111. package/dist/cli/commands/check.js +820 -0
  112. package/dist/cli/commands/cloud/badge.d.ts +3 -0
  113. package/dist/cli/commands/cloud/badge.js +74 -0
  114. package/dist/cli/commands/cloud/diff.d.ts +6 -0
  115. package/dist/cli/commands/cloud/diff.js +79 -0
  116. package/dist/cli/commands/cloud/history.d.ts +6 -0
  117. package/dist/cli/commands/cloud/history.js +102 -0
  118. package/dist/cli/commands/cloud/link.d.ts +9 -0
  119. package/dist/cli/commands/cloud/link.js +119 -0
  120. package/dist/cli/commands/cloud/login.d.ts +7 -0
  121. package/dist/cli/commands/cloud/login.js +499 -0
  122. package/dist/cli/commands/cloud/projects.d.ts +6 -0
  123. package/dist/cli/commands/cloud/projects.js +44 -0
  124. package/dist/cli/commands/cloud/shared.d.ts +7 -0
  125. package/dist/cli/commands/cloud/shared.js +42 -0
  126. package/dist/cli/commands/cloud/teams.d.ts +8 -0
  127. package/dist/cli/commands/cloud/teams.js +169 -0
  128. package/dist/cli/commands/cloud/upload.d.ts +8 -0
  129. package/dist/cli/commands/cloud/upload.js +181 -0
  130. package/dist/cli/commands/contract.d.ts +11 -0
  131. package/dist/cli/commands/contract.js +280 -0
  132. package/dist/cli/commands/discover.d.ts +3 -0
  133. package/dist/cli/commands/discover.js +82 -0
  134. package/dist/cli/commands/eval.d.ts +9 -0
  135. package/dist/cli/commands/eval.js +187 -0
  136. package/dist/cli/commands/explore.d.ts +11 -0
  137. package/dist/cli/commands/explore.js +437 -0
  138. package/dist/cli/commands/feedback.d.ts +9 -0
  139. package/dist/cli/commands/feedback.js +174 -0
  140. package/dist/cli/commands/golden.d.ts +12 -0
  141. package/dist/cli/commands/golden.js +407 -0
  142. package/dist/cli/commands/history.d.ts +10 -0
  143. package/dist/cli/commands/history.js +202 -0
  144. package/dist/cli/commands/init.d.ts +9 -0
  145. package/dist/cli/commands/init.js +219 -0
  146. package/dist/cli/commands/interview.d.ts +3 -0
  147. package/dist/cli/commands/interview.js +903 -0
  148. package/dist/cli/commands/link.d.ts +10 -0
  149. package/dist/cli/commands/link.js +169 -0
  150. package/dist/cli/commands/login.d.ts +7 -0
  151. package/dist/cli/commands/login.js +499 -0
  152. package/dist/cli/commands/preset.d.ts +33 -0
  153. package/dist/cli/commands/preset.js +297 -0
  154. package/dist/cli/commands/profile.d.ts +33 -0
  155. package/dist/cli/commands/profile.js +286 -0
  156. package/dist/cli/commands/registry.d.ts +11 -0
  157. package/dist/cli/commands/registry.js +146 -0
  158. package/dist/cli/commands/shared.d.ts +79 -0
  159. package/dist/cli/commands/shared.js +196 -0
  160. package/dist/cli/commands/teams.d.ts +8 -0
  161. package/dist/cli/commands/teams.js +169 -0
  162. package/dist/cli/commands/test.d.ts +9 -0
  163. package/dist/cli/commands/test.js +500 -0
  164. package/dist/cli/commands/upload.d.ts +8 -0
  165. package/dist/cli/commands/upload.js +223 -0
  166. package/dist/cli/commands/validate-config.d.ts +6 -0
  167. package/dist/cli/commands/validate-config.js +35 -0
  168. package/dist/cli/commands/verify.d.ts +11 -0
  169. package/dist/cli/commands/verify.js +283 -0
  170. package/dist/cli/commands/watch.d.ts +12 -0
  171. package/dist/cli/commands/watch.js +253 -0
  172. package/dist/cli/index.d.ts +3 -0
  173. package/dist/cli/index.js +178 -0
  174. package/dist/cli/interactive.d.ts +47 -0
  175. package/dist/cli/interactive.js +216 -0
  176. package/dist/cli/output/terminal-reporter.d.ts +19 -0
  177. package/dist/cli/output/terminal-reporter.js +104 -0
  178. package/dist/cli/output.d.ts +226 -0
  179. package/dist/cli/output.js +438 -0
  180. package/dist/cli/utils/env.d.ts +5 -0
  181. package/dist/cli/utils/env.js +14 -0
  182. package/dist/cli/utils/progress.d.ts +59 -0
  183. package/dist/cli/utils/progress.js +206 -0
  184. package/dist/cli/utils/server-context.d.ts +10 -0
  185. package/dist/cli/utils/server-context.js +36 -0
  186. package/dist/cloud/auth.d.ts +144 -0
  187. package/dist/cloud/auth.js +374 -0
  188. package/dist/cloud/client.d.ts +24 -0
  189. package/dist/cloud/client.js +65 -0
  190. package/dist/cloud/http-client.d.ts +38 -0
  191. package/dist/cloud/http-client.js +215 -0
  192. package/dist/cloud/index.d.ts +23 -0
  193. package/dist/cloud/index.js +25 -0
  194. package/dist/cloud/mock-client.d.ts +107 -0
  195. package/dist/cloud/mock-client.js +545 -0
  196. package/dist/cloud/types.d.ts +515 -0
  197. package/dist/cloud/types.js +15 -0
  198. package/dist/config/defaults.d.ts +160 -0
  199. package/dist/config/defaults.js +169 -0
  200. package/dist/config/loader.d.ts +24 -0
  201. package/dist/config/loader.js +122 -0
  202. package/dist/config/template.d.ts +42 -0
  203. package/dist/config/template.js +647 -0
  204. package/dist/config/validator.d.ts +2112 -0
  205. package/dist/config/validator.js +658 -0
  206. package/dist/constants/cloud.d.ts +107 -0
  207. package/dist/constants/cloud.js +110 -0
  208. package/dist/constants/core.d.ts +521 -0
  209. package/dist/constants/core.js +556 -0
  210. package/dist/constants/testing.d.ts +1283 -0
  211. package/dist/constants/testing.js +1568 -0
  212. package/dist/constants.d.ts +10 -0
  213. package/dist/constants.js +10 -0
  214. package/dist/contract/index.d.ts +6 -0
  215. package/dist/contract/index.js +5 -0
  216. package/dist/contract/validator.d.ts +177 -0
  217. package/dist/contract/validator.js +574 -0
  218. package/dist/cost/index.d.ts +6 -0
  219. package/dist/cost/index.js +5 -0
  220. package/dist/cost/tracker.d.ts +134 -0
  221. package/dist/cost/tracker.js +313 -0
  222. package/dist/discovery/discovery.d.ts +16 -0
  223. package/dist/discovery/discovery.js +173 -0
  224. package/dist/discovery/types.d.ts +51 -0
  225. package/dist/discovery/types.js +2 -0
  226. package/dist/docs/agents.d.ts +3 -0
  227. package/dist/docs/agents.js +995 -0
  228. package/dist/docs/contract.d.ts +51 -0
  229. package/dist/docs/contract.js +1681 -0
  230. package/dist/docs/generator.d.ts +4 -0
  231. package/dist/docs/generator.js +4 -0
  232. package/dist/docs/html-reporter.d.ts +9 -0
  233. package/dist/docs/html-reporter.js +757 -0
  234. package/dist/docs/index.d.ts +10 -0
  235. package/dist/docs/index.js +11 -0
  236. package/dist/docs/junit-reporter.d.ts +18 -0
  237. package/dist/docs/junit-reporter.js +210 -0
  238. package/dist/docs/report.d.ts +14 -0
  239. package/dist/docs/report.js +44 -0
  240. package/dist/docs/sarif-reporter.d.ts +19 -0
  241. package/dist/docs/sarif-reporter.js +335 -0
  242. package/dist/docs/shared.d.ts +35 -0
  243. package/dist/docs/shared.js +162 -0
  244. package/dist/docs/templates.d.ts +12 -0
  245. package/dist/docs/templates.js +76 -0
  246. package/dist/errors/index.d.ts +6 -0
  247. package/dist/errors/index.js +6 -0
  248. package/dist/errors/retry.d.ts +92 -0
  249. package/dist/errors/retry.js +323 -0
  250. package/dist/errors/types.d.ts +321 -0
  251. package/dist/errors/types.js +584 -0
  252. package/dist/index.d.ts +32 -0
  253. package/dist/index.js +32 -0
  254. package/dist/interview/dependency-resolver.d.ts +11 -0
  255. package/dist/interview/dependency-resolver.js +32 -0
  256. package/dist/interview/interviewer.d.ts +232 -0
  257. package/dist/interview/interviewer.js +1939 -0
  258. package/dist/interview/mock-response-generator.d.ts +7 -0
  259. package/dist/interview/mock-response-generator.js +102 -0
  260. package/dist/interview/orchestrator.d.ts +237 -0
  261. package/dist/interview/orchestrator.js +1296 -0
  262. package/dist/interview/rate-limiter.d.ts +15 -0
  263. package/dist/interview/rate-limiter.js +55 -0
  264. package/dist/interview/response-validator.d.ts +10 -0
  265. package/dist/interview/response-validator.js +132 -0
  266. package/dist/interview/schema-inferrer.d.ts +8 -0
  267. package/dist/interview/schema-inferrer.js +71 -0
  268. package/dist/interview/schema-test-generator.d.ts +71 -0
  269. package/dist/interview/schema-test-generator.js +834 -0
  270. package/dist/interview/smart-value-generator.d.ts +155 -0
  271. package/dist/interview/smart-value-generator.js +554 -0
  272. package/dist/interview/stateful-test-runner.d.ts +19 -0
  273. package/dist/interview/stateful-test-runner.js +106 -0
  274. package/dist/interview/types.d.ts +561 -0
  275. package/dist/interview/types.js +2 -0
  276. package/dist/llm/anthropic.d.ts +41 -0
  277. package/dist/llm/anthropic.js +355 -0
  278. package/dist/llm/client.d.ts +123 -0
  279. package/dist/llm/client.js +42 -0
  280. package/dist/llm/factory.d.ts +38 -0
  281. package/dist/llm/factory.js +145 -0
  282. package/dist/llm/fallback.d.ts +140 -0
  283. package/dist/llm/fallback.js +379 -0
  284. package/dist/llm/index.d.ts +18 -0
  285. package/dist/llm/index.js +15 -0
  286. package/dist/llm/ollama.d.ts +37 -0
  287. package/dist/llm/ollama.js +330 -0
  288. package/dist/llm/openai.d.ts +25 -0
  289. package/dist/llm/openai.js +320 -0
  290. package/dist/llm/token-budget.d.ts +161 -0
  291. package/dist/llm/token-budget.js +395 -0
  292. package/dist/logging/logger.d.ts +70 -0
  293. package/dist/logging/logger.js +130 -0
  294. package/dist/metrics/collector.d.ts +106 -0
  295. package/dist/metrics/collector.js +547 -0
  296. package/dist/metrics/index.d.ts +7 -0
  297. package/dist/metrics/index.js +7 -0
  298. package/dist/metrics/prometheus.d.ts +20 -0
  299. package/dist/metrics/prometheus.js +241 -0
  300. package/dist/metrics/types.d.ts +209 -0
  301. package/dist/metrics/types.js +5 -0
  302. package/dist/persona/builtins.d.ts +54 -0
  303. package/dist/persona/builtins.js +219 -0
  304. package/dist/persona/index.d.ts +8 -0
  305. package/dist/persona/index.js +8 -0
  306. package/dist/persona/loader.d.ts +30 -0
  307. package/dist/persona/loader.js +190 -0
  308. package/dist/persona/types.d.ts +144 -0
  309. package/dist/persona/types.js +5 -0
  310. package/dist/persona/validation.d.ts +94 -0
  311. package/dist/persona/validation.js +332 -0
  312. package/dist/prompts/index.d.ts +5 -0
  313. package/dist/prompts/index.js +5 -0
  314. package/dist/prompts/templates.d.ts +180 -0
  315. package/dist/prompts/templates.js +431 -0
  316. package/dist/registry/client.d.ts +49 -0
  317. package/dist/registry/client.js +191 -0
  318. package/dist/registry/index.d.ts +7 -0
  319. package/dist/registry/index.js +6 -0
  320. package/dist/registry/types.d.ts +140 -0
  321. package/dist/registry/types.js +6 -0
  322. package/dist/scenarios/evaluator.d.ts +43 -0
  323. package/dist/scenarios/evaluator.js +206 -0
  324. package/dist/scenarios/index.d.ts +10 -0
  325. package/dist/scenarios/index.js +9 -0
  326. package/dist/scenarios/loader.d.ts +20 -0
  327. package/dist/scenarios/loader.js +285 -0
  328. package/dist/scenarios/types.d.ts +153 -0
  329. package/dist/scenarios/types.js +8 -0
  330. package/dist/security/index.d.ts +17 -0
  331. package/dist/security/index.js +18 -0
  332. package/dist/security/payloads.d.ts +61 -0
  333. package/dist/security/payloads.js +268 -0
  334. package/dist/security/security-tester.d.ts +42 -0
  335. package/dist/security/security-tester.js +582 -0
  336. package/dist/security/types.d.ts +166 -0
  337. package/dist/security/types.js +8 -0
  338. package/dist/transport/base-transport.d.ts +59 -0
  339. package/dist/transport/base-transport.js +38 -0
  340. package/dist/transport/http-transport.d.ts +67 -0
  341. package/dist/transport/http-transport.js +238 -0
  342. package/dist/transport/mcp-client.d.ts +141 -0
  343. package/dist/transport/mcp-client.js +496 -0
  344. package/dist/transport/sse-transport.d.ts +88 -0
  345. package/dist/transport/sse-transport.js +316 -0
  346. package/dist/transport/stdio-transport.d.ts +43 -0
  347. package/dist/transport/stdio-transport.js +238 -0
  348. package/dist/transport/types.d.ts +125 -0
  349. package/dist/transport/types.js +16 -0
  350. package/dist/utils/concurrency.d.ts +123 -0
  351. package/dist/utils/concurrency.js +213 -0
  352. package/dist/utils/formatters.d.ts +16 -0
  353. package/dist/utils/formatters.js +37 -0
  354. package/dist/utils/index.d.ts +8 -0
  355. package/dist/utils/index.js +8 -0
  356. package/dist/utils/jsonpath.d.ts +87 -0
  357. package/dist/utils/jsonpath.js +326 -0
  358. package/dist/utils/markdown.d.ts +113 -0
  359. package/dist/utils/markdown.js +265 -0
  360. package/dist/utils/network.d.ts +14 -0
  361. package/dist/utils/network.js +17 -0
  362. package/dist/utils/sanitize.d.ts +92 -0
  363. package/dist/utils/sanitize.js +191 -0
  364. package/dist/utils/semantic.d.ts +194 -0
  365. package/dist/utils/semantic.js +1051 -0
  366. package/dist/utils/smart-truncate.d.ts +94 -0
  367. package/dist/utils/smart-truncate.js +361 -0
  368. package/dist/utils/timeout.d.ts +153 -0
  369. package/dist/utils/timeout.js +205 -0
  370. package/dist/utils/yaml-parser.d.ts +58 -0
  371. package/dist/utils/yaml-parser.js +86 -0
  372. package/dist/validation/index.d.ts +32 -0
  373. package/dist/validation/index.js +32 -0
  374. package/dist/validation/semantic-test-generator.d.ts +50 -0
  375. package/dist/validation/semantic-test-generator.js +176 -0
  376. package/dist/validation/semantic-types.d.ts +66 -0
  377. package/dist/validation/semantic-types.js +94 -0
  378. package/dist/validation/semantic-validator.d.ts +38 -0
  379. package/dist/validation/semantic-validator.js +340 -0
  380. package/dist/verification/index.d.ts +6 -0
  381. package/dist/verification/index.js +5 -0
  382. package/dist/verification/types.d.ts +133 -0
  383. package/dist/verification/types.js +5 -0
  384. package/dist/verification/verifier.d.ts +30 -0
  385. package/dist/verification/verifier.js +309 -0
  386. package/dist/version.d.ts +19 -0
  387. package/dist/version.js +48 -0
  388. package/dist/workflow/auto-generator.d.ts +27 -0
  389. package/dist/workflow/auto-generator.js +513 -0
  390. package/dist/workflow/discovery.d.ts +40 -0
  391. package/dist/workflow/discovery.js +195 -0
  392. package/dist/workflow/executor.d.ts +82 -0
  393. package/dist/workflow/executor.js +611 -0
  394. package/dist/workflow/index.d.ts +10 -0
  395. package/dist/workflow/index.js +10 -0
  396. package/dist/workflow/loader.d.ts +24 -0
  397. package/dist/workflow/loader.js +194 -0
  398. package/dist/workflow/state-tracker.d.ts +98 -0
  399. package/dist/workflow/state-tracker.js +424 -0
  400. package/dist/workflow/types.d.ts +337 -0
  401. package/dist/workflow/types.js +5 -0
  402. package/package.json +94 -0
  403. package/schemas/bellwether-check.schema.json +651 -0
@@ -0,0 +1,728 @@
1
+ /**
2
+ * Response fingerprinting for structural drift detection.
3
+ *
4
+ * Analyzes MCP tool responses to create deterministic fingerprints
5
+ * that capture response structure, shape, and characteristics without
6
+ * requiring LLM analysis.
7
+ */
8
+ import { createHash } from 'crypto';
9
+ import { analyzeErrorPatterns as analyzeErrorPatternsEnhanced } from './error-analyzer.js';
10
+ /**
11
+ * Detect if content appears to be binary data.
12
+ * Checks for Base64 data URIs, control characters, and other binary patterns.
13
+ */
14
+ function isBinaryContent(content) {
15
+ if (typeof content !== 'string')
16
+ return false;
17
+ // Sample only the first portion to avoid performance issues with large strings
18
+ const sample = content.slice(0, 1000);
19
+ // Check for Base64 data URI pattern
20
+ if (/^data:[^;]+;base64,/.test(sample)) {
21
+ return true;
22
+ }
23
+ // Check for control characters (excluding common whitespace)
24
+ // Control chars: 0x00-0x08, 0x0B, 0x0C, 0x0E-0x1F
25
+ // eslint-disable-next-line no-control-regex
26
+ if (/[\x00-\x08\x0B\x0C\x0E-\x1F]/.test(sample)) {
27
+ return true;
28
+ }
29
+ // Check for high concentration of non-printable characters
30
+ const nonPrintable = sample.match(/[^\x20-\x7E\t\n\r]/g);
31
+ if (nonPrintable && nonPrintable.length > sample.length * 0.3) {
32
+ return true;
33
+ }
34
+ return false;
35
+ }
36
+ /**
37
+ * Categorize binary content size for fingerprinting.
38
+ */
39
+ function categorizeBinarySize(content) {
40
+ const size = Buffer.byteLength(content, 'utf-8');
41
+ if (size < 1024)
42
+ return 'tiny';
43
+ if (size < 10 * 1024)
44
+ return 'small';
45
+ if (size < 100 * 1024)
46
+ return 'medium';
47
+ return 'large';
48
+ }
49
+ /**
50
+ * Analyze multiple tool responses to create a comprehensive fingerprint.
51
+ */
52
+ export function analyzeResponses(responses) {
53
+ const successfulResponses = responses.filter((r) => r.response && !r.response.isError && !r.error);
54
+ const errorResponses = responses.filter((r) => r.error || r.response?.isError);
55
+ // Analyze successful responses
56
+ const structures = [];
57
+ const inferredSchemas = [];
58
+ for (const { response } of successfulResponses) {
59
+ if (!response)
60
+ continue;
61
+ const content = extractResponseContent(response);
62
+ if (content !== undefined) {
63
+ structures.push(computeStructureHash(content));
64
+ inferredSchemas.push(inferSchemaFromValue(content));
65
+ }
66
+ }
67
+ // Analyze error patterns
68
+ const errorPatterns = analyzeErrorPatterns(errorResponses);
69
+ // Generate enhanced error analyses
70
+ const enhancedErrorAnalyses = errorPatterns.length > 0 ? analyzeErrorPatternsEnhanced(errorPatterns) : undefined;
71
+ // Build fingerprint
72
+ const fingerprint = buildFingerprint(successfulResponses, structures);
73
+ // Merge inferred schemas
74
+ const inferredSchema = inferredSchemas.length > 0 ? mergeSchemas(inferredSchemas) : undefined;
75
+ // Check consistency
76
+ const uniqueStructures = new Set(structures);
77
+ const isConsistent = uniqueStructures.size <= 1;
78
+ return {
79
+ fingerprint,
80
+ inferredSchema,
81
+ errorPatterns,
82
+ enhancedErrorAnalyses,
83
+ isConsistent,
84
+ };
85
+ }
86
+ /**
87
+ * Extract the meaningful content from an MCP tool response.
88
+ */
89
+ function extractResponseContent(response) {
90
+ if (!response.content || response.content.length === 0) {
91
+ return undefined;
92
+ }
93
+ // Handle single content item
94
+ if (response.content.length === 1) {
95
+ const item = response.content[0];
96
+ if (item.type === 'text' && 'text' in item && typeof item.text === 'string') {
97
+ // Try to parse as JSON
98
+ try {
99
+ return JSON.parse(item.text);
100
+ }
101
+ catch {
102
+ return item.text;
103
+ }
104
+ }
105
+ return item;
106
+ }
107
+ // Multiple content items - return as array
108
+ return response.content.map((item) => {
109
+ if (item.type === 'text' && 'text' in item && typeof item.text === 'string') {
110
+ try {
111
+ return JSON.parse(item.text);
112
+ }
113
+ catch {
114
+ return item.text;
115
+ }
116
+ }
117
+ return item;
118
+ });
119
+ }
120
+ /**
121
+ * Compute a structure hash that captures shape but not values.
122
+ */
123
+ function computeStructureHash(value) {
124
+ const structure = extractStructure(value);
125
+ const serialized = JSON.stringify(structure);
126
+ return createHash('sha256').update(serialized).digest('hex').slice(0, 16);
127
+ }
128
+ /**
129
+ * Extract the structural representation of a value.
130
+ * Captures types, keys, and nesting but not actual values.
131
+ */
132
+ function extractStructure(value, depth = 0) {
133
+ // Prevent infinite recursion
134
+ if (depth > 10) {
135
+ return { type: 'deep' };
136
+ }
137
+ if (value === null) {
138
+ return { type: 'null' };
139
+ }
140
+ if (value === undefined) {
141
+ return { type: 'undefined' };
142
+ }
143
+ const valueType = typeof value;
144
+ if (valueType === 'string') {
145
+ // Classify string patterns
146
+ const str = value;
147
+ if (str.length === 0)
148
+ return { type: 'string', subtype: 'empty' };
149
+ // Check for binary content before other patterns
150
+ if (isBinaryContent(str)) {
151
+ return {
152
+ type: 'binary',
153
+ size: categorizeBinarySize(str),
154
+ byteLength: Buffer.byteLength(str, 'utf-8'),
155
+ };
156
+ }
157
+ if (/^\d{4}-\d{2}-\d{2}/.test(str))
158
+ return { type: 'string', subtype: 'date' };
159
+ if (/^https?:\/\//.test(str))
160
+ return { type: 'string', subtype: 'url' };
161
+ if (/^[\w.-]+@[\w.-]+\.\w+$/.test(str))
162
+ return { type: 'string', subtype: 'email' };
163
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str)) {
164
+ return { type: 'string', subtype: 'uuid' };
165
+ }
166
+ return { type: 'string' };
167
+ }
168
+ if (valueType === 'number') {
169
+ const num = value;
170
+ if (Number.isInteger(num))
171
+ return { type: 'integer' };
172
+ return { type: 'number' };
173
+ }
174
+ if (valueType === 'boolean') {
175
+ return { type: 'boolean' };
176
+ }
177
+ if (Array.isArray(value)) {
178
+ if (value.length === 0) {
179
+ return { type: 'array', items: { type: 'unknown' }, empty: true };
180
+ }
181
+ // Sample first few items to determine array item structure
182
+ const sampleSize = Math.min(3, value.length);
183
+ const itemStructures = value
184
+ .slice(0, sampleSize)
185
+ .map((item) => extractStructure(item, depth + 1));
186
+ // Check if all items have the same structure
187
+ const firstStructure = JSON.stringify(itemStructures[0]);
188
+ const isHomogeneous = itemStructures.every((s) => JSON.stringify(s) === firstStructure);
189
+ return {
190
+ type: 'array',
191
+ items: isHomogeneous ? itemStructures[0] : { type: 'mixed' },
192
+ homogeneous: isHomogeneous,
193
+ };
194
+ }
195
+ if (valueType === 'object') {
196
+ const obj = value;
197
+ const keys = Object.keys(obj).sort();
198
+ if (keys.length === 0) {
199
+ return { type: 'object', properties: {}, empty: true };
200
+ }
201
+ const properties = {};
202
+ for (const key of keys) {
203
+ properties[key] = extractStructure(obj[key], depth + 1);
204
+ }
205
+ return {
206
+ type: 'object',
207
+ properties,
208
+ keys: keys.length,
209
+ };
210
+ }
211
+ return { type: valueType };
212
+ }
213
+ /**
214
+ * Build a fingerprint from analyzed responses.
215
+ */
216
+ function buildFingerprint(responses, structureHashes) {
217
+ if (responses.length === 0 || structureHashes.length === 0) {
218
+ return {
219
+ structureHash: 'empty',
220
+ contentType: 'empty',
221
+ size: 'tiny',
222
+ isEmpty: true,
223
+ sampleCount: 0,
224
+ confidence: 0,
225
+ };
226
+ }
227
+ // Determine most common structure hash
228
+ const hashCounts = new Map();
229
+ for (const hash of structureHashes) {
230
+ hashCounts.set(hash, (hashCounts.get(hash) ?? 0) + 1);
231
+ }
232
+ let dominantHash = 'empty';
233
+ let maxCount = 0;
234
+ for (const [hash, count] of hashCounts) {
235
+ if (count > maxCount) {
236
+ dominantHash = hash;
237
+ maxCount = count;
238
+ }
239
+ }
240
+ // Analyze first successful response for details
241
+ const firstResponse = responses.find((r) => r.response)?.response;
242
+ const content = firstResponse ? extractResponseContent(firstResponse) : undefined;
243
+ const contentType = classifyContentType(content);
244
+ const fields = extractTopLevelFields(content);
245
+ const arrayItemStructure = extractArrayItemStructure(content);
246
+ const size = classifySize(firstResponse);
247
+ const isEmpty = checkIsEmpty(content);
248
+ // Calculate confidence based on consistency
249
+ const confidence = structureHashes.length > 0 ? maxCount / structureHashes.length : 0;
250
+ return {
251
+ structureHash: dominantHash,
252
+ contentType,
253
+ fields,
254
+ arrayItemStructure,
255
+ size,
256
+ isEmpty,
257
+ sampleCount: responses.length,
258
+ confidence,
259
+ };
260
+ }
261
+ /**
262
+ * Classify the content type of a response.
263
+ */
264
+ function classifyContentType(content) {
265
+ if (content === undefined || content === null) {
266
+ return 'empty';
267
+ }
268
+ if (typeof content === 'string') {
269
+ if (content.trim().length === 0)
270
+ return 'empty';
271
+ // Check for binary content
272
+ if (isBinaryContent(content))
273
+ return 'binary';
274
+ return 'text';
275
+ }
276
+ if (Array.isArray(content)) {
277
+ return 'array';
278
+ }
279
+ if (typeof content === 'object') {
280
+ return 'object';
281
+ }
282
+ if (typeof content === 'number' || typeof content === 'boolean') {
283
+ return 'primitive';
284
+ }
285
+ return 'mixed';
286
+ }
287
+ /**
288
+ * Extract top-level field names from an object response.
289
+ */
290
+ function extractTopLevelFields(content) {
291
+ if (content && typeof content === 'object' && !Array.isArray(content)) {
292
+ return Object.keys(content).sort();
293
+ }
294
+ return undefined;
295
+ }
296
+ /**
297
+ * Extract array item structure hash if content is an array.
298
+ */
299
+ function extractArrayItemStructure(content) {
300
+ if (Array.isArray(content) && content.length > 0) {
301
+ return computeStructureHash(content[0]);
302
+ }
303
+ return undefined;
304
+ }
305
+ /**
306
+ * Classify response size.
307
+ */
308
+ function classifySize(response) {
309
+ if (!response?.content)
310
+ return 'tiny';
311
+ let totalLength = 0;
312
+ for (const item of response.content) {
313
+ if (item.type === 'text' && 'text' in item && typeof item.text === 'string') {
314
+ totalLength += item.text.length;
315
+ }
316
+ }
317
+ if (totalLength < 100)
318
+ return 'tiny';
319
+ if (totalLength < 1000)
320
+ return 'small';
321
+ if (totalLength < 10000)
322
+ return 'medium';
323
+ return 'large';
324
+ }
325
+ /**
326
+ * Check if content is effectively empty.
327
+ */
328
+ function checkIsEmpty(content) {
329
+ if (content === undefined || content === null)
330
+ return true;
331
+ if (typeof content === 'string') {
332
+ return content.trim().length === 0;
333
+ }
334
+ if (Array.isArray(content)) {
335
+ return content.length === 0;
336
+ }
337
+ if (typeof content === 'object') {
338
+ return Object.keys(content).length === 0;
339
+ }
340
+ return false;
341
+ }
342
+ /**
343
+ * Infer a JSON schema from a sample value.
344
+ */
345
+ export function inferSchemaFromValue(value) {
346
+ if (value === null) {
347
+ return { type: 'null', nullable: true };
348
+ }
349
+ if (value === undefined) {
350
+ return { type: 'undefined', nullable: true };
351
+ }
352
+ const valueType = typeof value;
353
+ if (valueType === 'string') {
354
+ return { type: 'string' };
355
+ }
356
+ if (valueType === 'number') {
357
+ return { type: Number.isInteger(value) ? 'integer' : 'number' };
358
+ }
359
+ if (valueType === 'boolean') {
360
+ return { type: 'boolean' };
361
+ }
362
+ if (Array.isArray(value)) {
363
+ if (value.length === 0) {
364
+ return { type: 'array' };
365
+ }
366
+ // Infer item schema from samples
367
+ const itemSchemas = value.slice(0, 5).map(inferSchemaFromValue);
368
+ const mergedItemSchema = mergeSchemas(itemSchemas);
369
+ return {
370
+ type: 'array',
371
+ items: mergedItemSchema,
372
+ };
373
+ }
374
+ if (valueType === 'object') {
375
+ const obj = value;
376
+ const properties = {};
377
+ const required = [];
378
+ for (const [key, val] of Object.entries(obj)) {
379
+ properties[key] = inferSchemaFromValue(val);
380
+ if (val !== null && val !== undefined) {
381
+ required.push(key);
382
+ }
383
+ }
384
+ return {
385
+ type: 'object',
386
+ properties,
387
+ required: required.length > 0 ? required.sort() : undefined,
388
+ };
389
+ }
390
+ return { type: 'unknown' };
391
+ }
392
+ /**
393
+ * Merge multiple inferred schemas into one.
394
+ */
395
+ function mergeSchemas(schemas) {
396
+ if (schemas.length === 0) {
397
+ return { type: 'unknown' };
398
+ }
399
+ if (schemas.length === 1) {
400
+ return schemas[0];
401
+ }
402
+ // Check if all schemas have the same type
403
+ const types = new Set(schemas.map((s) => s.type));
404
+ if (types.size === 1) {
405
+ const type = schemas[0].type;
406
+ if (type === 'object') {
407
+ // Merge object properties
408
+ const allProperties = new Map();
409
+ const allRequiredSets = [];
410
+ for (const schema of schemas) {
411
+ if (schema.properties) {
412
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
413
+ if (!allProperties.has(key)) {
414
+ allProperties.set(key, []);
415
+ }
416
+ allProperties.get(key).push(propSchema);
417
+ }
418
+ }
419
+ if (schema.required) {
420
+ allRequiredSets.push(new Set(schema.required));
421
+ }
422
+ }
423
+ const mergedProperties = {};
424
+ for (const [key, propSchemas] of allProperties) {
425
+ mergedProperties[key] = mergeSchemas(propSchemas);
426
+ }
427
+ // Required fields must be required in ALL schemas
428
+ let required;
429
+ if (allRequiredSets.length > 0) {
430
+ const intersection = allRequiredSets.reduce((acc, set) => {
431
+ return new Set([...acc].filter((x) => set.has(x)));
432
+ });
433
+ if (intersection.size > 0) {
434
+ required = [...intersection].sort();
435
+ }
436
+ }
437
+ return {
438
+ type: 'object',
439
+ properties: mergedProperties,
440
+ required,
441
+ };
442
+ }
443
+ if (type === 'array' && schemas.every((s) => s.items)) {
444
+ // Merge array item schemas
445
+ const itemSchemas = schemas.map((s) => s.items);
446
+ return {
447
+ type: 'array',
448
+ items: mergeSchemas(itemSchemas),
449
+ };
450
+ }
451
+ return { type };
452
+ }
453
+ // Mixed types - return union-like
454
+ if (types.has('null') || types.has('undefined')) {
455
+ const nonNullSchemas = schemas.filter((s) => s.type !== 'null' && s.type !== 'undefined');
456
+ if (nonNullSchemas.length > 0) {
457
+ const merged = mergeSchemas(nonNullSchemas);
458
+ merged.nullable = true;
459
+ return merged;
460
+ }
461
+ }
462
+ return { type: 'mixed' };
463
+ }
464
+ /**
465
+ * Analyze error responses to extract patterns.
466
+ */
467
+ function analyzeErrorPatterns(responses) {
468
+ const patterns = new Map();
469
+ for (const { response, error } of responses) {
470
+ const errorMessage = error ?? extractErrorMessage(response);
471
+ if (!errorMessage)
472
+ continue;
473
+ const category = categorizeError(errorMessage);
474
+ const patternHash = hashErrorPattern(errorMessage);
475
+ const key = `${category}:${patternHash}`;
476
+ if (patterns.has(key)) {
477
+ patterns.get(key).count++;
478
+ }
479
+ else {
480
+ patterns.set(key, {
481
+ category,
482
+ patternHash,
483
+ example: errorMessage.slice(0, 200),
484
+ count: 1,
485
+ });
486
+ }
487
+ }
488
+ return [...patterns.values()];
489
+ }
490
+ /**
491
+ * Extract error message from a response.
492
+ */
493
+ function extractErrorMessage(response) {
494
+ if (!response?.isError)
495
+ return null;
496
+ const textContent = response.content?.find((c) => c.type === 'text');
497
+ if (textContent && 'text' in textContent && typeof textContent.text === 'string') {
498
+ return textContent.text;
499
+ }
500
+ return null;
501
+ }
502
+ /**
503
+ * Categorize an error message.
504
+ */
505
+ function categorizeError(message) {
506
+ const lower = message.toLowerCase();
507
+ if (lower.includes('invalid') ||
508
+ lower.includes('required') ||
509
+ lower.includes('missing') ||
510
+ lower.includes('must be') ||
511
+ lower.includes('expected')) {
512
+ return 'validation';
513
+ }
514
+ if (lower.includes('not found') ||
515
+ lower.includes('does not exist') ||
516
+ lower.includes('no such') ||
517
+ lower.includes('404')) {
518
+ return 'not_found';
519
+ }
520
+ if (lower.includes('permission') ||
521
+ lower.includes('denied') ||
522
+ lower.includes('unauthorized') ||
523
+ lower.includes('forbidden') ||
524
+ lower.includes('access')) {
525
+ return 'permission';
526
+ }
527
+ if (lower.includes('timeout') || lower.includes('timed out')) {
528
+ return 'timeout';
529
+ }
530
+ if (lower.includes('internal') ||
531
+ lower.includes('server error') ||
532
+ lower.includes('unexpected')) {
533
+ return 'internal';
534
+ }
535
+ return 'unknown';
536
+ }
537
+ /**
538
+ * Create a normalized hash of an error pattern.
539
+ * Strips specific values (IDs, paths, numbers) to capture the pattern.
540
+ */
541
+ function hashErrorPattern(message) {
542
+ // Normalize the error message
543
+ const normalized = message
544
+ // Remove UUIDs
545
+ .replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '<UUID>')
546
+ // Remove file paths
547
+ .replace(/\/[\w./\-_]+/g, '<PATH>')
548
+ // Remove numbers
549
+ .replace(/\b\d+\b/g, '<N>')
550
+ // Remove quoted strings
551
+ .replace(/"[^"]*"/g, '"<STR>"')
552
+ .replace(/'[^']*'/g, "'<STR>'")
553
+ // Normalize whitespace
554
+ .replace(/\s+/g, ' ')
555
+ .trim()
556
+ .toLowerCase();
557
+ return createHash('sha256').update(normalized).digest('hex').slice(0, 12);
558
+ }
559
+ /**
560
+ * Compare two response fingerprints.
561
+ */
562
+ export function compareFingerprints(previous, current) {
563
+ // Handle missing fingerprints
564
+ if (!previous && !current) {
565
+ return { identical: true, changes: [], significance: 'none' };
566
+ }
567
+ if (!previous) {
568
+ return {
569
+ identical: false,
570
+ changes: [
571
+ {
572
+ aspect: 'structure',
573
+ description: 'Response fingerprint added (new baseline data)',
574
+ before: 'none',
575
+ after: current.structureHash,
576
+ breaking: false,
577
+ },
578
+ ],
579
+ significance: 'low',
580
+ };
581
+ }
582
+ if (!current) {
583
+ return {
584
+ identical: false,
585
+ changes: [
586
+ {
587
+ aspect: 'structure',
588
+ description: 'Response fingerprint removed',
589
+ before: previous.structureHash,
590
+ after: 'none',
591
+ breaking: false,
592
+ },
593
+ ],
594
+ significance: 'low',
595
+ };
596
+ }
597
+ const changes = [];
598
+ // Compare structure hash
599
+ if (previous.structureHash !== current.structureHash) {
600
+ changes.push({
601
+ aspect: 'structure',
602
+ description: 'Response structure changed',
603
+ before: previous.structureHash,
604
+ after: current.structureHash,
605
+ breaking: true,
606
+ });
607
+ }
608
+ // Compare content type
609
+ if (previous.contentType !== current.contentType) {
610
+ changes.push({
611
+ aspect: 'content_type',
612
+ description: `Response type changed from ${previous.contentType} to ${current.contentType}`,
613
+ before: previous.contentType,
614
+ after: current.contentType,
615
+ breaking: true,
616
+ });
617
+ }
618
+ // Compare fields
619
+ const prevFields = previous.fields?.join(',') ?? '';
620
+ const currFields = current.fields?.join(',') ?? '';
621
+ if (prevFields !== currFields) {
622
+ const addedFields = current.fields?.filter((f) => !previous.fields?.includes(f)) ?? [];
623
+ const removedFields = previous.fields?.filter((f) => !current.fields?.includes(f)) ?? [];
624
+ if (removedFields.length > 0) {
625
+ changes.push({
626
+ aspect: 'fields',
627
+ description: `Fields removed: ${removedFields.join(', ')}`,
628
+ before: prevFields,
629
+ after: currFields,
630
+ breaking: true,
631
+ });
632
+ }
633
+ if (addedFields.length > 0) {
634
+ changes.push({
635
+ aspect: 'fields',
636
+ description: `Fields added: ${addedFields.join(', ')}`,
637
+ before: prevFields,
638
+ after: currFields,
639
+ breaking: false,
640
+ });
641
+ }
642
+ }
643
+ // Compare array item structure
644
+ if (previous.arrayItemStructure !== current.arrayItemStructure) {
645
+ changes.push({
646
+ aspect: 'array_items',
647
+ description: 'Array item structure changed',
648
+ before: previous.arrayItemStructure ?? 'none',
649
+ after: current.arrayItemStructure ?? 'none',
650
+ breaking: true,
651
+ });
652
+ }
653
+ // Compare emptiness (significant behavioral change)
654
+ if (previous.isEmpty !== current.isEmpty) {
655
+ changes.push({
656
+ aspect: 'emptiness',
657
+ description: previous.isEmpty
658
+ ? 'Response now returns data (was empty)'
659
+ : 'Response now empty (was returning data)',
660
+ before: String(previous.isEmpty),
661
+ after: String(current.isEmpty),
662
+ breaking: !current.isEmpty, // Becoming empty is breaking
663
+ });
664
+ }
665
+ // Determine overall significance
666
+ let significance = 'none';
667
+ if (changes.length > 0) {
668
+ const hasBreaking = changes.some((c) => c.breaking);
669
+ const structureChanged = changes.some((c) => c.aspect === 'structure');
670
+ if (hasBreaking && structureChanged) {
671
+ significance = 'high';
672
+ }
673
+ else if (hasBreaking) {
674
+ significance = 'medium';
675
+ }
676
+ else {
677
+ significance = 'low';
678
+ }
679
+ }
680
+ return {
681
+ identical: changes.length === 0,
682
+ changes,
683
+ significance,
684
+ };
685
+ }
686
+ export function compareErrorPatterns(previous, current) {
687
+ const prevPatterns = new Set((previous ?? []).map((p) => p.patternHash));
688
+ const currPatterns = new Set((current ?? []).map((p) => p.patternHash));
689
+ const added = (current ?? []).filter((p) => !prevPatterns.has(p.patternHash));
690
+ const removed = (previous ?? []).filter((p) => !currPatterns.has(p.patternHash));
691
+ return {
692
+ added,
693
+ removed,
694
+ behaviorChanged: added.length > 0 || removed.length > 0,
695
+ };
696
+ }
697
+ /**
698
+ * Compute a hash for the inferred schema for comparison.
699
+ */
700
+ export function computeInferredSchemaHash(schema) {
701
+ if (!schema)
702
+ return 'empty';
703
+ // Create normalized representation
704
+ const normalized = normalizeInferredSchema(schema);
705
+ const serialized = JSON.stringify(normalized);
706
+ return createHash('sha256').update(serialized).digest('hex').slice(0, 16);
707
+ }
708
+ function normalizeInferredSchema(schema) {
709
+ const result = { type: schema.type };
710
+ if (schema.nullable) {
711
+ result.nullable = true;
712
+ }
713
+ if (schema.properties) {
714
+ const sortedProps = {};
715
+ for (const key of Object.keys(schema.properties).sort()) {
716
+ sortedProps[key] = normalizeInferredSchema(schema.properties[key]);
717
+ }
718
+ result.properties = sortedProps;
719
+ }
720
+ if (schema.items) {
721
+ result.items = normalizeInferredSchema(schema.items);
722
+ }
723
+ if (schema.required && schema.required.length > 0) {
724
+ result.required = [...schema.required].sort();
725
+ }
726
+ return result;
727
+ }
728
+ //# sourceMappingURL=response-fingerprint.js.map