@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,407 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Golden command - Manage golden outputs for tool validation.
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* bellwether golden save --tool <name> Save current output as golden
|
|
6
|
+
* bellwether golden compare Compare against all golden outputs
|
|
7
|
+
* bellwether golden list List all saved golden outputs
|
|
8
|
+
* bellwether golden delete --tool <name> Delete a golden output
|
|
9
|
+
*/
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
12
|
+
import { MCPClient } from '../../transport/mcp-client.js';
|
|
13
|
+
import { discover } from '../../discovery/discovery.js';
|
|
14
|
+
import { loadConfig, ConfigNotFoundError } from '../../config/loader.js';
|
|
15
|
+
import { validateConfigForCheck } from '../../config/validator.js';
|
|
16
|
+
import { getGoldenStorePath, saveGoldenOutput, createGoldenOutput, listGoldenOutputs, deleteGoldenOutput, compareWithGolden, } from '../../baseline/golden-output.js';
|
|
17
|
+
import * as output from '../output.js';
|
|
18
|
+
import { EXIT_CODES, PATHS } from '../../constants.js';
|
|
19
|
+
import { formatDateISO } from '../../utils/index.js';
|
|
20
|
+
export const goldenCommand = new Command('golden')
|
|
21
|
+
.description('Manage golden outputs for tool validation');
|
|
22
|
+
// Save command
|
|
23
|
+
goldenCommand
|
|
24
|
+
.command('save')
|
|
25
|
+
.description('Capture current tool output as golden reference')
|
|
26
|
+
.requiredOption('--tool <name>', 'Tool name to capture golden output for')
|
|
27
|
+
.option('-c, --config <path>', 'Path to config file', PATHS.DEFAULT_CONFIG_FILENAME)
|
|
28
|
+
.option('--args <json>', 'JSON arguments to pass to the tool')
|
|
29
|
+
.option('--mode <mode>', 'Comparison mode: exact, structural, semantic')
|
|
30
|
+
.option('--allowed-drift <paths>', 'Comma-separated JSONPath patterns for allowed changes')
|
|
31
|
+
.option('--no-normalize-timestamps', 'Disable timestamp normalization')
|
|
32
|
+
.option('--no-normalize-uuids', 'Disable UUID normalization')
|
|
33
|
+
.option('--description <text>', 'Description of this golden output')
|
|
34
|
+
.action(async (options) => {
|
|
35
|
+
// Load configuration
|
|
36
|
+
let config;
|
|
37
|
+
try {
|
|
38
|
+
config = loadConfig(options.config);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error instanceof ConfigNotFoundError) {
|
|
42
|
+
output.error(error.message);
|
|
43
|
+
process.exit(EXIT_CODES.ERROR);
|
|
44
|
+
}
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
// Validate config
|
|
48
|
+
const serverCommand = config.server.command;
|
|
49
|
+
const args = config.server.args;
|
|
50
|
+
try {
|
|
51
|
+
validateConfigForCheck(config, serverCommand);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
output.error(error instanceof Error ? error.message : String(error));
|
|
55
|
+
process.exit(EXIT_CODES.ERROR);
|
|
56
|
+
}
|
|
57
|
+
const argsJson = options.args ?? config.golden.defaultArgs;
|
|
58
|
+
const mode = options.mode ?? config.golden.mode;
|
|
59
|
+
const normalizeTimestamps = options.normalizeTimestamps === false
|
|
60
|
+
? false
|
|
61
|
+
: config.golden.normalizeTimestamps;
|
|
62
|
+
const normalizeUuids = options.normalizeUuids === false
|
|
63
|
+
? false
|
|
64
|
+
: config.golden.normalizeUuids;
|
|
65
|
+
// Parse tool arguments
|
|
66
|
+
let toolArgs;
|
|
67
|
+
try {
|
|
68
|
+
toolArgs = JSON.parse(argsJson);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
output.error(`Invalid JSON for --args: ${error instanceof Error ? error.message : error}`);
|
|
72
|
+
process.exit(EXIT_CODES.ERROR);
|
|
73
|
+
}
|
|
74
|
+
// Parse allowed drift paths
|
|
75
|
+
const allowedDrift = options.allowedDrift
|
|
76
|
+
? options.allowedDrift.split(',').map((p) => p.trim())
|
|
77
|
+
: [];
|
|
78
|
+
// Validate comparison mode
|
|
79
|
+
const validModes = ['exact', 'structural', 'semantic'];
|
|
80
|
+
if (!validModes.includes(mode)) {
|
|
81
|
+
output.error(`Invalid mode "${mode}". Valid modes: ${validModes.join(', ')}`);
|
|
82
|
+
process.exit(EXIT_CODES.ERROR);
|
|
83
|
+
}
|
|
84
|
+
const outputDir = config.output.dir;
|
|
85
|
+
const storePath = getGoldenStorePath(outputDir);
|
|
86
|
+
output.info(`Capturing golden output for: ${options.tool}`);
|
|
87
|
+
output.info(`Mode: ${mode}`);
|
|
88
|
+
if (allowedDrift.length > 0) {
|
|
89
|
+
output.info(`Allowed drift paths: ${allowedDrift.join(', ')}`);
|
|
90
|
+
}
|
|
91
|
+
output.newline();
|
|
92
|
+
const mcpClient = new MCPClient({
|
|
93
|
+
timeout: config.server.timeout,
|
|
94
|
+
debug: config.logging.level === 'debug',
|
|
95
|
+
transport: 'stdio',
|
|
96
|
+
});
|
|
97
|
+
try {
|
|
98
|
+
// Connect to server
|
|
99
|
+
output.info('Connecting to MCP server...');
|
|
100
|
+
await mcpClient.connect(serverCommand, args, config.server.env);
|
|
101
|
+
// Discover tools
|
|
102
|
+
const discovery = await discover(mcpClient, serverCommand, args);
|
|
103
|
+
const tool = discovery.tools.find(t => t.name === options.tool);
|
|
104
|
+
if (!tool) {
|
|
105
|
+
output.error(`Tool not found: ${options.tool}`);
|
|
106
|
+
output.info(`Available tools: ${discovery.tools.map(t => t.name).join(', ')}`);
|
|
107
|
+
process.exit(EXIT_CODES.ERROR);
|
|
108
|
+
}
|
|
109
|
+
// Call the tool
|
|
110
|
+
output.info(`Calling tool: ${options.tool}`);
|
|
111
|
+
const response = await mcpClient.callTool(options.tool, toolArgs);
|
|
112
|
+
if (response.isError) {
|
|
113
|
+
output.error('Tool returned an error:');
|
|
114
|
+
const textContent = response.content.find(c => c.type === 'text');
|
|
115
|
+
if (textContent && 'text' in textContent) {
|
|
116
|
+
output.error(String(textContent.text));
|
|
117
|
+
}
|
|
118
|
+
process.exit(EXIT_CODES.ERROR);
|
|
119
|
+
}
|
|
120
|
+
// Create golden output
|
|
121
|
+
const golden = createGoldenOutput(options.tool, toolArgs, response, {
|
|
122
|
+
mode,
|
|
123
|
+
allowedDrift,
|
|
124
|
+
normalizeTimestamps,
|
|
125
|
+
normalizeUuids,
|
|
126
|
+
description: options.description,
|
|
127
|
+
});
|
|
128
|
+
// Ensure output directory exists
|
|
129
|
+
mkdirSync(outputDir, { recursive: true });
|
|
130
|
+
// Save golden output
|
|
131
|
+
saveGoldenOutput(golden, storePath);
|
|
132
|
+
output.success('\nGolden output saved!');
|
|
133
|
+
output.info(`Store: ${storePath}`);
|
|
134
|
+
output.info(`Tool: ${golden.toolName}`);
|
|
135
|
+
output.info(`Content type: ${golden.output.contentType}`);
|
|
136
|
+
output.info(`Content hash: ${golden.output.contentHash}`);
|
|
137
|
+
output.info(`Comparison mode: ${golden.tolerance.mode}`);
|
|
138
|
+
// Show preview of captured content
|
|
139
|
+
const preview = golden.output.raw.slice(0, 200);
|
|
140
|
+
if (preview) {
|
|
141
|
+
output.newline();
|
|
142
|
+
output.info('Output preview:');
|
|
143
|
+
output.info(preview + (golden.output.raw.length > 200 ? '...' : ''));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
output.error(`Failed to capture golden output: ${error instanceof Error ? error.message : error}`);
|
|
148
|
+
process.exit(EXIT_CODES.ERROR);
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
await mcpClient.disconnect();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
// Compare command
|
|
155
|
+
goldenCommand
|
|
156
|
+
.command('compare')
|
|
157
|
+
.description('Compare current outputs against saved golden outputs')
|
|
158
|
+
.option('-c, --config <path>', 'Path to config file', PATHS.DEFAULT_CONFIG_FILENAME)
|
|
159
|
+
.option('--tool <name>', 'Only compare a specific tool')
|
|
160
|
+
.option('--fail-on-drift', 'Exit with error if any drift detected')
|
|
161
|
+
.option('--format <format>', 'Output format: text, json, markdown')
|
|
162
|
+
.action(async (options) => {
|
|
163
|
+
// Load configuration
|
|
164
|
+
let config;
|
|
165
|
+
try {
|
|
166
|
+
config = loadConfig(options.config);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (error instanceof ConfigNotFoundError) {
|
|
170
|
+
output.error(error.message);
|
|
171
|
+
process.exit(EXIT_CODES.ERROR);
|
|
172
|
+
}
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
// Validate config
|
|
176
|
+
const serverCommand = config.server.command;
|
|
177
|
+
const args = config.server.args;
|
|
178
|
+
try {
|
|
179
|
+
validateConfigForCheck(config, serverCommand);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
output.error(error instanceof Error ? error.message : String(error));
|
|
183
|
+
process.exit(EXIT_CODES.ERROR);
|
|
184
|
+
}
|
|
185
|
+
const outputDir = config.output.dir;
|
|
186
|
+
const storePath = getGoldenStorePath(outputDir);
|
|
187
|
+
const format = options.format ?? config.golden.compareFormat;
|
|
188
|
+
if (!existsSync(storePath)) {
|
|
189
|
+
output.warn('No golden outputs found.');
|
|
190
|
+
output.info('Use "bellwether golden save --tool <name>" to capture golden outputs.');
|
|
191
|
+
process.exit(EXIT_CODES.CLEAN);
|
|
192
|
+
}
|
|
193
|
+
const goldens = listGoldenOutputs(storePath);
|
|
194
|
+
const filteredGoldens = options.tool
|
|
195
|
+
? goldens.filter(g => g.toolName === options.tool)
|
|
196
|
+
: goldens;
|
|
197
|
+
if (filteredGoldens.length === 0) {
|
|
198
|
+
if (options.tool) {
|
|
199
|
+
output.warn(`No golden output found for tool: ${options.tool}`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
output.warn('No golden outputs saved.');
|
|
203
|
+
}
|
|
204
|
+
process.exit(EXIT_CODES.CLEAN);
|
|
205
|
+
}
|
|
206
|
+
output.info(`Comparing ${filteredGoldens.length} golden output(s)...`);
|
|
207
|
+
output.newline();
|
|
208
|
+
const mcpClient = new MCPClient({
|
|
209
|
+
timeout: config.server.timeout,
|
|
210
|
+
debug: config.logging.level === 'debug',
|
|
211
|
+
transport: 'stdio',
|
|
212
|
+
});
|
|
213
|
+
try {
|
|
214
|
+
// Connect to server
|
|
215
|
+
output.info('Connecting to MCP server...');
|
|
216
|
+
await mcpClient.connect(serverCommand, args, config.server.env);
|
|
217
|
+
output.newline();
|
|
218
|
+
const results = [];
|
|
219
|
+
for (const golden of filteredGoldens) {
|
|
220
|
+
output.info(`Comparing: ${golden.toolName}`);
|
|
221
|
+
try {
|
|
222
|
+
const response = await mcpClient.callTool(golden.toolName, golden.inputArgs);
|
|
223
|
+
const result = compareWithGolden(golden, response);
|
|
224
|
+
results.push(result);
|
|
225
|
+
const icon = result.passed ? '\u2713' : '\u2717';
|
|
226
|
+
if (result.passed) {
|
|
227
|
+
output.success(` ${icon} ${result.summary}`);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
output.error(` ${icon} ${result.summary}`);
|
|
231
|
+
if (result.differences.filter(d => !d.allowed).length <= 5) {
|
|
232
|
+
for (const diff of result.differences.filter(d => !d.allowed)) {
|
|
233
|
+
output.warn(` - ${diff.description} at ${diff.path}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
results.push({
|
|
240
|
+
toolName: golden.toolName,
|
|
241
|
+
passed: false,
|
|
242
|
+
severity: 'breaking',
|
|
243
|
+
mode: golden.tolerance.mode,
|
|
244
|
+
goldenCapturedAt: golden.capturedAt,
|
|
245
|
+
differences: [{
|
|
246
|
+
type: 'changed',
|
|
247
|
+
path: '$',
|
|
248
|
+
expected: 'successful response',
|
|
249
|
+
actual: `error: ${error instanceof Error ? error.message : String(error)}`,
|
|
250
|
+
allowed: false,
|
|
251
|
+
description: 'Tool call failed',
|
|
252
|
+
}],
|
|
253
|
+
summary: `Tool call failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
254
|
+
});
|
|
255
|
+
output.error(` \u2717 Tool call failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
output.newline();
|
|
259
|
+
// Summary
|
|
260
|
+
const passed = results.filter(r => r.passed).length;
|
|
261
|
+
const failed = results.length - passed;
|
|
262
|
+
if (format === 'json') {
|
|
263
|
+
output.info(JSON.stringify(results, null, 2));
|
|
264
|
+
}
|
|
265
|
+
else if (format === 'markdown') {
|
|
266
|
+
output.info(formatResultsMarkdown(results));
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
output.info('--- Summary ---');
|
|
270
|
+
output.info(`Total: ${results.length}`);
|
|
271
|
+
output.success(`Passed: ${passed}`);
|
|
272
|
+
if (failed > 0) {
|
|
273
|
+
output.error(`Failed: ${failed}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (options.failOnDrift && failed > 0) {
|
|
277
|
+
process.exit(EXIT_CODES.BREAKING);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
await mcpClient.disconnect();
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
// List command
|
|
285
|
+
goldenCommand
|
|
286
|
+
.command('list')
|
|
287
|
+
.description('List all saved golden outputs')
|
|
288
|
+
.option('-c, --config <path>', 'Path to config file', PATHS.DEFAULT_CONFIG_FILENAME)
|
|
289
|
+
.option('--format <format>', 'Output format: text, json')
|
|
290
|
+
.action(async (options) => {
|
|
291
|
+
// Load configuration
|
|
292
|
+
let config;
|
|
293
|
+
try {
|
|
294
|
+
config = loadConfig(options.config);
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
if (error instanceof ConfigNotFoundError) {
|
|
298
|
+
output.error(error.message);
|
|
299
|
+
process.exit(EXIT_CODES.ERROR);
|
|
300
|
+
}
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
const outputDir = config.output.dir;
|
|
304
|
+
const storePath = getGoldenStorePath(outputDir);
|
|
305
|
+
const format = options.format ?? config.golden.listFormat;
|
|
306
|
+
if (!existsSync(storePath)) {
|
|
307
|
+
output.info('No golden outputs found.');
|
|
308
|
+
output.info('Use "bellwether golden save --tool <name>" to capture golden outputs.');
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const goldens = listGoldenOutputs(storePath);
|
|
312
|
+
if (goldens.length === 0) {
|
|
313
|
+
output.info('No golden outputs saved.');
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (format === 'json') {
|
|
317
|
+
output.info(JSON.stringify(goldens, null, 2));
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
output.info(`Golden Outputs (${goldens.length}):`);
|
|
321
|
+
output.newline();
|
|
322
|
+
output.info('| Tool | Captured | Mode | Content Type |');
|
|
323
|
+
output.info('|------|----------|------|--------------|');
|
|
324
|
+
for (const golden of goldens) {
|
|
325
|
+
const captured = formatDateISO(new Date(golden.capturedAt));
|
|
326
|
+
output.info(`| \`${golden.toolName}\` | ${captured} | ${golden.tolerance.mode} | ${golden.output.contentType} |`);
|
|
327
|
+
}
|
|
328
|
+
output.newline();
|
|
329
|
+
output.info(`Store: ${storePath}`);
|
|
330
|
+
});
|
|
331
|
+
// Delete command
|
|
332
|
+
goldenCommand
|
|
333
|
+
.command('delete')
|
|
334
|
+
.description('Delete a saved golden output')
|
|
335
|
+
.requiredOption('--tool <name>', 'Tool name to delete golden output for')
|
|
336
|
+
.option('-c, --config <path>', 'Path to config file', PATHS.DEFAULT_CONFIG_FILENAME)
|
|
337
|
+
.option('--all', 'Delete all golden outputs for this tool (if multiple with different args)')
|
|
338
|
+
.action(async (options) => {
|
|
339
|
+
// Load configuration
|
|
340
|
+
let config;
|
|
341
|
+
try {
|
|
342
|
+
config = loadConfig(options.config);
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
if (error instanceof ConfigNotFoundError) {
|
|
346
|
+
output.error(error.message);
|
|
347
|
+
process.exit(EXIT_CODES.ERROR);
|
|
348
|
+
}
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
const outputDir = config.output.dir;
|
|
352
|
+
const storePath = getGoldenStorePath(outputDir);
|
|
353
|
+
if (!existsSync(storePath)) {
|
|
354
|
+
output.warn('No golden outputs found.');
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const deleted = deleteGoldenOutput(options.tool, storePath);
|
|
358
|
+
if (deleted) {
|
|
359
|
+
output.success(`Deleted golden output for: ${options.tool}`);
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
output.warn(`No golden output found for: ${options.tool}`);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
/**
|
|
366
|
+
* Format comparison results as Markdown.
|
|
367
|
+
*/
|
|
368
|
+
function formatResultsMarkdown(results) {
|
|
369
|
+
const lines = [];
|
|
370
|
+
lines.push('## Golden Output Validation');
|
|
371
|
+
lines.push('');
|
|
372
|
+
lines.push('| Tool | Status | Mode | Differences |');
|
|
373
|
+
lines.push('|------|--------|------|-------------|');
|
|
374
|
+
for (const result of results) {
|
|
375
|
+
const status = result.passed ? '✓ Match' : `✗ ${result.severity}`;
|
|
376
|
+
const diffCount = result.differences.filter(d => !d.allowed).length;
|
|
377
|
+
lines.push(`| \`${result.toolName}\` | ${status} | ${result.mode} | ${diffCount} |`);
|
|
378
|
+
}
|
|
379
|
+
lines.push('');
|
|
380
|
+
// Details for failed comparisons
|
|
381
|
+
const failed = results.filter(r => !r.passed);
|
|
382
|
+
if (failed.length > 0) {
|
|
383
|
+
lines.push('### Drift Details');
|
|
384
|
+
lines.push('');
|
|
385
|
+
for (const result of failed) {
|
|
386
|
+
lines.push(`#### ${result.toolName}`);
|
|
387
|
+
lines.push('');
|
|
388
|
+
lines.push(`**Golden captured:** ${formatDateISO(new Date(result.goldenCapturedAt))}`);
|
|
389
|
+
lines.push(`**Mode:** ${result.mode}`);
|
|
390
|
+
lines.push(`**Severity:** ${result.severity}`);
|
|
391
|
+
lines.push('');
|
|
392
|
+
lines.push('**Changes:**');
|
|
393
|
+
for (const diff of result.differences.filter(d => !d.allowed)) {
|
|
394
|
+
lines.push(`- ${diff.description}`);
|
|
395
|
+
if (diff.expected !== undefined) {
|
|
396
|
+
lines.push(` - Expected: \`${String(diff.expected)}\``);
|
|
397
|
+
}
|
|
398
|
+
if (diff.actual !== undefined) {
|
|
399
|
+
lines.push(` - Actual: \`${String(diff.actual)}\``);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
lines.push('');
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return lines.join('\n');
|
|
406
|
+
}
|
|
407
|
+
//# sourceMappingURL=golden.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History command for viewing baseline history in Bellwether Cloud.
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
export declare const historyCommand: Command;
|
|
6
|
+
/**
|
|
7
|
+
* Diff command for comparing specific versions.
|
|
8
|
+
*/
|
|
9
|
+
export declare const diffCommand: Command;
|
|
10
|
+
//# sourceMappingURL=history.d.ts.map
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History command for viewing baseline history in Bellwether Cloud.
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import { getSessionToken, getLinkedProject } from '../../cloud/auth.js';
|
|
6
|
+
import { createCloudClient } from '../../cloud/client.js';
|
|
7
|
+
import { formatDateLocale } from '../../utils/index.js';
|
|
8
|
+
import { EXIT_CODES } from '../../constants.js';
|
|
9
|
+
import * as output from '../output.js';
|
|
10
|
+
import { getSeverityIcon } from '../output.js';
|
|
11
|
+
export const historyCommand = new Command('history')
|
|
12
|
+
.description('View baseline history for a project')
|
|
13
|
+
.argument('[project-id]', 'Project ID (uses linked project if not specified)')
|
|
14
|
+
.option('-n, --limit <n>', 'Number of versions to show', '10')
|
|
15
|
+
.option('--json', 'Output as JSON')
|
|
16
|
+
.option('--session <session>', 'Session token (overrides stored/env session)')
|
|
17
|
+
.action(async (projectIdArg, options) => {
|
|
18
|
+
// Get session
|
|
19
|
+
const sessionToken = options.session ?? getSessionToken();
|
|
20
|
+
if (!sessionToken) {
|
|
21
|
+
output.error('Not authenticated. Run `bellwether login` first.');
|
|
22
|
+
process.exit(EXIT_CODES.ERROR);
|
|
23
|
+
}
|
|
24
|
+
// Determine project ID
|
|
25
|
+
let projectId = projectIdArg;
|
|
26
|
+
if (!projectId) {
|
|
27
|
+
const link = getLinkedProject();
|
|
28
|
+
if (link) {
|
|
29
|
+
projectId = link.projectId;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (!projectId) {
|
|
33
|
+
output.error('No project specified.');
|
|
34
|
+
output.error('\nEither:');
|
|
35
|
+
output.error(' - Provide a project ID as argument');
|
|
36
|
+
output.error(' - Run `bellwether link` to link this directory to a project');
|
|
37
|
+
process.exit(EXIT_CODES.ERROR);
|
|
38
|
+
}
|
|
39
|
+
// Create client and fetch history
|
|
40
|
+
const client = createCloudClient({ sessionToken });
|
|
41
|
+
if (!client.isAuthenticated()) {
|
|
42
|
+
output.error('Authentication failed. Run `bellwether login` to re-authenticate.');
|
|
43
|
+
process.exit(EXIT_CODES.ERROR);
|
|
44
|
+
}
|
|
45
|
+
const limit = parseInt(options.limit, 10);
|
|
46
|
+
try {
|
|
47
|
+
const history = await client.getHistory(projectId, limit);
|
|
48
|
+
if (options.json) {
|
|
49
|
+
output.info(JSON.stringify(history, null, 2));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (history.length === 0) {
|
|
53
|
+
output.info('No baselines uploaded yet.');
|
|
54
|
+
output.info('\nRun `bellwether check <server> --save-baseline` then `bellwether upload`.');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// Get project info for display
|
|
58
|
+
const project = await client.getProject(projectId);
|
|
59
|
+
const projectName = project?.name ?? projectId;
|
|
60
|
+
output.info(`Baseline History: ${projectName}`);
|
|
61
|
+
output.info(`Showing ${history.length} version(s)\n`);
|
|
62
|
+
output.info('Ver Uploaded CLI Version Hash');
|
|
63
|
+
output.info('─── ─────────────────────── ─────────── ────────────────');
|
|
64
|
+
for (const baseline of history) {
|
|
65
|
+
const date = formatDateLocale(baseline.uploadedAt);
|
|
66
|
+
const cliVersion = baseline.cliVersion.padEnd(11);
|
|
67
|
+
const hash = baseline.hash.slice(0, 16);
|
|
68
|
+
output.info(`${baseline.version.toString().padStart(3)} ` +
|
|
69
|
+
`${date.padEnd(23)} ` +
|
|
70
|
+
`${cliVersion} ` +
|
|
71
|
+
hash);
|
|
72
|
+
}
|
|
73
|
+
// Show diff summary if multiple versions
|
|
74
|
+
if (history.length >= 2) {
|
|
75
|
+
output.info('\nLatest changes:');
|
|
76
|
+
try {
|
|
77
|
+
const diff = await client.getLatestDiff(projectId);
|
|
78
|
+
if (diff) {
|
|
79
|
+
printDiffSummary(diff);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Diff failed, just skip
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
output.error('Failed to fetch history: ' + (error instanceof Error ? error.message : String(error)));
|
|
89
|
+
process.exit(EXIT_CODES.ERROR);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
/**
|
|
93
|
+
* Print a diff summary (compact format).
|
|
94
|
+
*/
|
|
95
|
+
function printDiffSummary(diff) {
|
|
96
|
+
const parts = [];
|
|
97
|
+
if (diff.toolsAdded > 0) {
|
|
98
|
+
parts.push(`+${diff.toolsAdded} tools`);
|
|
99
|
+
}
|
|
100
|
+
if (diff.toolsRemoved > 0) {
|
|
101
|
+
parts.push(`-${diff.toolsRemoved} tools`);
|
|
102
|
+
}
|
|
103
|
+
if (diff.toolsModified > 0) {
|
|
104
|
+
parts.push(`~${diff.toolsModified} modified`);
|
|
105
|
+
}
|
|
106
|
+
if (diff.behaviorChanges > 0) {
|
|
107
|
+
parts.push(`${diff.behaviorChanges} behavior changes`);
|
|
108
|
+
}
|
|
109
|
+
if (parts.length === 0) {
|
|
110
|
+
output.info(' No changes from previous version');
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
const icon = getSeverityIcon(diff.severity);
|
|
114
|
+
output.info(` ${icon} ${diff.severity}: ${parts.join(', ')}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Diff command for comparing specific versions.
|
|
119
|
+
*/
|
|
120
|
+
export const diffCommand = new Command('diff')
|
|
121
|
+
.description('Compare two baseline versions')
|
|
122
|
+
.argument('<from>', 'From version number')
|
|
123
|
+
.argument('<to>', 'To version number')
|
|
124
|
+
.option('-p, --project <id>', 'Project ID (uses linked project if not specified)')
|
|
125
|
+
.option('--json', 'Output as JSON')
|
|
126
|
+
.option('--session <session>', 'Session token (overrides stored/env session)')
|
|
127
|
+
.action(async (fromArg, toArg, options) => {
|
|
128
|
+
// Get session
|
|
129
|
+
const sessionToken = options.session ?? getSessionToken();
|
|
130
|
+
if (!sessionToken) {
|
|
131
|
+
output.error('Not authenticated. Run `bellwether login` first.');
|
|
132
|
+
process.exit(EXIT_CODES.ERROR);
|
|
133
|
+
}
|
|
134
|
+
// Determine project ID
|
|
135
|
+
let projectId = options.project;
|
|
136
|
+
if (!projectId) {
|
|
137
|
+
const link = getLinkedProject();
|
|
138
|
+
if (link) {
|
|
139
|
+
projectId = link.projectId;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (!projectId) {
|
|
143
|
+
output.error('No project specified. Use --project <id> or run `bellwether link`.');
|
|
144
|
+
process.exit(EXIT_CODES.ERROR);
|
|
145
|
+
}
|
|
146
|
+
// Parse versions
|
|
147
|
+
const fromVersion = parseInt(fromArg, 10);
|
|
148
|
+
const toVersion = parseInt(toArg, 10);
|
|
149
|
+
if (isNaN(fromVersion) || isNaN(toVersion)) {
|
|
150
|
+
output.error('Invalid version numbers. Provide integers (e.g., `bellwether diff 1 2`).');
|
|
151
|
+
process.exit(EXIT_CODES.ERROR);
|
|
152
|
+
}
|
|
153
|
+
// Create client and fetch diff
|
|
154
|
+
const client = createCloudClient({ sessionToken });
|
|
155
|
+
if (!client.isAuthenticated()) {
|
|
156
|
+
output.error('Authentication failed. Run `bellwether login` to re-authenticate.');
|
|
157
|
+
process.exit(EXIT_CODES.ERROR);
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const diff = await client.getDiff(projectId, fromVersion, toVersion);
|
|
161
|
+
if (options.json) {
|
|
162
|
+
output.info(JSON.stringify(diff, null, 2));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
output.info(`Comparing v${fromVersion} → v${toVersion}\n`);
|
|
166
|
+
const severityIcon = {
|
|
167
|
+
none: '✓',
|
|
168
|
+
info: 'ℹ',
|
|
169
|
+
warning: '⚠',
|
|
170
|
+
breaking: '✗',
|
|
171
|
+
};
|
|
172
|
+
output.info(`Severity: ${severityIcon[diff.severity] ?? '?'} ${diff.severity.toUpperCase()}`);
|
|
173
|
+
output.info('');
|
|
174
|
+
if (diff.toolsAdded > 0) {
|
|
175
|
+
output.info(`Tools added: +${diff.toolsAdded}`);
|
|
176
|
+
}
|
|
177
|
+
if (diff.toolsRemoved > 0) {
|
|
178
|
+
output.info(`Tools removed: -${diff.toolsRemoved}`);
|
|
179
|
+
}
|
|
180
|
+
if (diff.toolsModified > 0) {
|
|
181
|
+
output.info(`Tools modified: ~${diff.toolsModified}`);
|
|
182
|
+
}
|
|
183
|
+
if (diff.behaviorChanges > 0) {
|
|
184
|
+
output.info(`Behavior changes: ${diff.behaviorChanges}`);
|
|
185
|
+
}
|
|
186
|
+
if (diff.toolsAdded === 0 &&
|
|
187
|
+
diff.toolsRemoved === 0 &&
|
|
188
|
+
diff.toolsModified === 0 &&
|
|
189
|
+
diff.behaviorChanges === 0) {
|
|
190
|
+
output.info('No changes detected between these versions.');
|
|
191
|
+
}
|
|
192
|
+
if (diff.severity === 'breaking') {
|
|
193
|
+
output.info('\n⚠️ Breaking changes detected!');
|
|
194
|
+
output.info(' Tools were removed or modified in incompatible ways.');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
output.error('Failed to compute diff: ' + (error instanceof Error ? error.message : String(error)));
|
|
199
|
+
process.exit(EXIT_CODES.ERROR);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init command - creates a bellwether.yaml configuration file.
|
|
3
|
+
*
|
|
4
|
+
* The generated config includes ALL possible options with comments,
|
|
5
|
+
* making it self-documenting. Users can customize by editing the file.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
export declare const initCommand: Command;
|
|
9
|
+
//# sourceMappingURL=init.d.ts.map
|