@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,574 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract-as-Code Validator.
|
|
3
|
+
*
|
|
4
|
+
* Validates MCP server behavior against defined contract expectations.
|
|
5
|
+
* Contracts define expected tools, parameters, and output constraints,
|
|
6
|
+
* enabling CI/CD integration and regression detection.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync, existsSync } from 'fs';
|
|
9
|
+
import * as yaml from 'yaml';
|
|
10
|
+
import { CONTRACT_TESTING } from '../constants.js';
|
|
11
|
+
/**
|
|
12
|
+
* Load a contract from a file.
|
|
13
|
+
*/
|
|
14
|
+
export function loadContract(filePath) {
|
|
15
|
+
if (!existsSync(filePath)) {
|
|
16
|
+
throw new Error(`Contract file not found: ${filePath}`);
|
|
17
|
+
}
|
|
18
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
19
|
+
try {
|
|
20
|
+
const parsed = yaml.parse(content);
|
|
21
|
+
// Validate schema version
|
|
22
|
+
if (parsed.version && parsed.version !== CONTRACT_TESTING.SCHEMA_VERSION) {
|
|
23
|
+
throw new Error(`Contract version ${parsed.version} is not supported. Expected version ${CONTRACT_TESTING.SCHEMA_VERSION}`);
|
|
24
|
+
}
|
|
25
|
+
// Set default version
|
|
26
|
+
if (!parsed.version) {
|
|
27
|
+
parsed.version = CONTRACT_TESTING.SCHEMA_VERSION;
|
|
28
|
+
}
|
|
29
|
+
// Ensure tools object exists
|
|
30
|
+
if (!parsed.tools) {
|
|
31
|
+
parsed.tools = {};
|
|
32
|
+
}
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error instanceof yaml.YAMLError) {
|
|
37
|
+
throw new Error(`Invalid YAML in contract file: ${error.message}`);
|
|
38
|
+
}
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Find a contract file in the given directory.
|
|
44
|
+
*/
|
|
45
|
+
export function findContractFile(directory) {
|
|
46
|
+
for (const filename of CONTRACT_TESTING.CONTRACT_FILENAMES) {
|
|
47
|
+
const filePath = `${directory}/${filename}`;
|
|
48
|
+
if (existsSync(filePath)) {
|
|
49
|
+
return filePath;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validate an MCP server against a contract.
|
|
56
|
+
*/
|
|
57
|
+
export async function validateContract(contract, tools, options = {}) {
|
|
58
|
+
const mode = options.mode || 'strict';
|
|
59
|
+
const violations = [];
|
|
60
|
+
// Build tool map for quick lookup
|
|
61
|
+
const toolMap = new Map();
|
|
62
|
+
for (const tool of tools) {
|
|
63
|
+
toolMap.set(tool.name, tool);
|
|
64
|
+
}
|
|
65
|
+
// Check each contracted tool
|
|
66
|
+
for (const [toolName, toolContract] of Object.entries(contract.tools)) {
|
|
67
|
+
const tool = toolMap.get(toolName);
|
|
68
|
+
// Check if required tool exists
|
|
69
|
+
if (!tool) {
|
|
70
|
+
if (toolContract.required !== false) {
|
|
71
|
+
violations.push({
|
|
72
|
+
type: 'missing_tool',
|
|
73
|
+
severity: 'breaking',
|
|
74
|
+
tool: toolName,
|
|
75
|
+
expected: 'Tool should exist',
|
|
76
|
+
actual: 'Tool not found',
|
|
77
|
+
message: `Required tool "${toolName}" is missing from server`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
// Validate input parameters
|
|
83
|
+
if (toolContract.input) {
|
|
84
|
+
const paramViolations = validateParameters(tool, toolContract.input);
|
|
85
|
+
violations.push(...paramViolations);
|
|
86
|
+
}
|
|
87
|
+
// Validate output (if function provided)
|
|
88
|
+
if (options.validateOutput && options.callTool && toolContract.output) {
|
|
89
|
+
try {
|
|
90
|
+
const result = await options.callTool(toolName, {});
|
|
91
|
+
const outputViolations = validateOutput(toolName, result, toolContract.output);
|
|
92
|
+
violations.push(...outputViolations);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
violations.push({
|
|
96
|
+
type: 'output_assertion_failed',
|
|
97
|
+
severity: 'warning',
|
|
98
|
+
tool: toolName,
|
|
99
|
+
expected: 'Successful tool call',
|
|
100
|
+
actual: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
101
|
+
message: `Failed to call tool "${toolName}" for output validation`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Check for unexpected tools (if strict mode or option enabled)
|
|
107
|
+
if (options.failOnUnexpectedTools || mode === 'strict') {
|
|
108
|
+
for (const tool of tools) {
|
|
109
|
+
if (!contract.tools[tool.name]) {
|
|
110
|
+
violations.push({
|
|
111
|
+
type: 'unexpected_tool',
|
|
112
|
+
severity: 'info',
|
|
113
|
+
tool: tool.name,
|
|
114
|
+
expected: 'Tool should be in contract',
|
|
115
|
+
actual: 'Tool not defined in contract',
|
|
116
|
+
message: `Unexpected tool "${tool.name}" not defined in contract`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Calculate summary
|
|
122
|
+
const breakingCount = violations.filter(v => v.severity === 'breaking').length;
|
|
123
|
+
const warningCount = violations.filter(v => v.severity === 'warning').length;
|
|
124
|
+
const infoCount = violations.filter(v => v.severity === 'info').length;
|
|
125
|
+
// Determine overall severity
|
|
126
|
+
let severity = 'none';
|
|
127
|
+
if (breakingCount > 0)
|
|
128
|
+
severity = 'breaking';
|
|
129
|
+
else if (warningCount > 0)
|
|
130
|
+
severity = 'warning';
|
|
131
|
+
else if (infoCount > 0)
|
|
132
|
+
severity = 'info';
|
|
133
|
+
// Determine pass/fail based on mode
|
|
134
|
+
let passed;
|
|
135
|
+
switch (mode) {
|
|
136
|
+
case 'strict':
|
|
137
|
+
passed = violations.length === 0;
|
|
138
|
+
break;
|
|
139
|
+
case 'lenient':
|
|
140
|
+
passed = breakingCount === 0;
|
|
141
|
+
break;
|
|
142
|
+
case 'report':
|
|
143
|
+
passed = true;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
passed,
|
|
148
|
+
severity,
|
|
149
|
+
violations,
|
|
150
|
+
summary: {
|
|
151
|
+
totalViolations: violations.length,
|
|
152
|
+
breakingCount,
|
|
153
|
+
warningCount,
|
|
154
|
+
infoCount,
|
|
155
|
+
toolsChecked: Object.keys(contract.tools).length,
|
|
156
|
+
toolsPassed: Object.keys(contract.tools).length -
|
|
157
|
+
violations.filter(v => v.type === 'missing_tool').length,
|
|
158
|
+
},
|
|
159
|
+
mode,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Validate tool parameters against contract.
|
|
164
|
+
*/
|
|
165
|
+
function validateParameters(tool, paramContracts) {
|
|
166
|
+
const violations = [];
|
|
167
|
+
const schema = tool.inputSchema;
|
|
168
|
+
const actualProperties = schema?.properties || {};
|
|
169
|
+
const actualRequired = schema?.required || [];
|
|
170
|
+
// Check contracted parameters
|
|
171
|
+
for (const [paramName, paramContract] of Object.entries(paramContracts)) {
|
|
172
|
+
const actualParam = actualProperties[paramName];
|
|
173
|
+
// Check if required parameter exists
|
|
174
|
+
if (!actualParam && paramContract.required !== false) {
|
|
175
|
+
violations.push({
|
|
176
|
+
type: 'missing_parameter',
|
|
177
|
+
severity: 'breaking',
|
|
178
|
+
tool: tool.name,
|
|
179
|
+
parameter: paramName,
|
|
180
|
+
expected: 'Parameter should exist',
|
|
181
|
+
actual: 'Parameter not found',
|
|
182
|
+
message: `Required parameter "${paramName}" is missing from tool "${tool.name}"`,
|
|
183
|
+
});
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (!actualParam)
|
|
187
|
+
continue;
|
|
188
|
+
// Check type
|
|
189
|
+
if (paramContract.type) {
|
|
190
|
+
const actualType = actualParam.type;
|
|
191
|
+
if (actualType !== paramContract.type) {
|
|
192
|
+
violations.push({
|
|
193
|
+
type: 'type_mismatch',
|
|
194
|
+
severity: 'breaking',
|
|
195
|
+
tool: tool.name,
|
|
196
|
+
parameter: paramName,
|
|
197
|
+
expected: paramContract.type,
|
|
198
|
+
actual: actualType || 'unknown',
|
|
199
|
+
message: `Parameter "${paramName}" type mismatch: expected ${paramContract.type}, got ${actualType}`,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Check format
|
|
204
|
+
if (paramContract.format) {
|
|
205
|
+
const actualFormat = actualParam.format;
|
|
206
|
+
if (actualFormat !== paramContract.format) {
|
|
207
|
+
violations.push({
|
|
208
|
+
type: 'format_mismatch',
|
|
209
|
+
severity: 'warning',
|
|
210
|
+
tool: tool.name,
|
|
211
|
+
parameter: paramName,
|
|
212
|
+
expected: paramContract.format,
|
|
213
|
+
actual: actualFormat || 'none',
|
|
214
|
+
message: `Parameter "${paramName}" format mismatch: expected ${paramContract.format}, got ${actualFormat || 'none'}`,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Check required status
|
|
219
|
+
if (paramContract.required === true && !actualRequired.includes(paramName)) {
|
|
220
|
+
violations.push({
|
|
221
|
+
type: 'constraint_violation',
|
|
222
|
+
severity: 'warning',
|
|
223
|
+
tool: tool.name,
|
|
224
|
+
parameter: paramName,
|
|
225
|
+
expected: 'Parameter should be required',
|
|
226
|
+
actual: 'Parameter is optional',
|
|
227
|
+
message: `Parameter "${paramName}" should be required in tool "${tool.name}"`,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
// Check enum
|
|
231
|
+
if (paramContract.enum) {
|
|
232
|
+
const actualEnum = actualParam.enum;
|
|
233
|
+
if (actualEnum) {
|
|
234
|
+
for (const value of paramContract.enum) {
|
|
235
|
+
if (!actualEnum.includes(value)) {
|
|
236
|
+
violations.push({
|
|
237
|
+
type: 'constraint_violation',
|
|
238
|
+
severity: 'warning',
|
|
239
|
+
tool: tool.name,
|
|
240
|
+
parameter: paramName,
|
|
241
|
+
expected: `Enum should contain ${String(value)}`,
|
|
242
|
+
actual: `Enum values: ${actualEnum.join(', ')}`,
|
|
243
|
+
message: `Parameter "${paramName}" enum missing expected value: ${String(value)}`,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Check min/max
|
|
250
|
+
if (paramContract.min !== undefined) {
|
|
251
|
+
const actualMin = actualParam.minimum;
|
|
252
|
+
if (actualMin === undefined || actualMin > paramContract.min) {
|
|
253
|
+
violations.push({
|
|
254
|
+
type: 'constraint_violation',
|
|
255
|
+
severity: 'warning',
|
|
256
|
+
tool: tool.name,
|
|
257
|
+
parameter: paramName,
|
|
258
|
+
expected: `minimum <= ${paramContract.min}`,
|
|
259
|
+
actual: `minimum = ${actualMin ?? 'none'}`,
|
|
260
|
+
message: `Parameter "${paramName}" minimum constraint mismatch`,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (paramContract.max !== undefined) {
|
|
265
|
+
const actualMax = actualParam.maximum;
|
|
266
|
+
if (actualMax === undefined || actualMax < paramContract.max) {
|
|
267
|
+
violations.push({
|
|
268
|
+
type: 'constraint_violation',
|
|
269
|
+
severity: 'warning',
|
|
270
|
+
tool: tool.name,
|
|
271
|
+
parameter: paramName,
|
|
272
|
+
expected: `maximum >= ${paramContract.max}`,
|
|
273
|
+
actual: `maximum = ${actualMax ?? 'none'}`,
|
|
274
|
+
message: `Parameter "${paramName}" maximum constraint mismatch`,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return violations;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Validate tool output against contract.
|
|
283
|
+
*/
|
|
284
|
+
function validateOutput(toolName, result, outputContract) {
|
|
285
|
+
const violations = [];
|
|
286
|
+
// Extract text content
|
|
287
|
+
const textContent = result.content.find(c => c.type === 'text');
|
|
288
|
+
const raw = textContent && 'text' in textContent ? String(textContent.text) : '';
|
|
289
|
+
// Check content type
|
|
290
|
+
if (outputContract.content_type) {
|
|
291
|
+
const actualType = detectContentType(raw);
|
|
292
|
+
if (actualType !== outputContract.content_type) {
|
|
293
|
+
violations.push({
|
|
294
|
+
type: 'content_type_mismatch',
|
|
295
|
+
severity: 'warning',
|
|
296
|
+
tool: toolName,
|
|
297
|
+
expected: outputContract.content_type,
|
|
298
|
+
actual: actualType,
|
|
299
|
+
message: `Output content type mismatch: expected ${outputContract.content_type}, got ${actualType}`,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// Parse JSON if applicable
|
|
304
|
+
let parsed;
|
|
305
|
+
try {
|
|
306
|
+
parsed = JSON.parse(raw);
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// Not JSON - can only check content type
|
|
310
|
+
if (outputContract.must_contain || outputContract.must_not_contain) {
|
|
311
|
+
violations.push({
|
|
312
|
+
type: 'output_assertion_failed',
|
|
313
|
+
severity: 'warning',
|
|
314
|
+
tool: toolName,
|
|
315
|
+
expected: 'JSON output for path assertions',
|
|
316
|
+
actual: 'Non-JSON output',
|
|
317
|
+
message: `Cannot evaluate JSONPath assertions on non-JSON output`,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return violations;
|
|
321
|
+
}
|
|
322
|
+
// Check must_contain assertions
|
|
323
|
+
if (outputContract.must_contain) {
|
|
324
|
+
for (const assertion of outputContract.must_contain) {
|
|
325
|
+
const value = getValueAtPath(parsed, assertion.path);
|
|
326
|
+
if (value === undefined) {
|
|
327
|
+
violations.push({
|
|
328
|
+
type: 'missing_output_field',
|
|
329
|
+
severity: 'warning',
|
|
330
|
+
tool: toolName,
|
|
331
|
+
path: assertion.path,
|
|
332
|
+
expected: 'Path should exist',
|
|
333
|
+
actual: 'Path not found',
|
|
334
|
+
message: `Required output path "${assertion.path}" not found in tool "${toolName}" response`,
|
|
335
|
+
});
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
// Check type
|
|
339
|
+
if (assertion.type) {
|
|
340
|
+
const actualType = getValueType(value);
|
|
341
|
+
if (actualType !== assertion.type) {
|
|
342
|
+
violations.push({
|
|
343
|
+
type: 'output_assertion_failed',
|
|
344
|
+
severity: 'warning',
|
|
345
|
+
tool: toolName,
|
|
346
|
+
path: assertion.path,
|
|
347
|
+
expected: assertion.type,
|
|
348
|
+
actual: actualType,
|
|
349
|
+
message: `Output type mismatch at "${assertion.path}": expected ${assertion.type}, got ${actualType}`,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Check pattern
|
|
354
|
+
if (assertion.pattern && typeof value === 'string') {
|
|
355
|
+
const regex = new RegExp(assertion.pattern);
|
|
356
|
+
if (!regex.test(value)) {
|
|
357
|
+
violations.push({
|
|
358
|
+
type: 'output_assertion_failed',
|
|
359
|
+
severity: 'warning',
|
|
360
|
+
tool: toolName,
|
|
361
|
+
path: assertion.path,
|
|
362
|
+
expected: `matches /${assertion.pattern}/`,
|
|
363
|
+
actual: truncate(value, 50),
|
|
364
|
+
message: `Output at "${assertion.path}" doesn't match pattern: ${assertion.pattern}`,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
// Check exact value
|
|
369
|
+
if (assertion.value !== undefined && value !== assertion.value) {
|
|
370
|
+
violations.push({
|
|
371
|
+
type: 'output_assertion_failed',
|
|
372
|
+
severity: 'warning',
|
|
373
|
+
tool: toolName,
|
|
374
|
+
path: assertion.path,
|
|
375
|
+
expected: String(assertion.value),
|
|
376
|
+
actual: String(value),
|
|
377
|
+
message: `Output value mismatch at "${assertion.path}"`,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// Check must_not_contain assertions
|
|
383
|
+
if (outputContract.must_not_contain) {
|
|
384
|
+
for (const assertion of outputContract.must_not_contain) {
|
|
385
|
+
const value = getValueAtPath(parsed, assertion.path);
|
|
386
|
+
if (value !== undefined) {
|
|
387
|
+
violations.push({
|
|
388
|
+
type: 'unexpected_output_field',
|
|
389
|
+
severity: 'info',
|
|
390
|
+
tool: toolName,
|
|
391
|
+
path: assertion.path,
|
|
392
|
+
expected: 'Path should not exist',
|
|
393
|
+
actual: `Path exists with value: ${truncate(String(value), 50)}`,
|
|
394
|
+
message: `Forbidden output path "${assertion.path}" found in tool "${toolName}" response`,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return violations;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Get value at a JSONPath-like path.
|
|
403
|
+
* Supports simple paths like $.field.nested[0].value
|
|
404
|
+
*/
|
|
405
|
+
function getValueAtPath(obj, path) {
|
|
406
|
+
// Remove leading $. if present
|
|
407
|
+
const cleanPath = path.replace(/^\$\.?/, '');
|
|
408
|
+
if (!cleanPath)
|
|
409
|
+
return obj;
|
|
410
|
+
const segments = cleanPath.split(/\.|\[|\]/).filter(Boolean);
|
|
411
|
+
let current = obj;
|
|
412
|
+
for (const segment of segments) {
|
|
413
|
+
if (current === null || current === undefined)
|
|
414
|
+
return undefined;
|
|
415
|
+
if (typeof current !== 'object')
|
|
416
|
+
return undefined;
|
|
417
|
+
// Handle array index
|
|
418
|
+
const index = parseInt(segment, 10);
|
|
419
|
+
if (!isNaN(index) && Array.isArray(current)) {
|
|
420
|
+
current = current[index];
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
current = current[segment];
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return current;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Get the type name of a value.
|
|
430
|
+
*/
|
|
431
|
+
function getValueType(value) {
|
|
432
|
+
if (value === null)
|
|
433
|
+
return 'null';
|
|
434
|
+
if (Array.isArray(value))
|
|
435
|
+
return 'array';
|
|
436
|
+
return typeof value;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Detect content type from raw output.
|
|
440
|
+
*/
|
|
441
|
+
function detectContentType(raw) {
|
|
442
|
+
const trimmed = raw.trim();
|
|
443
|
+
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
|
|
444
|
+
(trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
|
445
|
+
try {
|
|
446
|
+
JSON.parse(trimmed);
|
|
447
|
+
return 'json';
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
// Not valid JSON
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (/^#|^\*{1,3}[^*]|\[.*\]\(.*\)|^```/.test(trimmed)) {
|
|
454
|
+
return 'markdown';
|
|
455
|
+
}
|
|
456
|
+
return 'text';
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Truncate a string for display.
|
|
460
|
+
*/
|
|
461
|
+
function truncate(str, maxLen) {
|
|
462
|
+
if (str.length <= maxLen)
|
|
463
|
+
return str;
|
|
464
|
+
return str.slice(0, maxLen - 3) + '...';
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Generate a contract from current server state.
|
|
468
|
+
*/
|
|
469
|
+
export function generateContract(tools, serverName) {
|
|
470
|
+
const contract = {
|
|
471
|
+
version: CONTRACT_TESTING.SCHEMA_VERSION,
|
|
472
|
+
server: serverName ? { name: serverName } : undefined,
|
|
473
|
+
tools: {},
|
|
474
|
+
};
|
|
475
|
+
for (const tool of tools) {
|
|
476
|
+
const schema = tool.inputSchema;
|
|
477
|
+
const inputContracts = {};
|
|
478
|
+
if (schema?.properties) {
|
|
479
|
+
for (const [paramName, paramSchema] of Object.entries(schema.properties)) {
|
|
480
|
+
const param = paramSchema;
|
|
481
|
+
const paramContract = {
|
|
482
|
+
required: schema.required?.includes(paramName),
|
|
483
|
+
};
|
|
484
|
+
if (param.type)
|
|
485
|
+
paramContract.type = String(param.type);
|
|
486
|
+
if (param.format)
|
|
487
|
+
paramContract.format = String(param.format);
|
|
488
|
+
if (param.minimum !== undefined)
|
|
489
|
+
paramContract.min = Number(param.minimum);
|
|
490
|
+
if (param.maximum !== undefined)
|
|
491
|
+
paramContract.max = Number(param.maximum);
|
|
492
|
+
if (Array.isArray(param.enum))
|
|
493
|
+
paramContract.enum = param.enum;
|
|
494
|
+
inputContracts[paramName] = paramContract;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
contract.tools[tool.name] = {
|
|
498
|
+
required: true,
|
|
499
|
+
input: Object.keys(inputContracts).length > 0 ? inputContracts : undefined,
|
|
500
|
+
description: tool.description,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
return contract;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Generate contract YAML from contract object.
|
|
507
|
+
*/
|
|
508
|
+
export function generateContractYaml(contract) {
|
|
509
|
+
return yaml.stringify(contract, {
|
|
510
|
+
indent: 2,
|
|
511
|
+
lineWidth: 100,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Generate markdown report for contract validation.
|
|
516
|
+
*/
|
|
517
|
+
export function generateContractValidationMarkdown(result) {
|
|
518
|
+
const lines = [];
|
|
519
|
+
const statusIcon = result.passed ? '✓' : '✗';
|
|
520
|
+
const statusText = result.passed ? 'PASSED' : 'FAILED';
|
|
521
|
+
lines.push('## Contract Validation');
|
|
522
|
+
lines.push('');
|
|
523
|
+
lines.push(`**Status: ${statusIcon} ${statusText}** (${result.mode} mode)`);
|
|
524
|
+
lines.push('');
|
|
525
|
+
// Summary table
|
|
526
|
+
lines.push('| Metric | Count |');
|
|
527
|
+
lines.push('|--------|-------|');
|
|
528
|
+
lines.push(`| Tools Checked | ${result.summary.toolsChecked} |`);
|
|
529
|
+
lines.push(`| Tools Passed | ${result.summary.toolsPassed} |`);
|
|
530
|
+
lines.push(`| Total Violations | ${result.summary.totalViolations} |`);
|
|
531
|
+
if (result.summary.breakingCount > 0) {
|
|
532
|
+
lines.push(`| Breaking | ${result.summary.breakingCount} |`);
|
|
533
|
+
}
|
|
534
|
+
if (result.summary.warningCount > 0) {
|
|
535
|
+
lines.push(`| Warnings | ${result.summary.warningCount} |`);
|
|
536
|
+
}
|
|
537
|
+
if (result.summary.infoCount > 0) {
|
|
538
|
+
lines.push(`| Info | ${result.summary.infoCount} |`);
|
|
539
|
+
}
|
|
540
|
+
lines.push('');
|
|
541
|
+
// Violation details
|
|
542
|
+
if (result.violations.length > 0) {
|
|
543
|
+
lines.push('### Violations');
|
|
544
|
+
lines.push('');
|
|
545
|
+
// Group by severity
|
|
546
|
+
const bySeverity = {
|
|
547
|
+
breaking: [],
|
|
548
|
+
warning: [],
|
|
549
|
+
info: [],
|
|
550
|
+
none: [],
|
|
551
|
+
};
|
|
552
|
+
for (const v of result.violations) {
|
|
553
|
+
bySeverity[v.severity].push(v);
|
|
554
|
+
}
|
|
555
|
+
for (const severity of ['breaking', 'warning', 'info']) {
|
|
556
|
+
const violations = bySeverity[severity];
|
|
557
|
+
if (violations.length === 0)
|
|
558
|
+
continue;
|
|
559
|
+
const icon = severity === 'breaking' ? '❌' : severity === 'warning' ? '⚠️' : 'ℹ️';
|
|
560
|
+
lines.push(`#### ${icon} ${severity.charAt(0).toUpperCase() + severity.slice(1)} (${violations.length})`);
|
|
561
|
+
lines.push('');
|
|
562
|
+
for (const v of violations.slice(0, CONTRACT_TESTING.MAX_VALIDATION_ERRORS)) {
|
|
563
|
+
const location = [v.tool, v.parameter, v.path].filter(Boolean).join(' › ');
|
|
564
|
+
lines.push(`- **${location || v.type}**: ${v.message}`);
|
|
565
|
+
}
|
|
566
|
+
if (violations.length > CONTRACT_TESTING.MAX_VALIDATION_ERRORS) {
|
|
567
|
+
lines.push(`- ... and ${violations.length - CONTRACT_TESTING.MAX_VALIDATION_ERRORS} more`);
|
|
568
|
+
}
|
|
569
|
+
lines.push('');
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return lines.join('\n');
|
|
573
|
+
}
|
|
574
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost tracking and estimation module.
|
|
3
|
+
*/
|
|
4
|
+
export { CostTracker, estimateInterviewCost, formatCostEstimate, getModelPricing, estimateInterviewTime, formatCostAndTimeEstimate, suggestOptimizations, formatOptimizationSuggestions, isLocalProvider, } from './tracker.js';
|
|
5
|
+
export type { TokenUsage, CostEstimate, InterviewTimeEstimate, OptimizationSuggestion, OptimizationContext, } from './tracker.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost tracking and estimation module.
|
|
3
|
+
*/
|
|
4
|
+
export { CostTracker, estimateInterviewCost, formatCostEstimate, getModelPricing, estimateInterviewTime, formatCostAndTimeEstimate, suggestOptimizations, formatOptimizationSuggestions, isLocalProvider, } from './tracker.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|