@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,86 @@
1
+ /**
2
+ * Enhanced schema comparison for baseline drift detection.
3
+ *
4
+ * Improvements over basic comparison:
5
+ * - Hash argument types, not just keys
6
+ * - Detect constraint changes (min/max, patterns, enums)
7
+ * - Compare across multiple interactions
8
+ * - Visualize schema differences
9
+ * - Circular reference protection
10
+ * - Unicode normalization for consistent property comparison
11
+ */
12
+ /**
13
+ * JSON Schema property type.
14
+ */
15
+ interface SchemaProperty {
16
+ type?: string | string[];
17
+ format?: string;
18
+ description?: string;
19
+ enum?: unknown[];
20
+ minimum?: number;
21
+ maximum?: number;
22
+ minLength?: number;
23
+ maxLength?: number;
24
+ pattern?: string;
25
+ default?: unknown;
26
+ items?: SchemaProperty;
27
+ properties?: Record<string, SchemaProperty>;
28
+ required?: string[];
29
+ additionalProperties?: boolean | SchemaProperty;
30
+ }
31
+ /**
32
+ * Input schema for a tool.
33
+ */
34
+ interface InputSchema {
35
+ type?: string;
36
+ properties?: Record<string, SchemaProperty>;
37
+ required?: string[];
38
+ additionalProperties?: boolean | SchemaProperty;
39
+ }
40
+ /**
41
+ * Schema change type.
42
+ */
43
+ export type SchemaChangeType = 'property_added' | 'property_removed' | 'type_changed' | 'constraint_changed' | 'required_changed' | 'enum_changed' | 'description_changed' | 'format_changed';
44
+ /**
45
+ * Individual schema change.
46
+ */
47
+ export interface SchemaChange {
48
+ path: string;
49
+ changeType: SchemaChangeType;
50
+ before: unknown;
51
+ after: unknown;
52
+ breaking: boolean;
53
+ description: string;
54
+ }
55
+ /**
56
+ * Schema comparison result.
57
+ */
58
+ export interface SchemaComparisonResult {
59
+ identical: boolean;
60
+ changes: SchemaChange[];
61
+ previousHash: string;
62
+ currentHash: string;
63
+ visualDiff: string;
64
+ }
65
+ /**
66
+ * Compute a comprehensive schema hash that includes types and constraints.
67
+ * Protected against circular references and excessively deep schemas.
68
+ */
69
+ export declare function computeSchemaHash(schema: InputSchema | undefined): string;
70
+ /**
71
+ * Compare two schemas and return detailed differences.
72
+ */
73
+ export declare function compareSchemas(previous: InputSchema | undefined, current: InputSchema | undefined): SchemaComparisonResult;
74
+ /**
75
+ * Compute schema hash from multiple interactions (not just first).
76
+ * Returns the most common schema hash if schemas vary.
77
+ */
78
+ export declare function computeConsensusSchemaHash(interactions: Array<{
79
+ args: Record<string, unknown>;
80
+ }>): {
81
+ hash: string;
82
+ consistency: number;
83
+ variations: number;
84
+ };
85
+ export {};
86
+ //# sourceMappingURL=schema-compare.d.ts.map
@@ -0,0 +1,557 @@
1
+ /**
2
+ * Enhanced schema comparison for baseline drift detection.
3
+ *
4
+ * Improvements over basic comparison:
5
+ * - Hash argument types, not just keys
6
+ * - Detect constraint changes (min/max, patterns, enums)
7
+ * - Compare across multiple interactions
8
+ * - Visualize schema differences
9
+ * - Circular reference protection
10
+ * - Unicode normalization for consistent property comparison
11
+ */
12
+ import { createHash } from 'crypto';
13
+ import { PAYLOAD_LIMITS } from '../constants.js';
14
+ /**
15
+ * Maximum depth for schema traversal to prevent stack overflow
16
+ * from circular references or extremely deep nesting.
17
+ */
18
+ const MAX_SCHEMA_DEPTH = PAYLOAD_LIMITS.MAX_SCHEMA_DEPTH;
19
+ /**
20
+ * Compute a comprehensive schema hash that includes types and constraints.
21
+ * Protected against circular references and excessively deep schemas.
22
+ */
23
+ export function computeSchemaHash(schema) {
24
+ if (!schema)
25
+ return 'empty';
26
+ // Create normalized representation for hashing with circular reference protection
27
+ const seen = new WeakSet();
28
+ const normalized = normalizeSchema(schema, 0, seen);
29
+ const serialized = JSON.stringify(normalized);
30
+ return createHash('sha256').update(serialized).digest('hex').slice(0, 16);
31
+ }
32
+ /**
33
+ * Normalize a Unicode string key for consistent comparison.
34
+ * Uses NFC (Canonical Decomposition, followed by Canonical Composition)
35
+ * to ensure equivalent Unicode sequences compare as equal.
36
+ */
37
+ function normalizeUnicodeKey(key) {
38
+ return key.normalize('NFC');
39
+ }
40
+ /**
41
+ * Check if we've exceeded the maximum schema depth.
42
+ * Returns a truncation marker instead of continuing.
43
+ */
44
+ function checkDepthLimit(depth) {
45
+ if (depth > MAX_SCHEMA_DEPTH) {
46
+ return { _truncated: true, _reason: 'max_depth_exceeded', _depth: depth };
47
+ }
48
+ return null;
49
+ }
50
+ /**
51
+ * Check for circular reference and mark if detected.
52
+ */
53
+ function checkCircularRef(obj, seen) {
54
+ if (typeof obj === 'object' && obj !== null) {
55
+ if (seen.has(obj)) {
56
+ return { _circular: true };
57
+ }
58
+ seen.add(obj);
59
+ }
60
+ return null;
61
+ }
62
+ /**
63
+ * Normalize schema for consistent hashing.
64
+ * Sorts keys, removes undefined values, and handles edge cases:
65
+ * - Circular reference protection via WeakSet
66
+ * - Depth limiting to prevent stack overflow
67
+ * - Unicode normalization for property keys
68
+ *
69
+ * @param schema - The schema to normalize
70
+ * @param depth - Current recursion depth
71
+ * @param seen - WeakSet tracking visited objects for circular reference detection
72
+ */
73
+ function normalizeSchema(schema, depth = 0, seen = new WeakSet()) {
74
+ // Check depth limit
75
+ const depthLimit = checkDepthLimit(depth);
76
+ if (depthLimit)
77
+ return depthLimit;
78
+ // Check circular reference
79
+ const circularRef = checkCircularRef(schema, seen);
80
+ if (circularRef)
81
+ return circularRef;
82
+ const result = {};
83
+ // Sort and normalize simple fields
84
+ if (schema.type !== undefined) {
85
+ result.type = Array.isArray(schema.type) ? schema.type.sort() : schema.type;
86
+ }
87
+ if (schema.format !== undefined) {
88
+ result.format = schema.format;
89
+ }
90
+ if (schema.enum !== undefined) {
91
+ result.enum = [...schema.enum].sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
92
+ }
93
+ // Constraints - normalize numeric values to handle 1.0 vs 1
94
+ const constraintFields = ['minimum', 'maximum', 'minLength', 'maxLength', 'pattern', 'default'];
95
+ for (const field of constraintFields) {
96
+ const value = schema[field];
97
+ if (value !== undefined) {
98
+ // Normalize numeric values to avoid 1.0 vs 1 differences
99
+ if (typeof value === 'number') {
100
+ result[field] = Number.isInteger(value) ? Math.floor(value) : value;
101
+ }
102
+ else {
103
+ result[field] = value;
104
+ }
105
+ }
106
+ }
107
+ // Required array - normalize Unicode in property names
108
+ if (schema.required !== undefined && schema.required.length > 0) {
109
+ result.required = [...schema.required].map(normalizeUnicodeKey).sort();
110
+ }
111
+ // Properties - recursively normalize with Unicode-normalized keys
112
+ if (schema.properties) {
113
+ const props = {};
114
+ // Normalize Unicode in property keys and sort
115
+ const sortedKeys = Object.keys(schema.properties)
116
+ .map(normalizeUnicodeKey)
117
+ .sort();
118
+ for (const key of sortedKeys) {
119
+ // Find the original key (may differ in Unicode representation)
120
+ const originalKey = Object.keys(schema.properties).find(k => normalizeUnicodeKey(k) === key);
121
+ if (originalKey) {
122
+ props[key] = normalizeSchema(schema.properties[originalKey], depth + 1, seen);
123
+ }
124
+ }
125
+ result.properties = props;
126
+ }
127
+ // Items for arrays
128
+ if (schema.items) {
129
+ result.items = normalizeSchema(schema.items, depth + 1, seen);
130
+ }
131
+ // Additional properties
132
+ if (schema.additionalProperties !== undefined) {
133
+ if (typeof schema.additionalProperties === 'boolean') {
134
+ result.additionalProperties = schema.additionalProperties;
135
+ }
136
+ else {
137
+ result.additionalProperties = normalizeSchema(schema.additionalProperties, depth + 1, seen);
138
+ }
139
+ }
140
+ return result;
141
+ }
142
+ /**
143
+ * Compare two schemas and return detailed differences.
144
+ */
145
+ export function compareSchemas(previous, current) {
146
+ const previousHash = computeSchemaHash(previous);
147
+ const currentHash = computeSchemaHash(current);
148
+ if (previousHash === currentHash) {
149
+ return {
150
+ identical: true,
151
+ changes: [],
152
+ previousHash,
153
+ currentHash,
154
+ visualDiff: '',
155
+ };
156
+ }
157
+ const changes = [];
158
+ // Compare root required arrays
159
+ const prevRequired = new Set(previous?.required ?? []);
160
+ const currRequired = new Set(current?.required ?? []);
161
+ for (const req of currRequired) {
162
+ if (!prevRequired.has(req)) {
163
+ changes.push({
164
+ path: `required`,
165
+ changeType: 'required_changed',
166
+ before: Array.from(prevRequired),
167
+ after: Array.from(currRequired),
168
+ breaking: true, // New required field is breaking
169
+ description: `Property "${req}" is now required`,
170
+ });
171
+ break; // Only report once for required changes
172
+ }
173
+ }
174
+ for (const req of prevRequired) {
175
+ if (!currRequired.has(req)) {
176
+ changes.push({
177
+ path: `required`,
178
+ changeType: 'required_changed',
179
+ before: Array.from(prevRequired),
180
+ after: Array.from(currRequired),
181
+ breaking: false, // Removing required is non-breaking
182
+ description: `Property "${req}" is no longer required`,
183
+ });
184
+ break;
185
+ }
186
+ }
187
+ // Compare properties
188
+ const prevProps = previous?.properties ?? {};
189
+ const currProps = current?.properties ?? {};
190
+ const allKeys = new Set([...Object.keys(prevProps), ...Object.keys(currProps)]);
191
+ for (const key of allKeys) {
192
+ const prevProp = prevProps[key];
193
+ const currProp = currProps[key];
194
+ const path = key;
195
+ if (prevProp === undefined && currProp !== undefined) {
196
+ // Property added
197
+ const isRequired = currRequired.has(key);
198
+ changes.push({
199
+ path,
200
+ changeType: 'property_added',
201
+ before: undefined,
202
+ after: summarizeProperty(currProp),
203
+ breaking: isRequired, // Only breaking if required
204
+ description: `Property "${key}" added${isRequired ? ' (required)' : ' (optional)'}`,
205
+ });
206
+ }
207
+ else if (prevProp !== undefined && currProp === undefined) {
208
+ // Property removed
209
+ changes.push({
210
+ path,
211
+ changeType: 'property_removed',
212
+ before: summarizeProperty(prevProp),
213
+ after: undefined,
214
+ breaking: true, // Removing properties is always breaking
215
+ description: `Property "${key}" removed`,
216
+ });
217
+ }
218
+ else if (prevProp !== undefined && currProp !== undefined) {
219
+ // Compare property details
220
+ compareProperties(prevProp, currProp, path, changes);
221
+ }
222
+ }
223
+ // Generate visual diff
224
+ const visualDiff = generateVisualDiff(previous, current, changes);
225
+ return {
226
+ identical: false,
227
+ changes,
228
+ previousHash,
229
+ currentHash,
230
+ visualDiff,
231
+ };
232
+ }
233
+ /**
234
+ * Compare two properties recursively.
235
+ */
236
+ function compareProperties(prev, curr, path, changes) {
237
+ // Compare type
238
+ const prevType = normalizeType(prev.type);
239
+ const currType = normalizeType(curr.type);
240
+ if (prevType !== currType) {
241
+ changes.push({
242
+ path,
243
+ changeType: 'type_changed',
244
+ before: prev.type,
245
+ after: curr.type,
246
+ breaking: true,
247
+ description: `Type changed from "${prevType}" to "${currType}"`,
248
+ });
249
+ }
250
+ // Compare format
251
+ if (prev.format !== curr.format) {
252
+ changes.push({
253
+ path,
254
+ changeType: 'format_changed',
255
+ before: prev.format,
256
+ after: curr.format,
257
+ breaking: curr.format !== undefined && prev.format === undefined,
258
+ description: `Format changed from "${prev.format ?? 'none'}" to "${curr.format ?? 'none'}"`,
259
+ });
260
+ }
261
+ // Compare enums
262
+ if (!arraysEqual(prev.enum, curr.enum)) {
263
+ const prevSet = new Set((prev.enum ?? []).map(String));
264
+ const currSet = new Set((curr.enum ?? []).map(String));
265
+ const removed = [...prevSet].filter(v => !currSet.has(v));
266
+ const added = [...currSet].filter(v => !prevSet.has(v));
267
+ changes.push({
268
+ path,
269
+ changeType: 'enum_changed',
270
+ before: prev.enum,
271
+ after: curr.enum,
272
+ breaking: removed.length > 0, // Removing enum values is breaking
273
+ description: `Enum values changed: ${removed.length} removed, ${added.length} added`,
274
+ });
275
+ }
276
+ // Compare constraints
277
+ compareConstraint(prev, curr, path, 'minimum', changes);
278
+ compareConstraint(prev, curr, path, 'maximum', changes);
279
+ compareConstraint(prev, curr, path, 'minLength', changes);
280
+ compareConstraint(prev, curr, path, 'maxLength', changes);
281
+ compareConstraint(prev, curr, path, 'pattern', changes);
282
+ // Compare nested properties
283
+ if (prev.properties || curr.properties) {
284
+ const prevNested = prev.properties ?? {};
285
+ const currNested = curr.properties ?? {};
286
+ const nestedKeys = new Set([...Object.keys(prevNested), ...Object.keys(currNested)]);
287
+ for (const key of nestedKeys) {
288
+ const nestedPath = `${path}.${key}`;
289
+ const prevProp = prevNested[key];
290
+ const currProp = currNested[key];
291
+ if (!prevProp && currProp) {
292
+ changes.push({
293
+ path: nestedPath,
294
+ changeType: 'property_added',
295
+ before: undefined,
296
+ after: summarizeProperty(currProp),
297
+ breaking: false,
298
+ description: `Nested property "${key}" added`,
299
+ });
300
+ }
301
+ else if (prevProp && !currProp) {
302
+ changes.push({
303
+ path: nestedPath,
304
+ changeType: 'property_removed',
305
+ before: summarizeProperty(prevProp),
306
+ after: undefined,
307
+ breaking: true,
308
+ description: `Nested property "${key}" removed`,
309
+ });
310
+ }
311
+ else if (prevProp && currProp) {
312
+ compareProperties(prevProp, currProp, nestedPath, changes);
313
+ }
314
+ }
315
+ }
316
+ // Compare array items
317
+ if (prev.items || curr.items) {
318
+ if (prev.items && curr.items) {
319
+ compareProperties(prev.items, curr.items, `${path}[]`, changes);
320
+ }
321
+ else if (!prev.items && curr.items) {
322
+ changes.push({
323
+ path: `${path}[]`,
324
+ changeType: 'type_changed',
325
+ before: 'untyped array',
326
+ after: summarizeProperty(curr.items),
327
+ breaking: false,
328
+ description: 'Array items type added',
329
+ });
330
+ }
331
+ else if (prev.items && !curr.items) {
332
+ changes.push({
333
+ path: `${path}[]`,
334
+ changeType: 'type_changed',
335
+ before: summarizeProperty(prev.items),
336
+ after: 'untyped array',
337
+ breaking: false,
338
+ description: 'Array items type removed',
339
+ });
340
+ }
341
+ }
342
+ }
343
+ /**
344
+ * Compare a single constraint.
345
+ */
346
+ function compareConstraint(prev, curr, path, field, changes) {
347
+ const prevValue = prev[field];
348
+ const currValue = curr[field];
349
+ if (prevValue !== currValue) {
350
+ // Determine if breaking
351
+ let breaking = false;
352
+ const isMinConstraint = field === 'minimum' || field === 'minLength';
353
+ const isMaxConstraint = field === 'maximum' || field === 'maxLength';
354
+ if (isMinConstraint) {
355
+ // Increasing minimum is breaking (more restrictive)
356
+ breaking = currValue !== undefined && (prevValue === undefined || currValue > prevValue);
357
+ }
358
+ else if (isMaxConstraint) {
359
+ // Decreasing maximum is breaking (more restrictive)
360
+ breaking = currValue !== undefined && (prevValue === undefined || currValue < prevValue);
361
+ }
362
+ else if (field === 'pattern') {
363
+ // Changing pattern is potentially breaking
364
+ breaking = currValue !== undefined;
365
+ }
366
+ changes.push({
367
+ path,
368
+ changeType: 'constraint_changed',
369
+ before: prevValue,
370
+ after: currValue,
371
+ breaking,
372
+ description: `Constraint "${field}" changed from ${prevValue ?? 'none'} to ${currValue ?? 'none'}`,
373
+ });
374
+ }
375
+ }
376
+ /**
377
+ * Normalize type to string for comparison.
378
+ */
379
+ function normalizeType(type) {
380
+ if (type === undefined)
381
+ return 'any';
382
+ if (Array.isArray(type))
383
+ return type.sort().join('|');
384
+ return type;
385
+ }
386
+ /**
387
+ * Check if two arrays are equal.
388
+ */
389
+ function arraysEqual(a, b) {
390
+ if (a === b)
391
+ return true;
392
+ if (!a || !b)
393
+ return false;
394
+ if (a.length !== b.length)
395
+ return false;
396
+ const sortedA = [...a].sort((x, y) => JSON.stringify(x).localeCompare(JSON.stringify(y)));
397
+ const sortedB = [...b].sort((x, y) => JSON.stringify(x).localeCompare(JSON.stringify(y)));
398
+ return sortedA.every((v, i) => JSON.stringify(v) === JSON.stringify(sortedB[i]));
399
+ }
400
+ /**
401
+ * Summarize a property for display.
402
+ */
403
+ function summarizeProperty(prop) {
404
+ const parts = [];
405
+ if (prop.type) {
406
+ parts.push(Array.isArray(prop.type) ? prop.type.join('|') : prop.type);
407
+ }
408
+ if (prop.format) {
409
+ parts.push(`(${prop.format})`);
410
+ }
411
+ if (prop.enum) {
412
+ parts.push(`enum[${prop.enum.length}]`);
413
+ }
414
+ const constraints = [];
415
+ if (prop.minimum !== undefined)
416
+ constraints.push(`min:${prop.minimum}`);
417
+ if (prop.maximum !== undefined)
418
+ constraints.push(`max:${prop.maximum}`);
419
+ if (prop.minLength !== undefined)
420
+ constraints.push(`minLen:${prop.minLength}`);
421
+ if (prop.maxLength !== undefined)
422
+ constraints.push(`maxLen:${prop.maxLength}`);
423
+ if (prop.pattern)
424
+ constraints.push(`pattern`);
425
+ if (constraints.length > 0) {
426
+ parts.push(`{${constraints.join(',')}}`);
427
+ }
428
+ return parts.join(' ') || 'unknown';
429
+ }
430
+ /**
431
+ * Generate a visual diff of two schemas.
432
+ */
433
+ function generateVisualDiff(_previous, _current, changes) {
434
+ if (changes.length === 0)
435
+ return '';
436
+ const lines = ['Schema Diff:'];
437
+ lines.push('');
438
+ // Group changes by path
439
+ const byPath = new Map();
440
+ for (const change of changes) {
441
+ const existing = byPath.get(change.path) ?? [];
442
+ existing.push(change);
443
+ byPath.set(change.path, existing);
444
+ }
445
+ // Format each path's changes
446
+ for (const [path, pathChanges] of byPath) {
447
+ const marker = pathChanges.some(c => c.breaking) ? '!' : '~';
448
+ lines.push(`${marker} ${path}:`);
449
+ for (const change of pathChanges) {
450
+ const prefix = change.breaking ? ' [BREAKING]' : ' [info]';
451
+ lines.push(`${prefix} ${change.description}`);
452
+ if (change.before !== undefined) {
453
+ lines.push(` - ${formatValue(change.before)}`);
454
+ }
455
+ if (change.after !== undefined) {
456
+ lines.push(` + ${formatValue(change.after)}`);
457
+ }
458
+ }
459
+ }
460
+ // Summary
461
+ const breakingCount = changes.filter(c => c.breaking).length;
462
+ const nonBreakingCount = changes.length - breakingCount;
463
+ lines.push('');
464
+ lines.push(`Summary: ${breakingCount} breaking, ${nonBreakingCount} non-breaking change(s)`);
465
+ return lines.join('\n');
466
+ }
467
+ /**
468
+ * Format a value for display.
469
+ */
470
+ function formatValue(value) {
471
+ if (value === undefined)
472
+ return '<none>';
473
+ if (value === null)
474
+ return 'null';
475
+ if (typeof value === 'string')
476
+ return value;
477
+ return JSON.stringify(value);
478
+ }
479
+ /**
480
+ * Compute schema hash from multiple interactions (not just first).
481
+ * Returns the most common schema hash if schemas vary.
482
+ */
483
+ export function computeConsensusSchemaHash(interactions) {
484
+ if (interactions.length === 0) {
485
+ return { hash: 'empty', consistency: 1, variations: 0 };
486
+ }
487
+ // Compute hash for each interaction
488
+ const hashCounts = new Map();
489
+ for (const interaction of interactions) {
490
+ const argsSchema = inferSchemaFromArgs(interaction.args);
491
+ const hash = computeSchemaHash(argsSchema);
492
+ hashCounts.set(hash, (hashCounts.get(hash) ?? 0) + 1);
493
+ }
494
+ // Find most common hash
495
+ let mostCommonHash = 'empty';
496
+ let maxCount = 0;
497
+ for (const [hash, count] of hashCounts) {
498
+ if (count > maxCount) {
499
+ mostCommonHash = hash;
500
+ maxCount = count;
501
+ }
502
+ }
503
+ return {
504
+ hash: mostCommonHash,
505
+ consistency: maxCount / interactions.length,
506
+ variations: hashCounts.size,
507
+ };
508
+ }
509
+ /**
510
+ * Infer schema from actual argument values.
511
+ */
512
+ function inferSchemaFromArgs(args) {
513
+ const properties = {};
514
+ for (const [key, value] of Object.entries(args)) {
515
+ properties[key] = inferPropertyType(value);
516
+ }
517
+ return {
518
+ type: 'object',
519
+ properties,
520
+ required: Object.keys(properties).sort(),
521
+ };
522
+ }
523
+ /**
524
+ * Infer property type from a value.
525
+ */
526
+ function inferPropertyType(value) {
527
+ if (value === null)
528
+ return { type: 'null' };
529
+ if (value === undefined)
530
+ return { type: 'null' };
531
+ const type = typeof value;
532
+ switch (type) {
533
+ case 'string':
534
+ return { type: 'string' };
535
+ case 'number':
536
+ return Number.isInteger(value) ? { type: 'integer' } : { type: 'number' };
537
+ case 'boolean':
538
+ return { type: 'boolean' };
539
+ case 'object': {
540
+ if (Array.isArray(value)) {
541
+ if (value.length === 0)
542
+ return { type: 'array' };
543
+ // Infer items type from first element
544
+ return { type: 'array', items: inferPropertyType(value[0]) };
545
+ }
546
+ // Nested object
547
+ const properties = {};
548
+ for (const [k, v] of Object.entries(value)) {
549
+ properties[k] = inferPropertyType(v);
550
+ }
551
+ return { type: 'object', properties };
552
+ }
553
+ default:
554
+ return { type: 'string' }; // Fallback
555
+ }
556
+ }
557
+ //# sourceMappingURL=schema-compare.js.map