@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.
- package/CHANGELOG.md +291 -0
- package/LICENSE +21 -0
- package/README.md +739 -0
- package/dist/auth/credentials.d.ts +64 -0
- package/dist/auth/credentials.js +218 -0
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.js +6 -0
- package/dist/auth/keychain.d.ts +64 -0
- package/dist/auth/keychain.js +268 -0
- package/dist/baseline/ab-testing.d.ts +80 -0
- package/dist/baseline/ab-testing.js +236 -0
- package/dist/baseline/ai-compatibility-scorer.d.ts +95 -0
- package/dist/baseline/ai-compatibility-scorer.js +606 -0
- package/dist/baseline/calibration.d.ts +77 -0
- package/dist/baseline/calibration.js +136 -0
- package/dist/baseline/category-matching.d.ts +85 -0
- package/dist/baseline/category-matching.js +289 -0
- package/dist/baseline/change-impact-analyzer.d.ts +98 -0
- package/dist/baseline/change-impact-analyzer.js +592 -0
- package/dist/baseline/comparator.d.ts +64 -0
- package/dist/baseline/comparator.js +916 -0
- package/dist/baseline/confidence.d.ts +55 -0
- package/dist/baseline/confidence.js +122 -0
- package/dist/baseline/converter.d.ts +61 -0
- package/dist/baseline/converter.js +585 -0
- package/dist/baseline/dependency-analyzer.d.ts +89 -0
- package/dist/baseline/dependency-analyzer.js +567 -0
- package/dist/baseline/deprecation-tracker.d.ts +133 -0
- package/dist/baseline/deprecation-tracker.js +322 -0
- package/dist/baseline/diff.d.ts +55 -0
- package/dist/baseline/diff.js +1584 -0
- package/dist/baseline/documentation-scorer.d.ts +205 -0
- package/dist/baseline/documentation-scorer.js +466 -0
- package/dist/baseline/embeddings.d.ts +118 -0
- package/dist/baseline/embeddings.js +251 -0
- package/dist/baseline/error-analyzer.d.ts +198 -0
- package/dist/baseline/error-analyzer.js +721 -0
- package/dist/baseline/evaluation/evaluator.d.ts +42 -0
- package/dist/baseline/evaluation/evaluator.js +323 -0
- package/dist/baseline/evaluation/expanded-dataset.d.ts +45 -0
- package/dist/baseline/evaluation/expanded-dataset.js +1164 -0
- package/dist/baseline/evaluation/golden-dataset.d.ts +58 -0
- package/dist/baseline/evaluation/golden-dataset.js +717 -0
- package/dist/baseline/evaluation/index.d.ts +15 -0
- package/dist/baseline/evaluation/index.js +15 -0
- package/dist/baseline/evaluation/types.d.ts +186 -0
- package/dist/baseline/evaluation/types.js +8 -0
- package/dist/baseline/external-dependency-detector.d.ts +181 -0
- package/dist/baseline/external-dependency-detector.js +524 -0
- package/dist/baseline/golden-output.d.ts +162 -0
- package/dist/baseline/golden-output.js +636 -0
- package/dist/baseline/health-scorer.d.ts +174 -0
- package/dist/baseline/health-scorer.js +451 -0
- package/dist/baseline/incremental-checker.d.ts +97 -0
- package/dist/baseline/incremental-checker.js +174 -0
- package/dist/baseline/index.d.ts +31 -0
- package/dist/baseline/index.js +42 -0
- package/dist/baseline/migration-generator.d.ts +137 -0
- package/dist/baseline/migration-generator.js +554 -0
- package/dist/baseline/migrations.d.ts +60 -0
- package/dist/baseline/migrations.js +197 -0
- package/dist/baseline/performance-tracker.d.ts +214 -0
- package/dist/baseline/performance-tracker.js +577 -0
- package/dist/baseline/pr-comment-generator.d.ts +117 -0
- package/dist/baseline/pr-comment-generator.js +546 -0
- package/dist/baseline/response-fingerprint.d.ts +127 -0
- package/dist/baseline/response-fingerprint.js +728 -0
- package/dist/baseline/response-schema-tracker.d.ts +129 -0
- package/dist/baseline/response-schema-tracker.js +420 -0
- package/dist/baseline/risk-scorer.d.ts +54 -0
- package/dist/baseline/risk-scorer.js +434 -0
- package/dist/baseline/saver.d.ts +89 -0
- package/dist/baseline/saver.js +554 -0
- package/dist/baseline/scenario-generator.d.ts +151 -0
- package/dist/baseline/scenario-generator.js +905 -0
- package/dist/baseline/schema-compare.d.ts +86 -0
- package/dist/baseline/schema-compare.js +557 -0
- package/dist/baseline/schema-evolution.d.ts +189 -0
- package/dist/baseline/schema-evolution.js +467 -0
- package/dist/baseline/semantic.d.ts +203 -0
- package/dist/baseline/semantic.js +908 -0
- package/dist/baseline/synonyms.d.ts +60 -0
- package/dist/baseline/synonyms.js +386 -0
- package/dist/baseline/telemetry.d.ts +165 -0
- package/dist/baseline/telemetry.js +294 -0
- package/dist/baseline/test-pruner.d.ts +120 -0
- package/dist/baseline/test-pruner.js +387 -0
- package/dist/baseline/types.d.ts +449 -0
- package/dist/baseline/types.js +5 -0
- package/dist/baseline/version.d.ts +138 -0
- package/dist/baseline/version.js +206 -0
- package/dist/cache/index.d.ts +5 -0
- package/dist/cache/index.js +5 -0
- package/dist/cache/response-cache.d.ts +151 -0
- package/dist/cache/response-cache.js +287 -0
- package/dist/ci/index.d.ts +60 -0
- package/dist/ci/index.js +342 -0
- package/dist/cli/commands/auth.d.ts +12 -0
- package/dist/cli/commands/auth.js +352 -0
- package/dist/cli/commands/badge.d.ts +3 -0
- package/dist/cli/commands/badge.js +74 -0
- package/dist/cli/commands/baseline-accept.d.ts +15 -0
- package/dist/cli/commands/baseline-accept.js +178 -0
- package/dist/cli/commands/baseline-migrate.d.ts +12 -0
- package/dist/cli/commands/baseline-migrate.js +164 -0
- package/dist/cli/commands/baseline.d.ts +14 -0
- package/dist/cli/commands/baseline.js +449 -0
- package/dist/cli/commands/beta.d.ts +10 -0
- package/dist/cli/commands/beta.js +231 -0
- package/dist/cli/commands/check.d.ts +11 -0
- package/dist/cli/commands/check.js +820 -0
- package/dist/cli/commands/cloud/badge.d.ts +3 -0
- package/dist/cli/commands/cloud/badge.js +74 -0
- package/dist/cli/commands/cloud/diff.d.ts +6 -0
- package/dist/cli/commands/cloud/diff.js +79 -0
- package/dist/cli/commands/cloud/history.d.ts +6 -0
- package/dist/cli/commands/cloud/history.js +102 -0
- package/dist/cli/commands/cloud/link.d.ts +9 -0
- package/dist/cli/commands/cloud/link.js +119 -0
- package/dist/cli/commands/cloud/login.d.ts +7 -0
- package/dist/cli/commands/cloud/login.js +499 -0
- package/dist/cli/commands/cloud/projects.d.ts +6 -0
- package/dist/cli/commands/cloud/projects.js +44 -0
- package/dist/cli/commands/cloud/shared.d.ts +7 -0
- package/dist/cli/commands/cloud/shared.js +42 -0
- package/dist/cli/commands/cloud/teams.d.ts +8 -0
- package/dist/cli/commands/cloud/teams.js +169 -0
- package/dist/cli/commands/cloud/upload.d.ts +8 -0
- package/dist/cli/commands/cloud/upload.js +181 -0
- package/dist/cli/commands/contract.d.ts +11 -0
- package/dist/cli/commands/contract.js +280 -0
- package/dist/cli/commands/discover.d.ts +3 -0
- package/dist/cli/commands/discover.js +82 -0
- package/dist/cli/commands/eval.d.ts +9 -0
- package/dist/cli/commands/eval.js +187 -0
- package/dist/cli/commands/explore.d.ts +11 -0
- package/dist/cli/commands/explore.js +437 -0
- package/dist/cli/commands/feedback.d.ts +9 -0
- package/dist/cli/commands/feedback.js +174 -0
- package/dist/cli/commands/golden.d.ts +12 -0
- package/dist/cli/commands/golden.js +407 -0
- package/dist/cli/commands/history.d.ts +10 -0
- package/dist/cli/commands/history.js +202 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.js +219 -0
- package/dist/cli/commands/interview.d.ts +3 -0
- package/dist/cli/commands/interview.js +903 -0
- package/dist/cli/commands/link.d.ts +10 -0
- package/dist/cli/commands/link.js +169 -0
- package/dist/cli/commands/login.d.ts +7 -0
- package/dist/cli/commands/login.js +499 -0
- package/dist/cli/commands/preset.d.ts +33 -0
- package/dist/cli/commands/preset.js +297 -0
- package/dist/cli/commands/profile.d.ts +33 -0
- package/dist/cli/commands/profile.js +286 -0
- package/dist/cli/commands/registry.d.ts +11 -0
- package/dist/cli/commands/registry.js +146 -0
- package/dist/cli/commands/shared.d.ts +79 -0
- package/dist/cli/commands/shared.js +196 -0
- package/dist/cli/commands/teams.d.ts +8 -0
- package/dist/cli/commands/teams.js +169 -0
- package/dist/cli/commands/test.d.ts +9 -0
- package/dist/cli/commands/test.js +500 -0
- package/dist/cli/commands/upload.d.ts +8 -0
- package/dist/cli/commands/upload.js +223 -0
- package/dist/cli/commands/validate-config.d.ts +6 -0
- package/dist/cli/commands/validate-config.js +35 -0
- package/dist/cli/commands/verify.d.ts +11 -0
- package/dist/cli/commands/verify.js +283 -0
- package/dist/cli/commands/watch.d.ts +12 -0
- package/dist/cli/commands/watch.js +253 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +178 -0
- package/dist/cli/interactive.d.ts +47 -0
- package/dist/cli/interactive.js +216 -0
- package/dist/cli/output/terminal-reporter.d.ts +19 -0
- package/dist/cli/output/terminal-reporter.js +104 -0
- package/dist/cli/output.d.ts +226 -0
- package/dist/cli/output.js +438 -0
- package/dist/cli/utils/env.d.ts +5 -0
- package/dist/cli/utils/env.js +14 -0
- package/dist/cli/utils/progress.d.ts +59 -0
- package/dist/cli/utils/progress.js +206 -0
- package/dist/cli/utils/server-context.d.ts +10 -0
- package/dist/cli/utils/server-context.js +36 -0
- package/dist/cloud/auth.d.ts +144 -0
- package/dist/cloud/auth.js +374 -0
- package/dist/cloud/client.d.ts +24 -0
- package/dist/cloud/client.js +65 -0
- package/dist/cloud/http-client.d.ts +38 -0
- package/dist/cloud/http-client.js +215 -0
- package/dist/cloud/index.d.ts +23 -0
- package/dist/cloud/index.js +25 -0
- package/dist/cloud/mock-client.d.ts +107 -0
- package/dist/cloud/mock-client.js +545 -0
- package/dist/cloud/types.d.ts +515 -0
- package/dist/cloud/types.js +15 -0
- package/dist/config/defaults.d.ts +160 -0
- package/dist/config/defaults.js +169 -0
- package/dist/config/loader.d.ts +24 -0
- package/dist/config/loader.js +122 -0
- package/dist/config/template.d.ts +42 -0
- package/dist/config/template.js +647 -0
- package/dist/config/validator.d.ts +2112 -0
- package/dist/config/validator.js +658 -0
- package/dist/constants/cloud.d.ts +107 -0
- package/dist/constants/cloud.js +110 -0
- package/dist/constants/core.d.ts +521 -0
- package/dist/constants/core.js +556 -0
- package/dist/constants/testing.d.ts +1283 -0
- package/dist/constants/testing.js +1568 -0
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +10 -0
- package/dist/contract/index.d.ts +6 -0
- package/dist/contract/index.js +5 -0
- package/dist/contract/validator.d.ts +177 -0
- package/dist/contract/validator.js +574 -0
- package/dist/cost/index.d.ts +6 -0
- package/dist/cost/index.js +5 -0
- package/dist/cost/tracker.d.ts +134 -0
- package/dist/cost/tracker.js +313 -0
- package/dist/discovery/discovery.d.ts +16 -0
- package/dist/discovery/discovery.js +173 -0
- package/dist/discovery/types.d.ts +51 -0
- package/dist/discovery/types.js +2 -0
- package/dist/docs/agents.d.ts +3 -0
- package/dist/docs/agents.js +995 -0
- package/dist/docs/contract.d.ts +51 -0
- package/dist/docs/contract.js +1681 -0
- package/dist/docs/generator.d.ts +4 -0
- package/dist/docs/generator.js +4 -0
- package/dist/docs/html-reporter.d.ts +9 -0
- package/dist/docs/html-reporter.js +757 -0
- package/dist/docs/index.d.ts +10 -0
- package/dist/docs/index.js +11 -0
- package/dist/docs/junit-reporter.d.ts +18 -0
- package/dist/docs/junit-reporter.js +210 -0
- package/dist/docs/report.d.ts +14 -0
- package/dist/docs/report.js +44 -0
- package/dist/docs/sarif-reporter.d.ts +19 -0
- package/dist/docs/sarif-reporter.js +335 -0
- package/dist/docs/shared.d.ts +35 -0
- package/dist/docs/shared.js +162 -0
- package/dist/docs/templates.d.ts +12 -0
- package/dist/docs/templates.js +76 -0
- package/dist/errors/index.d.ts +6 -0
- package/dist/errors/index.js +6 -0
- package/dist/errors/retry.d.ts +92 -0
- package/dist/errors/retry.js +323 -0
- package/dist/errors/types.d.ts +321 -0
- package/dist/errors/types.js +584 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +32 -0
- package/dist/interview/dependency-resolver.d.ts +11 -0
- package/dist/interview/dependency-resolver.js +32 -0
- package/dist/interview/interviewer.d.ts +232 -0
- package/dist/interview/interviewer.js +1939 -0
- package/dist/interview/mock-response-generator.d.ts +7 -0
- package/dist/interview/mock-response-generator.js +102 -0
- package/dist/interview/orchestrator.d.ts +237 -0
- package/dist/interview/orchestrator.js +1296 -0
- package/dist/interview/rate-limiter.d.ts +15 -0
- package/dist/interview/rate-limiter.js +55 -0
- package/dist/interview/response-validator.d.ts +10 -0
- package/dist/interview/response-validator.js +132 -0
- package/dist/interview/schema-inferrer.d.ts +8 -0
- package/dist/interview/schema-inferrer.js +71 -0
- package/dist/interview/schema-test-generator.d.ts +71 -0
- package/dist/interview/schema-test-generator.js +834 -0
- package/dist/interview/smart-value-generator.d.ts +155 -0
- package/dist/interview/smart-value-generator.js +554 -0
- package/dist/interview/stateful-test-runner.d.ts +19 -0
- package/dist/interview/stateful-test-runner.js +106 -0
- package/dist/interview/types.d.ts +561 -0
- package/dist/interview/types.js +2 -0
- package/dist/llm/anthropic.d.ts +41 -0
- package/dist/llm/anthropic.js +355 -0
- package/dist/llm/client.d.ts +123 -0
- package/dist/llm/client.js +42 -0
- package/dist/llm/factory.d.ts +38 -0
- package/dist/llm/factory.js +145 -0
- package/dist/llm/fallback.d.ts +140 -0
- package/dist/llm/fallback.js +379 -0
- package/dist/llm/index.d.ts +18 -0
- package/dist/llm/index.js +15 -0
- package/dist/llm/ollama.d.ts +37 -0
- package/dist/llm/ollama.js +330 -0
- package/dist/llm/openai.d.ts +25 -0
- package/dist/llm/openai.js +320 -0
- package/dist/llm/token-budget.d.ts +161 -0
- package/dist/llm/token-budget.js +395 -0
- package/dist/logging/logger.d.ts +70 -0
- package/dist/logging/logger.js +130 -0
- package/dist/metrics/collector.d.ts +106 -0
- package/dist/metrics/collector.js +547 -0
- package/dist/metrics/index.d.ts +7 -0
- package/dist/metrics/index.js +7 -0
- package/dist/metrics/prometheus.d.ts +20 -0
- package/dist/metrics/prometheus.js +241 -0
- package/dist/metrics/types.d.ts +209 -0
- package/dist/metrics/types.js +5 -0
- package/dist/persona/builtins.d.ts +54 -0
- package/dist/persona/builtins.js +219 -0
- package/dist/persona/index.d.ts +8 -0
- package/dist/persona/index.js +8 -0
- package/dist/persona/loader.d.ts +30 -0
- package/dist/persona/loader.js +190 -0
- package/dist/persona/types.d.ts +144 -0
- package/dist/persona/types.js +5 -0
- package/dist/persona/validation.d.ts +94 -0
- package/dist/persona/validation.js +332 -0
- package/dist/prompts/index.d.ts +5 -0
- package/dist/prompts/index.js +5 -0
- package/dist/prompts/templates.d.ts +180 -0
- package/dist/prompts/templates.js +431 -0
- package/dist/registry/client.d.ts +49 -0
- package/dist/registry/client.js +191 -0
- package/dist/registry/index.d.ts +7 -0
- package/dist/registry/index.js +6 -0
- package/dist/registry/types.d.ts +140 -0
- package/dist/registry/types.js +6 -0
- package/dist/scenarios/evaluator.d.ts +43 -0
- package/dist/scenarios/evaluator.js +206 -0
- package/dist/scenarios/index.d.ts +10 -0
- package/dist/scenarios/index.js +9 -0
- package/dist/scenarios/loader.d.ts +20 -0
- package/dist/scenarios/loader.js +285 -0
- package/dist/scenarios/types.d.ts +153 -0
- package/dist/scenarios/types.js +8 -0
- package/dist/security/index.d.ts +17 -0
- package/dist/security/index.js +18 -0
- package/dist/security/payloads.d.ts +61 -0
- package/dist/security/payloads.js +268 -0
- package/dist/security/security-tester.d.ts +42 -0
- package/dist/security/security-tester.js +582 -0
- package/dist/security/types.d.ts +166 -0
- package/dist/security/types.js +8 -0
- package/dist/transport/base-transport.d.ts +59 -0
- package/dist/transport/base-transport.js +38 -0
- package/dist/transport/http-transport.d.ts +67 -0
- package/dist/transport/http-transport.js +238 -0
- package/dist/transport/mcp-client.d.ts +141 -0
- package/dist/transport/mcp-client.js +496 -0
- package/dist/transport/sse-transport.d.ts +88 -0
- package/dist/transport/sse-transport.js +316 -0
- package/dist/transport/stdio-transport.d.ts +43 -0
- package/dist/transport/stdio-transport.js +238 -0
- package/dist/transport/types.d.ts +125 -0
- package/dist/transport/types.js +16 -0
- package/dist/utils/concurrency.d.ts +123 -0
- package/dist/utils/concurrency.js +213 -0
- package/dist/utils/formatters.d.ts +16 -0
- package/dist/utils/formatters.js +37 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/jsonpath.d.ts +87 -0
- package/dist/utils/jsonpath.js +326 -0
- package/dist/utils/markdown.d.ts +113 -0
- package/dist/utils/markdown.js +265 -0
- package/dist/utils/network.d.ts +14 -0
- package/dist/utils/network.js +17 -0
- package/dist/utils/sanitize.d.ts +92 -0
- package/dist/utils/sanitize.js +191 -0
- package/dist/utils/semantic.d.ts +194 -0
- package/dist/utils/semantic.js +1051 -0
- package/dist/utils/smart-truncate.d.ts +94 -0
- package/dist/utils/smart-truncate.js +361 -0
- package/dist/utils/timeout.d.ts +153 -0
- package/dist/utils/timeout.js +205 -0
- package/dist/utils/yaml-parser.d.ts +58 -0
- package/dist/utils/yaml-parser.js +86 -0
- package/dist/validation/index.d.ts +32 -0
- package/dist/validation/index.js +32 -0
- package/dist/validation/semantic-test-generator.d.ts +50 -0
- package/dist/validation/semantic-test-generator.js +176 -0
- package/dist/validation/semantic-types.d.ts +66 -0
- package/dist/validation/semantic-types.js +94 -0
- package/dist/validation/semantic-validator.d.ts +38 -0
- package/dist/validation/semantic-validator.js +340 -0
- package/dist/verification/index.d.ts +6 -0
- package/dist/verification/index.js +5 -0
- package/dist/verification/types.d.ts +133 -0
- package/dist/verification/types.js +5 -0
- package/dist/verification/verifier.d.ts +30 -0
- package/dist/verification/verifier.js +309 -0
- package/dist/version.d.ts +19 -0
- package/dist/version.js +48 -0
- package/dist/workflow/auto-generator.d.ts +27 -0
- package/dist/workflow/auto-generator.js +513 -0
- package/dist/workflow/discovery.d.ts +40 -0
- package/dist/workflow/discovery.js +195 -0
- package/dist/workflow/executor.d.ts +82 -0
- package/dist/workflow/executor.js +611 -0
- package/dist/workflow/index.d.ts +10 -0
- package/dist/workflow/index.js +10 -0
- package/dist/workflow/loader.d.ts +24 -0
- package/dist/workflow/loader.js +194 -0
- package/dist/workflow/state-tracker.d.ts +98 -0
- package/dist/workflow/state-tracker.js +424 -0
- package/dist/workflow/types.d.ts +337 -0
- package/dist/workflow/types.js +5 -0
- package/package.json +94 -0
- 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
|