evalshift 1.2.0__tar.gz → 1.2.2__tar.gz
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.
- {evalshift-1.2.0 → evalshift-1.2.2}/CHANGELOG.md +39 -1
- {evalshift-1.2.0 → evalshift-1.2.2}/PKG-INFO +13 -13
- {evalshift-1.2.0 → evalshift-1.2.2}/README.md +7 -7
- {evalshift-1.2.0 → evalshift-1.2.2}/docs/agents.md +2 -2
- evalshift-1.2.2/docs/changelog.md +6 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/docs/configuration.md +1 -1
- {evalshift-1.2.0 → evalshift-1.2.2}/docs/getting-started.md +1 -1
- {evalshift-1.2.0 → evalshift-1.2.2}/docs/github-action.md +4 -4
- {evalshift-1.2.0 → evalshift-1.2.2}/docs/hosted.md +12 -6
- {evalshift-1.2.0 → evalshift-1.2.2}/docs/sdk.md +5 -5
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/capture-first/README.md +1 -1
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/capture-first/evalshift.yaml +1 -1
- {evalshift-1.2.0 → evalshift-1.2.2}/pyproject.toml +7 -7
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/_scaffold.py +3 -3
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/init.py +1 -1
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/hosted/bundle_manifest.schema.json +2 -2
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/hosted/push.py +42 -1
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/hosted/trace_events.py +13 -1
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/reports/templates/report.css +33 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/reports/templates/report.html.j2 +5 -2
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/traces/models.py +7 -4
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/utils/ci_pin.py +20 -4
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/conftest.py +3 -3
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/test_validate_command.py +1 -1
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_ci_pin.py +32 -3
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_cli_capture.py +1 -1
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_docs_currency.py +28 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_doctor.py +1 -1
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_hosted_cli.py +135 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_init.py +3 -3
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_reports.py +15 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_suite_models.py +2 -2
- evalshift-1.2.0/docs/changelog.md +0 -6
- {evalshift-1.2.0 → evalshift-1.2.2}/.gitignore +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/CLAUDE.md +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/LICENSE +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/docs/conversations.md +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/docs/evaluators.md +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/docs/faq.md +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/docs/index.md +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/docs/methodology.md +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/docs/traces.md +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/agent/README.md +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/agent/evalshift.yaml +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/agent/golden.jsonl +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/agent/prompts.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/agent/tools.yaml +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/agent/toolsets/ad3f07832877239336d594c7c1626197b39b9eb4c178842304c8754e94323d67.json +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/agent-traces/evalshift.yaml +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/agent-traces/golden.jsonl +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/agent-traces/source-traces.jsonl +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/agent-traces/target-traces.jsonl +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/capture-first/.gitignore +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/capture-first/agent.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/simple/README.md +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/simple/evalshift.yaml +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/simple/golden.jsonl +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/examples/simple/prompts.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/scripts/smoke_live_tools.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/__main__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/analysis/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/analysis/policy.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/analysis/slicing.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/analysis/statistics.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cache/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cache/schema.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cache/store.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/captures/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/captures/models.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/captures/promote.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/captures/reader.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/captures/toolset.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/_agents.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/_suites.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/analyze.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/bundle.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/cache.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/capture.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/compare.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/debug_artifacts.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/diff.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/doctor.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/evaluate.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/inspect.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/login.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/logout.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/push.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/replay.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/report.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/run.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/runs.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/test_call.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/traces.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/validate.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/commands/whoami.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/cli/main.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/config/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/config/loader.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/config/models.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/evaluators/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/evaluators/agent_trace.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/evaluators/base.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/evaluators/failures.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/evaluators/llm_judge.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/evaluators/semantic.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/evaluators/structural.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/evaluators/tool_arguments.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/evaluators/tool_loader.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/evaluators/tool_models.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/evaluators/tool_parser.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/evaluators/tool_rounds.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/evaluators/tool_selection.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/evaluators/tool_trace_structure.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/hosted/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/hosted/bundle.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/hosted/client.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/hosted/credentials.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/insights/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/insights/facts.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/insights/generator.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/insights/models.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/insights/stage.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/insights/templates.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/models/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/models/capabilities.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/models/client.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/models/deepseek.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/models/family.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/models/registry.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/parsers/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/parsers/base.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/parsers/manual.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/parsers/python_string.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/py.typed +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/reports/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/reports/economics.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/reports/html.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/reports/json.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/runner/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/runner/checkpoint.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/runner/generation.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/runner/models.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/runner/orchestrator.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/suite/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/suite/loader.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/suite/models.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/suite/tags.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/traces/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/traces/diff.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/traces/loader.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/utils/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/utils/cost.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/src/evalshift_cli/utils/templating.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/fixtures/validate_missing_var/evalshift.yaml +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/fixtures/validate_missing_var/golden.jsonl +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/fixtures/validate_non_literal/evalshift.yaml +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/fixtures/validate_non_literal/golden.jsonl +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/fixtures/validate_non_literal/prompts.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/fixtures/validate_ok/evalshift.yaml +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/fixtures/validate_ok/golden.jsonl +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/fixtures/validate_ok/prompts.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/replay_client.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/test_agent_trace_pipeline.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/test_capture_to_verdict.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/test_conversation_pipeline.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/test_multi_round_replay.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/test_scoring_semantics.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/test_simple_example.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/integration/test_tool_pipeline.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/scoring_fixtures.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/__init__.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/conftest.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/fixtures/captures/multi_round_tools.json +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/fixtures/scoring/project_insights_pairs.jsonl +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/fixtures/tool_responses/anthropic/parallel_tool_calls.json +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/fixtures/tool_responses/anthropic/refusal.json +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/fixtures/tool_responses/anthropic/single_tool_call.json +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/fixtures/tool_responses/anthropic/text_only.json +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/fixtures/tool_responses/anthropic/tool_call_with_text.json +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/fixtures/tool_responses/deepseek/single_tool_call.json +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/fixtures/tool_responses/gemini/single_tool_call.json +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/fixtures/tool_responses/gemini/text_only.json +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/fixtures/tool_responses/openai/parallel_tool_calls.json +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/fixtures/tool_responses/openai/refusal.json +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/fixtures/tool_responses/openai/single_tool_call.json +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/fixtures/tool_responses/openai/text_only.json +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/insights_factories.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/suite_examples.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_agent_trace_evaluator.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_agents.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_analysis.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_analyze_command.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_bundle_shape.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_cache.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_captures_models.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_captures_promote.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_captures_reader.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_captures_toolset.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_checkpoint.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_cli_runs_clean.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_cli_smoke.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_compare_alias.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_compare_command.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_compare_suite_name.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_config_loader.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_config_models.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_config_suites.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_cost.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_debug_commands.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_deepseek.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_deferred_warnings.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_dropped_params.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_economics.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_evaluate_command.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_evaluators.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_failures.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_hosted_client.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_insight_facts.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_insight_generator.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_insights_stage.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_litellm_log_dedupe.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_litellm_log_stream.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_model_capabilities.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_model_client.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_model_family.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_model_registry.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_orchestrator.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_parsers.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_policy.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_push_server_id.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_push_validation.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_reserved_slice_name.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_run_command.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_run_determinism.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_run_retention.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_run_suite_name.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_runner_generation.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_runner_models.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_suite_loader.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_suites_helpers.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_templating.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_test_call_command.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_tool_arguments.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_tool_loader.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_tool_models.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_tool_parser.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_tool_rounds.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_tool_selection.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_tool_trace_structure.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_trace_diff.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_trace_events.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_trace_import_command.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_trace_models.py +0 -0
- {evalshift-1.2.0 → evalshift-1.2.2}/tests/unit/test_version.py +0 -0
|
@@ -7,6 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.2] - 2026-10-05
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- `evalshift push` printed a traceback instead of the upgrade prompt when hosted EvalShift
|
|
15
|
+
refused the run with HTTP 402 right after auto-creating the project (over the monthly run
|
|
16
|
+
or concurrent-run limit, or a subscription that stopped paying). It now prints the
|
|
17
|
+
server's message and the billing link and exits 1. A 402 while auto-creating the
|
|
18
|
+
project — what an organization whose trial has ended gets — now shows the same prompt
|
|
19
|
+
instead of an access-permission hint.
|
|
20
|
+
- `evalshift push` printed a traceback when the connection failed while auto-creating the
|
|
21
|
+
project; it now prints the error and exits 1.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- The EvalShift repositories moved from the `babaliauskas` GitHub account to
|
|
26
|
+
the `evalshift` organization: <https://github.com/evalshift/evalshift-cli>,
|
|
27
|
+
`evalshift-sdk` and `evalshift-action`. Links, package metadata and the
|
|
28
|
+
workflow `evalshift init --ci` scaffolds now use
|
|
29
|
+
`evalshift/evalshift-action@v0`. GitHub redirects the old names, so existing
|
|
30
|
+
workflows keep working.
|
|
31
|
+
- The CI pin check (`init`, `validate`, `doctor`, `capture sync`) recognizes
|
|
32
|
+
`evalshift/evalshift-action` steps as well as the old
|
|
33
|
+
`babaliauskas/evalshift-action` name, matching owner and repository names
|
|
34
|
+
case-insensitively as GitHub does.
|
|
35
|
+
- Docs and the `init --ci` workflow comment describe hosted EvalShift's current plans: a
|
|
36
|
+
30-day Pro trial for every new account, then Pro per organization (the hosted Free and
|
|
37
|
+
Team plans are gone). Local runs stay free and unlimited.
|
|
38
|
+
|
|
39
|
+
## [1.2.1] - 2026-10-01
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- Top regressions in the HTML report now read as expandable: the worst one
|
|
44
|
+
in each section starts open, each row has a boxed caret and a
|
|
45
|
+
"Show details" / "Hide details" hint, and the header reacts on hover and
|
|
46
|
+
shows a keyboard focus ring. Printing expands every regression in
|
|
47
|
+
Chromium-based browsers.
|
|
48
|
+
|
|
10
49
|
## [1.2.0] - 2026-10-01
|
|
11
50
|
|
|
12
51
|
Shipped as a minor again. The `slices` removal below is breaking by the
|
|
@@ -606,7 +645,6 @@ the key and the fix, which is the whole migration.
|
|
|
606
645
|
that imported CLI internals (`from evalshift.models.client import …`) must
|
|
607
646
|
import from `evalshift_cli`. No shim is possible — shipping any `evalshift/`
|
|
608
647
|
file would recreate the collision — so this ships as a minor bump (0.14.0).
|
|
609
|
-
Design: `docs/superpowers/specs/2026-09-09-namespace-collision-design.md`.
|
|
610
648
|
- Docs: the capture guides (`README.md`, `DOCS.md`, `docs/sdk.md`,
|
|
611
649
|
`docs/getting-started.md`, `llms-full.txt`) now cover the SDK 0.4.0 provider
|
|
612
650
|
client wrappers (`wrap_openai` / `wrap_anthropic` / `wrap_genai`), the
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: evalshift
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
4
4
|
Summary: Run your prompts on two LLMs and find out, with statistical confidence, what regressed.
|
|
5
|
-
Project-URL: Homepage, https://github.com/
|
|
6
|
-
Project-URL: Documentation, https://github.com/
|
|
7
|
-
Project-URL: Repository, https://github.com/
|
|
8
|
-
Project-URL: Issues, https://github.com/
|
|
9
|
-
Project-URL: Changelog, https://github.com/
|
|
5
|
+
Project-URL: Homepage, https://github.com/evalshift/evalshift-cli
|
|
6
|
+
Project-URL: Documentation, https://github.com/evalshift/evalshift-cli#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/evalshift/evalshift-cli
|
|
8
|
+
Project-URL: Issues, https://github.com/evalshift/evalshift-cli/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/evalshift/evalshift-cli/blob/main/CHANGELOG.md
|
|
10
10
|
Author: Lukas Babaliauskas
|
|
11
11
|
License-Expression: Apache-2.0
|
|
12
12
|
License-File: LICENSE
|
|
@@ -57,7 +57,7 @@ Description-Content-Type: text/markdown
|
|
|
57
57
|
|
|
58
58
|
Open-source LLM migration and regression testing for AI agents.
|
|
59
59
|
|
|
60
|
-
[](https://github.com/evalshift/evalshift-cli/actions/workflows/ci.yml)
|
|
61
61
|
[](LICENSE)
|
|
62
62
|
[](https://www.python.org/downloads/)
|
|
63
63
|
[](https://pypi.org/project/evalshift/)
|
|
@@ -82,7 +82,7 @@ statistics**: paired tests, Cohen's d, 95% CIs, and Benjamini-Hochberg
|
|
|
82
82
|
correction across every (prompt x evaluator x slice) comparison.
|
|
83
83
|
|
|
84
84
|
An eval is only worth the examples in it. That is why the
|
|
85
|
-
[capture SDK](https://github.com/
|
|
85
|
+
[capture SDK](https://github.com/evalshift/evalshift-sdk) is part of the
|
|
86
86
|
product rather than an add-on: it records real production runs — model calls,
|
|
87
87
|
tool calls, final outputs — to disk, and `evalshift capture sync` promotes them
|
|
88
88
|
into golden suites. Hand-written suites are fully supported too, but captured
|
|
@@ -99,7 +99,7 @@ Four pieces, released and documented independently:
|
|
|
99
99
|
| --- | --- | --- |
|
|
100
100
|
| **SDK** — PyPI `evalshift-sdk` | Records what your agent actually did in production — model calls, tool calls, final output — as capture files on disk. Those captures become your golden suite. | [docs/sdk.md](docs/sdk.md) |
|
|
101
101
|
| **CLI** — this repo, PyPI `evalshift` | Replays the suite on two models, scores, analyses, reports, bundles, pushes. | [DOCS.md](DOCS.md) |
|
|
102
|
-
| **GitHub Action** — `
|
|
102
|
+
| **GitHub Action** — `evalshift/evalshift-action@v0` | Runs the pipeline on pull requests, pushes the run, posts one PR comment, sets the `evalshift/regression` status. | [docs/github-action.md](docs/github-action.md) |
|
|
103
103
|
| **Hosted server** — `api.evalshift.dev`, web app at `evalshift.dev` | Optional. Stores pushed run bundles, diffs them across branches, drives PR comments and gating. | [docs/hosted.md](docs/hosted.md) |
|
|
104
104
|
|
|
105
105
|
The SDK and the CLI never call each other — the interface is files under
|
|
@@ -150,7 +150,7 @@ uv pip install evalshift-sdk # or: pip install evalshift-sdk
|
|
|
150
150
|
From source (for contributors):
|
|
151
151
|
|
|
152
152
|
```bash
|
|
153
|
-
git clone https://github.com/
|
|
153
|
+
git clone https://github.com/evalshift/evalshift-cli.git
|
|
154
154
|
cd evalshift-cli
|
|
155
155
|
uv venv --python 3.11
|
|
156
156
|
source .venv/bin/activate
|
|
@@ -168,7 +168,7 @@ candidate model to it.
|
|
|
168
168
|
evalshift init # minimal capture-first evalshift.yaml
|
|
169
169
|
```
|
|
170
170
|
|
|
171
|
-
Instrument the agent with [evalshift-sdk](https://github.com/
|
|
171
|
+
Instrument the agent with [evalshift-sdk](https://github.com/evalshift/evalshift-sdk)
|
|
172
172
|
— stdlib-only, Python 3.10+, installed with the CLI or on its own:
|
|
173
173
|
|
|
174
174
|
```python
|
|
@@ -297,7 +297,7 @@ The short version:
|
|
|
297
297
|
|
|
298
298
|
`evalshift init --ci` scaffolds a production-shaped workflow: it discovers
|
|
299
299
|
every committed suite under `.evalshift/suites/`, evaluates each on every
|
|
300
|
-
pull request via [`
|
|
300
|
+
pull request via [`evalshift/evalshift-action@v0`](https://github.com/evalshift/evalshift-action)
|
|
301
301
|
(one matrix job per suite), pushes the runs to hosted EvalShift, compares
|
|
302
302
|
against the latest compatible base-branch run, posts one PR comment, and
|
|
303
303
|
gates merges on your `migration_policy` through a single required
|
|
@@ -423,7 +423,7 @@ Runnable projects under [`examples/`](examples/):
|
|
|
423
423
|
|
|
424
424
|
[Apache-2.0](LICENSE). Free for any use, commercial included — no share-back
|
|
425
425
|
requirement, and an explicit patent grant. The capture SDK
|
|
426
|
-
([`evalshift-sdk`](https://github.com/
|
|
426
|
+
([`evalshift-sdk`](https://github.com/evalshift/evalshift-sdk)), the piece
|
|
427
427
|
you import into your own application, is MIT.
|
|
428
428
|
|
|
429
429
|
Earlier releases stay under the license they shipped with: `0.3.0` and earlier
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Open-source LLM migration and regression testing for AI agents.
|
|
4
4
|
|
|
5
|
-
[](https://github.com/evalshift/evalshift-cli/actions/workflows/ci.yml)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://www.python.org/downloads/)
|
|
8
8
|
[](https://pypi.org/project/evalshift/)
|
|
@@ -27,7 +27,7 @@ statistics**: paired tests, Cohen's d, 95% CIs, and Benjamini-Hochberg
|
|
|
27
27
|
correction across every (prompt x evaluator x slice) comparison.
|
|
28
28
|
|
|
29
29
|
An eval is only worth the examples in it. That is why the
|
|
30
|
-
[capture SDK](https://github.com/
|
|
30
|
+
[capture SDK](https://github.com/evalshift/evalshift-sdk) is part of the
|
|
31
31
|
product rather than an add-on: it records real production runs — model calls,
|
|
32
32
|
tool calls, final outputs — to disk, and `evalshift capture sync` promotes them
|
|
33
33
|
into golden suites. Hand-written suites are fully supported too, but captured
|
|
@@ -44,7 +44,7 @@ Four pieces, released and documented independently:
|
|
|
44
44
|
| --- | --- | --- |
|
|
45
45
|
| **SDK** — PyPI `evalshift-sdk` | Records what your agent actually did in production — model calls, tool calls, final output — as capture files on disk. Those captures become your golden suite. | [docs/sdk.md](docs/sdk.md) |
|
|
46
46
|
| **CLI** — this repo, PyPI `evalshift` | Replays the suite on two models, scores, analyses, reports, bundles, pushes. | [DOCS.md](DOCS.md) |
|
|
47
|
-
| **GitHub Action** — `
|
|
47
|
+
| **GitHub Action** — `evalshift/evalshift-action@v0` | Runs the pipeline on pull requests, pushes the run, posts one PR comment, sets the `evalshift/regression` status. | [docs/github-action.md](docs/github-action.md) |
|
|
48
48
|
| **Hosted server** — `api.evalshift.dev`, web app at `evalshift.dev` | Optional. Stores pushed run bundles, diffs them across branches, drives PR comments and gating. | [docs/hosted.md](docs/hosted.md) |
|
|
49
49
|
|
|
50
50
|
The SDK and the CLI never call each other — the interface is files under
|
|
@@ -95,7 +95,7 @@ uv pip install evalshift-sdk # or: pip install evalshift-sdk
|
|
|
95
95
|
From source (for contributors):
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
|
-
git clone https://github.com/
|
|
98
|
+
git clone https://github.com/evalshift/evalshift-cli.git
|
|
99
99
|
cd evalshift-cli
|
|
100
100
|
uv venv --python 3.11
|
|
101
101
|
source .venv/bin/activate
|
|
@@ -113,7 +113,7 @@ candidate model to it.
|
|
|
113
113
|
evalshift init # minimal capture-first evalshift.yaml
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
Instrument the agent with [evalshift-sdk](https://github.com/
|
|
116
|
+
Instrument the agent with [evalshift-sdk](https://github.com/evalshift/evalshift-sdk)
|
|
117
117
|
— stdlib-only, Python 3.10+, installed with the CLI or on its own:
|
|
118
118
|
|
|
119
119
|
```python
|
|
@@ -242,7 +242,7 @@ The short version:
|
|
|
242
242
|
|
|
243
243
|
`evalshift init --ci` scaffolds a production-shaped workflow: it discovers
|
|
244
244
|
every committed suite under `.evalshift/suites/`, evaluates each on every
|
|
245
|
-
pull request via [`
|
|
245
|
+
pull request via [`evalshift/evalshift-action@v0`](https://github.com/evalshift/evalshift-action)
|
|
246
246
|
(one matrix job per suite), pushes the runs to hosted EvalShift, compares
|
|
247
247
|
against the latest compatible base-branch run, posts one PR comment, and
|
|
248
248
|
gates merges on your `migration_policy` through a single required
|
|
@@ -368,7 +368,7 @@ Runnable projects under [`examples/`](examples/):
|
|
|
368
368
|
|
|
369
369
|
[Apache-2.0](LICENSE). Free for any use, commercial included — no share-back
|
|
370
370
|
requirement, and an explicit patent grant. The capture SDK
|
|
371
|
-
([`evalshift-sdk`](https://github.com/
|
|
371
|
+
([`evalshift-sdk`](https://github.com/evalshift/evalshift-sdk)), the piece
|
|
372
372
|
you import into your own application, is MIT.
|
|
373
373
|
|
|
374
374
|
Earlier releases stay under the license they shipped with: `0.3.0` and earlier
|
|
@@ -60,7 +60,7 @@ parsed `ToolTrace`, and the configured `tool_*` evaluators score against the
|
|
|
60
60
|
per-example `expected_tools` ground truth.
|
|
61
61
|
|
|
62
62
|
For your own agent, the recommended path is capture-first: instrument it
|
|
63
|
-
with the [evalshift-sdk](https://github.com/
|
|
63
|
+
with the [evalshift-sdk](https://github.com/evalshift/evalshift-sdk),
|
|
64
64
|
exercise it to record captures, then `evalshift capture sync` promotes them
|
|
65
65
|
into a golden suite carrying the toolset your production agent actually
|
|
66
66
|
offered — see [Getting started](getting-started.md).
|
|
@@ -135,7 +135,7 @@ Nothing in `evalshift.yaml` wires a toolset to a prompt — dispatch reads it
|
|
|
135
135
|
off each golden-suite *example* instead (`toolset_ref` or inline `tools`, see
|
|
136
136
|
[Suite ground truth](#suite-ground-truth) below), so the same prompt can
|
|
137
137
|
legitimately dispatch some examples with tools and others without, in one
|
|
138
|
-
run. [`examples/agent/tools.yaml`](https://github.com/
|
|
138
|
+
run. [`examples/agent/tools.yaml`](https://github.com/evalshift/evalshift-cli/blob/main/examples/agent/tools.yaml) is just this project's human-readable record of what
|
|
139
139
|
those tools are; it accepts either Anthropic-shape (`name` / `description` /
|
|
140
140
|
`input_schema`) or OpenAI-shape (`{ "type": "function", "function": {...}
|
|
141
141
|
}`) entries — `evalshift run` serialises whatever a toolset resolves to in
|
|
@@ -366,7 +366,7 @@ A list of prompt definitions. Each entry has:
|
|
|
366
366
|
| `judge_model` | string | `gemini-3.1-flash-lite-preview` | Default LLM-as-judge model. |
|
|
367
367
|
| `insights_model`| string | (none) | Model that writes the run-insights narrative rendered in `report.html` and uploaded with the bundle. Falls back to `judge_model` when unset — writing analytical prose is a harder task than a pairwise A/B verdict, so it is worth tuning separately. See [Run insights](#run-insights). |
|
|
368
368
|
| `concurrency` | int | 10 (1 ≤ x ≤ 64) | Max in-flight LLM calls during `evalshift run` **and** `evalshift evaluate` (the embedding and judge calls made while scoring). |
|
|
369
|
-
| `cache` | bool | `true` | Read/write the local SQLite cache at `~/.evalshift/cache.db`. Covers run-stage completions — for examples that offer tools, one entry per replayed round — plus `semantic` embeddings and `llm_judge` verdicts. See [Response cache](https://github.com/
|
|
369
|
+
| `cache` | bool | `true` | Read/write the local SQLite cache at `~/.evalshift/cache.db`. Covers run-stage completions — for examples that offer tools, one entry per replayed round — plus `semantic` embeddings and `llm_judge` verdicts. See [Response cache](https://github.com/evalshift/evalshift-cli/blob/main/DOCS.md#response-cache) for the key. |
|
|
370
370
|
| `max_cost_usd` | float | 50.0 | Soft ceiling reserved for future enforcement. The pre-flight cost prompt currently triggers above $10 (skip with `--yes`). |
|
|
371
371
|
| `max_tokens` | int | 4096 (`> 0`) | Completion length cap sent to every model call. Raise it if outputs are being truncated (the provider returns `finish_reason == "length"`); a `prompts[].max_tokens` entry overrides it per prompt. Truncated calls are detected, surfaced in the report, and **excluded from the regression statistics** so a cut-off output can't manufacture a false regression. |
|
|
372
372
|
| `samples_per_example` | int | 1 (1 ≤ x ≤ 20) | How many times each `(prompt, example)` is sent to **each** model. Above 1, every sample is its own live call (the cache keys on the sample index), sample *i* of the source is scored against sample *i* of the target, and the example's row in `scores.jsonl` becomes the **mean over samples** with the per-sample scores and the within-example `delta_variance` under `metadata.samples`. The paired tests still run over examples, not samples, so this reduces noise without inflating `n`. Cost and the call count multiply by it; only worth turning on for a model that samples non-deterministically (see the report banner). See [Methodology](methodology.md#limitations-to-be-aware-of). |
|
|
@@ -30,8 +30,8 @@ checklist) with three jobs:
|
|
|
30
30
|
to the CLI that scaffolded the project: the CLI that *reads* the config in CI must be at least as new
|
|
31
31
|
as the CLI that *wrote* it locally (`extra: forbid` rejects newer keys), and
|
|
32
32
|
the CLI warns when the pin falls behind (or runs ahead of the local CLI) — see [Pin drift](#pin-drift). `max-parallel` defaults to 1 —
|
|
33
|
-
raise it toward your hosted plan's
|
|
34
|
-
|
|
33
|
+
raise it toward your hosted plan's concurrent-run limit (Pro: 5;
|
|
34
|
+
Enterprise: by contract). The PR comment is posted by the first matrix job only: the
|
|
35
35
|
comment marker is a constant, so multiple suites would overwrite one
|
|
36
36
|
another's summary.
|
|
37
37
|
- **`evalshift gate`** — the join job to require in branch protection. It
|
|
@@ -182,12 +182,12 @@ The CLI checks for it wherever it writes or validates config — `capture sync`,
|
|
|
182
182
|
`init` (without `--ci`, next to a workflow it did not write; `init --ci` pins
|
|
183
183
|
the scaffolding CLI itself and does not warn about the file it just wrote),
|
|
184
184
|
`doctor` (a `ci pin` row), and `validate`. It parses every `.github/workflows/*.yml` for
|
|
185
|
-
`
|
|
185
|
+
`evalshift/evalshift-action` steps and compares their `evalshift-version`
|
|
186
186
|
with its own:
|
|
187
187
|
|
|
188
188
|
```text
|
|
189
189
|
⚠ CI installs evalshift 0.12.1 (.github/workflows/evalshift.yml, job evalshift) but the local CLI is 0.13.1 — an older CLI rejects config keys a newer one writes.
|
|
190
|
-
Fix: set `evalshift-version: "0.13.1"` on the
|
|
190
|
+
Fix: set `evalshift-version: "0.13.1"` on the evalshift/evalshift-action step.
|
|
191
191
|
```
|
|
192
192
|
|
|
193
193
|
| Status | Trigger | Fix |
|
|
@@ -237,20 +237,26 @@ the wrong one fails exactly like a permissions problem.
|
|
|
237
237
|
|
|
238
238
|
Running locally is always unlimited. Plan limits apply only to what you push.
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
240
|
+
Every new hosted account gets a 30-day Pro trial (no card) that covers every
|
|
241
|
+
org it creates; after that each org needs its own Pro subscription.
|
|
242
|
+
|
|
243
|
+
When a push exceeds your organization's plan — monthly runs, seats, concurrent
|
|
244
|
+
runs — the org's trial ended without a subscription, or the subscription has
|
|
245
|
+
stopped paying, the server refuses the push with HTTP 402 and the CLI prints
|
|
246
|
+
exactly what the server said:
|
|
243
247
|
|
|
244
248
|
```
|
|
245
249
|
✗ EvalShift: this run needs a paid plan.
|
|
246
|
-
|
|
250
|
+
The free trial for this organization ended on 2026-11-02. Subscribe to Pro to push runs and keep the CI gate — existing runs stay readable.
|
|
247
251
|
Upgrade: https://app.evalshift.dev/app/acme/settings/billing
|
|
248
252
|
```
|
|
249
253
|
|
|
250
254
|
`push` exits 1 and nothing is uploaded. The CLI does not evaluate entitlements
|
|
251
255
|
itself and does not retry a payment error — retrying cannot change the answer.
|
|
252
256
|
Your run and its `report.html` stay on disk under `.evalshift/runs/`, so you can
|
|
253
|
-
push the same run id again
|
|
257
|
+
push the same run id again once the org is subscribed (or, for the monthly
|
|
258
|
+
quota, after the counter resets). An expired org stays readable — its runs,
|
|
259
|
+
diffs and baselines still load.
|
|
254
260
|
|
|
255
261
|
Transient upload failures (HTTP 429 and 5xx from object storage) are a separate
|
|
256
262
|
case and *are* retried with exponential backoff.
|
|
@@ -355,5 +361,5 @@ credential file locally and repository secrets in CI.
|
|
|
355
361
|
| `could not determine a valid 40-character git SHA` | `bundle` (or `push <run-id>` building a bundle) ran outside a git checkout, or in one with no commits, and `GITHUB_SHA` is unset. | Run inside a git repository with at least one commit, or set `GITHUB_SHA`. |
|
|
356
362
|
| `project was not found` | The project does not exist and auto-create is disabled or not allowed. | Ask an owner to create it, use an org-scoped owner token, or enable auto-create. |
|
|
357
363
|
| `cannot auto-create <slug> at <host>` | The message names the host it talked to and the server's status. Most often the host is not the one you meant: with no `--host` and no `EVALSHIFT_HOST`, an unset credentials file falls back to `https://api.evalshift.dev`, where your org does not exist. | Run `evalshift whoami` and check the host it prints. If it is wrong, `evalshift login --host <hosted-api-url>`. If the host is right and the status is 403, the token lacks org access — see [Project auto-create](#project-auto-create). |
|
|
358
|
-
| `this run needs a paid plan` | The org's plan does not cover this push, or the subscription has stopped paying. | Open the upgrade URL printed with the message
|
|
364
|
+
| `this run needs a paid plan` | The org's plan does not cover this push, its 30-day trial ended without a subscription, or the subscription has stopped paying. | Open the upgrade URL printed with the message; for the monthly quota you can also wait for the reset and push the same run id again. See [Plan limits](#plan-limits). |
|
|
359
365
|
| `this run carries no migration policy` warning | No `migration_policy` is configured in `evalshift.yaml`, so the bundle has no `decision.policy`. | Add `migration_policy` to `evalshift.yaml` (see [Configuration](configuration.md#migration_policy)). Until then the gate has only whatever old web-app policy the project still has; with none, it reports `inconclusive` and never blocks the pull request unless the GitHub Action sets `require-policy: true`. |
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Capture SDK
|
|
2
2
|
|
|
3
|
-
The [evalshift-sdk](https://github.com/
|
|
3
|
+
The [evalshift-sdk](https://github.com/evalshift/evalshift-sdk) is a separate
|
|
4
4
|
package (`pip install evalshift-sdk`, import name `evalshift`) that you install
|
|
5
5
|
**inside your agent process**. It records what your agent actually did — model
|
|
6
6
|
calls, tool calls, the final output — as JSON capture files on disk. The CLI
|
|
7
7
|
promotes those captures into golden suites.
|
|
8
8
|
|
|
9
9
|
This page covers the CLI side of that contract. The full SDK guide lives in the
|
|
10
|
-
SDK repo: [DOCS.md](https://github.com/
|
|
10
|
+
SDK repo: [DOCS.md](https://github.com/evalshift/evalshift-sdk/blob/main/DOCS.md),
|
|
11
11
|
dense LLM reference at <https://www.evalshift.dev/sdk-llms-full.txt>.
|
|
12
12
|
|
|
13
13
|
## The contract
|
|
@@ -61,7 +61,7 @@ to state its masking policy in the call itself. `True` applies the SDK's
|
|
|
61
61
|
`default_redactor` (emails, `sk-…`, `AKIA…`, `Bearer …`), `False` records
|
|
62
62
|
verbatim, and a `(value) -> value` callable does something custom; any other
|
|
63
63
|
value — `None` included — raises `TypeError`. Details:
|
|
64
|
-
[REDACTION.md](https://github.com/
|
|
64
|
+
[REDACTION.md](https://github.com/evalshift/evalshift-sdk/blob/main/docs/REDACTION.md).
|
|
65
65
|
|
|
66
66
|
`tools` is required on the same entry points: the toolset the agent was offered,
|
|
67
67
|
or `[]` if it never calls tools. Omitting it is a `TypeError` too.
|
|
@@ -131,7 +131,7 @@ carries the recorded tool results so `run` replays later rounds teacher-forced
|
|
|
131
131
|
`--keep-duplicates` disables dedup.
|
|
132
132
|
|
|
133
133
|
Full behaviour: [Configuration](configuration.md) and the
|
|
134
|
-
[Capturing from production](https://github.com/
|
|
134
|
+
[Capturing from production](https://github.com/evalshift/evalshift-cli/blob/main/DOCS.md#capturing-from-production)
|
|
135
135
|
section of DOCS.md.
|
|
136
136
|
|
|
137
137
|
## 4. Evaluate a candidate against real behaviour
|
|
@@ -145,7 +145,7 @@ from production traffic rather than hand-written examples.
|
|
|
145
145
|
|
|
146
146
|
## Related
|
|
147
147
|
|
|
148
|
-
- [`examples/capture-first/`](https://github.com/
|
|
148
|
+
- [`examples/capture-first/`](https://github.com/evalshift/evalshift-cli/tree/main/examples/capture-first)
|
|
149
149
|
— every step above checked in: the instrumented agent, the captures and
|
|
150
150
|
toolset sidecar it wrote, the promoted suite, and the `evalshift.yaml` whose
|
|
151
151
|
managed `suites:` block `capture sync` filled in.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Capture-first example
|
|
2
2
|
|
|
3
3
|
The flow the README and `evalshift init` recommend, checked in end to end:
|
|
4
|
-
instrument an agent with the [evalshift-sdk](https://github.com/
|
|
4
|
+
instrument an agent with the [evalshift-sdk](https://github.com/evalshift/evalshift-sdk),
|
|
5
5
|
exercise it, then let `evalshift capture sync` turn what it recorded into a
|
|
6
6
|
golden suite and wire that suite into `evalshift.yaml`.
|
|
7
7
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# migration_profile: model-upgrade
|
|
2
|
-
# EvalShift configuration. See https://github.com/
|
|
2
|
+
# EvalShift configuration. See https://github.com/evalshift/evalshift-cli for docs.
|
|
3
3
|
#
|
|
4
4
|
# `init` writes only this file, set up for the capture-first flow: instrument
|
|
5
5
|
# your agent with the evalshift-sdk, exercise it to record captures, then run
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "evalshift"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.2"
|
|
8
8
|
description = "Run your prompts on two LLMs and find out, with statistical confidence, what regressed."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -30,7 +30,7 @@ dependencies = [
|
|
|
30
30
|
# The capture SDK (import name `evalshift`). Stdlib-only, so it adds nothing to
|
|
31
31
|
# the environment; declared so one `pip install evalshift` serves both
|
|
32
32
|
# instrumenting an agent and running evaluations, and so `import evalshift` is
|
|
33
|
-
# always the SDK. See
|
|
33
|
+
# always the SDK. See the co-install note in DOCS.md.
|
|
34
34
|
"evalshift-sdk>=0.4.0",
|
|
35
35
|
"typer>=0.12",
|
|
36
36
|
"pydantic>=2.5",
|
|
@@ -68,11 +68,11 @@ dev = [
|
|
|
68
68
|
]
|
|
69
69
|
|
|
70
70
|
[project.urls]
|
|
71
|
-
Homepage = "https://github.com/
|
|
72
|
-
Documentation = "https://github.com/
|
|
73
|
-
Repository = "https://github.com/
|
|
74
|
-
Issues = "https://github.com/
|
|
75
|
-
Changelog = "https://github.com/
|
|
71
|
+
Homepage = "https://github.com/evalshift/evalshift-cli"
|
|
72
|
+
Documentation = "https://github.com/evalshift/evalshift-cli#readme"
|
|
73
|
+
Repository = "https://github.com/evalshift/evalshift-cli"
|
|
74
|
+
Issues = "https://github.com/evalshift/evalshift-cli/issues"
|
|
75
|
+
Changelog = "https://github.com/evalshift/evalshift-cli/blob/main/CHANGELOG.md"
|
|
76
76
|
|
|
77
77
|
[project.scripts]
|
|
78
78
|
evalshift = "evalshift_cli.cli.main:app"
|
|
@@ -231,8 +231,8 @@ jobs:
|
|
|
231
231
|
strategy:
|
|
232
232
|
# One suite regressing must not cancel the others' results.
|
|
233
233
|
fail-fast: false
|
|
234
|
-
# Hosted EvalShift refuses uploads past your org
|
|
235
|
-
#
|
|
234
|
+
# Hosted EvalShift refuses uploads past your org's concurrent-run
|
|
235
|
+
# limit (Pro: 5; Enterprise: by contract). Raise this toward it
|
|
236
236
|
# to evaluate suites in parallel.
|
|
237
237
|
max-parallel: 1
|
|
238
238
|
matrix:
|
|
@@ -251,7 +251,7 @@ jobs:
|
|
|
251
251
|
|
|
252
252
|
- name: Run evalshift on ${{ matrix.suite }}
|
|
253
253
|
if: ${{ env.HAS_EVALSHIFT_TOKEN == 'true' }}
|
|
254
|
-
uses:
|
|
254
|
+
uses: evalshift/evalshift-action@v0
|
|
255
255
|
with:
|
|
256
256
|
token: ${{ secrets.EVALSHIFT_TOKEN }}
|
|
257
257
|
config: evalshift.yaml
|
|
@@ -98,7 +98,7 @@ _SEMANTIC_BLOCK_DISABLED: Final = """\
|
|
|
98
98
|
# Body of the minimal config, up to (but excluding) the suites region and the
|
|
99
99
|
# migration_policy block. ``render_minimal_config`` appends those.
|
|
100
100
|
_MINIMAL_YAML_BODY: Final = """\
|
|
101
|
-
# EvalShift configuration. See https://github.com/
|
|
101
|
+
# EvalShift configuration. See https://github.com/evalshift/evalshift-cli for docs.
|
|
102
102
|
#
|
|
103
103
|
# `init` writes only this file, set up for the capture-first flow: instrument
|
|
104
104
|
# your agent with the evalshift-sdk, exercise it to record captures, then run
|
|
@@ -1018,7 +1018,7 @@
|
|
|
1018
1018
|
},
|
|
1019
1019
|
"GuardrailEvent": {
|
|
1020
1020
|
"additionalProperties": false,
|
|
1021
|
-
"description": "A guardrail or policy check
|
|
1021
|
+
"description": "A guardrail or policy check.\n\nReserved for a bring-your-own-agent trace; no current producer emits it \u2014\n``evalshift traces import`` writes such traces only to a local artifact,\nnot into a pushed bundle.",
|
|
1022
1022
|
"properties": {
|
|
1023
1023
|
"name": {
|
|
1024
1024
|
"minLength": 1,
|
|
@@ -1405,7 +1405,7 @@
|
|
|
1405
1405
|
},
|
|
1406
1406
|
"RetrievalEvent": {
|
|
1407
1407
|
"additionalProperties": false,
|
|
1408
|
-
"description": "A retrieval step
|
|
1408
|
+
"description": "A retrieval step.\n\nReserved for a bring-your-own-agent trace; no current producer emits it \u2014\n``evalshift traces import`` writes such traces only to a local artifact,\nnot into a pushed bundle.",
|
|
1409
1409
|
"properties": {
|
|
1410
1410
|
"documents": {
|
|
1411
1411
|
"items": {
|
|
@@ -108,6 +108,39 @@ def _upgrade_prompt(exc: HostedHTTPError) -> PushError:
|
|
|
108
108
|
return PushError("\n".join(lines))
|
|
109
109
|
|
|
110
110
|
|
|
111
|
+
def _initiate_after_create(
|
|
112
|
+
client: HostedClient, manifest: dict[str, Any], *, size_bytes: int
|
|
113
|
+
) -> dict[str, Any]:
|
|
114
|
+
"""The ``POST /runs`` retried after auto-creating the project, mapped like the first.
|
|
115
|
+
|
|
116
|
+
It runs inside the first attempt's ``except`` clause, so its own errors would skip the
|
|
117
|
+
sibling handlers and surface as a traceback. ``POST /runs`` answers 404 for a missing
|
|
118
|
+
project before any entitlement check, so a 402 for the monthly run or concurrent-run
|
|
119
|
+
limit, seat overage, or a subscription that stopped paying arrives on this retry, not
|
|
120
|
+
on the first call.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
client: The hosted client the first attempt used.
|
|
124
|
+
manifest: The bundle manifest sent as the run's create payload.
|
|
125
|
+
size_bytes: Size of the bundle file that will be uploaded.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
The server's ``POST /runs`` response.
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
PushError: On any hosted error — the upgrade prompt for a 402, the server's
|
|
132
|
+
message otherwise.
|
|
133
|
+
"""
|
|
134
|
+
try:
|
|
135
|
+
return client.initiate_run(manifest, size_bytes=size_bytes)
|
|
136
|
+
except HostedHTTPError as exc:
|
|
137
|
+
if exc.status_code == _PAYMENT_REQUIRED:
|
|
138
|
+
raise _upgrade_prompt(exc) from exc
|
|
139
|
+
raise PushError(str(exc)) from exc
|
|
140
|
+
except (HostedNetworkError, HostedError) as exc:
|
|
141
|
+
raise PushError(str(exc)) from exc
|
|
142
|
+
|
|
143
|
+
|
|
111
144
|
@dataclass(frozen=True, slots=True)
|
|
112
145
|
class PushResult:
|
|
113
146
|
"""Outcome of a hosted push.
|
|
@@ -239,7 +272,7 @@ def push_bundle(
|
|
|
239
272
|
raise PushError(str(exc)) from exc
|
|
240
273
|
_auto_create_project(client, project_slug=str(manifest["project_slug"]))
|
|
241
274
|
project_created = True
|
|
242
|
-
response = client
|
|
275
|
+
response = _initiate_after_create(client, manifest, size_bytes=upload_size_bytes)
|
|
243
276
|
except (HostedNetworkError, HostedError) as exc:
|
|
244
277
|
raise PushError(str(exc)) from exc
|
|
245
278
|
|
|
@@ -489,17 +522,25 @@ def _auto_create_project(client: HostedClient, *, project_slug: str) -> None:
|
|
|
489
522
|
f"permission — check `evalshift whoami` reports this host and an org "
|
|
490
523
|
f"named {org_slug!r}",
|
|
491
524
|
) from exc
|
|
525
|
+
except (HostedNetworkError, HostedError) as exc:
|
|
526
|
+
raise PushError(str(exc)) from exc
|
|
492
527
|
if any(item.get("slug") == project for item in projects):
|
|
493
528
|
return
|
|
494
529
|
try:
|
|
495
530
|
client.create_project(org_slug, slug=project, name=_name_from_slug(project))
|
|
496
531
|
except HostedHTTPError as exc:
|
|
532
|
+
if exc.status_code == _PAYMENT_REQUIRED:
|
|
533
|
+
# An expired org is read-only: creating a project is refused with the same 402 a
|
|
534
|
+
# push gets, and it deserves the same prompt rather than an access-permission hint.
|
|
535
|
+
raise _upgrade_prompt(exc) from exc
|
|
497
536
|
raise PushError(
|
|
498
537
|
f"cannot auto-create {project_slug!r} at {client.host}: "
|
|
499
538
|
f"{_server_said(exc)}. Creating a project needs owner access to "
|
|
500
539
|
f"{org_slug!r}; a scoped service-account key cannot do it — create the "
|
|
501
540
|
f"project by hand, or push with an owner token",
|
|
502
541
|
) from exc
|
|
542
|
+
except (HostedNetworkError, HostedError) as exc:
|
|
543
|
+
raise PushError(str(exc)) from exc
|
|
503
544
|
|
|
504
545
|
|
|
505
546
|
def _server_said(exc: HostedHTTPError) -> str:
|
|
@@ -12,6 +12,11 @@ Two internal representations describe that, and neither goes on the wire:
|
|
|
12
12
|
|
|
13
13
|
Both map here onto one wire shape so the server, the database and the client
|
|
14
14
|
speak a single language. Neither internal model changes.
|
|
15
|
+
|
|
16
|
+
Only :func:`from_tool_trace` has a production caller (``hosted/bundle.py``): a run
|
|
17
|
+
bundle's ``examples[].traces`` come from the replay's own tool-call traces.
|
|
18
|
+
:func:`from_agent_trace` converts imported agent traces to the same wire shape but is
|
|
19
|
+
not wired into bundling today — those traces stay local.
|
|
15
20
|
"""
|
|
16
21
|
|
|
17
22
|
from __future__ import annotations
|
|
@@ -170,7 +175,14 @@ _PASSTHROUGH_FIELDS: dict[str, tuple[str, ...]] = {
|
|
|
170
175
|
|
|
171
176
|
|
|
172
177
|
def from_agent_trace(trace: AgentTrace) -> dict[str, Any] | None:
|
|
173
|
-
"""
|
|
178
|
+
"""Convert an imported agent trace to bundle wire events.
|
|
179
|
+
|
|
180
|
+
Not called by ``bundle.py`` today: imported traces (``evalshift traces import``)
|
|
181
|
+
stay local in ``.evalshift/runs/<id>/traces.jsonl`` and are read only by
|
|
182
|
+
``evaluate``, ``report.json`` and the ``diff``/``inspect``/``replay case``
|
|
183
|
+
commands. A run bundle's ``examples[].traces`` come solely from the replay's own
|
|
184
|
+
tool-call traces via :func:`from_tool_trace`. This function exists for a future
|
|
185
|
+
caller that would include imported traces in the bundle.
|
|
174
186
|
|
|
175
187
|
A round begins at each ``model_call``. The counter increments on every
|
|
176
188
|
``model_call`` after the first, so events preceding any model call stay in
|
|
@@ -556,6 +556,33 @@ details[open] > summary .caret::after { content: "\25BE"; }
|
|
|
556
556
|
background: var(--strip);
|
|
557
557
|
flex-wrap: wrap;
|
|
558
558
|
}
|
|
559
|
+
/* The row must read as a control, not a static line: a boxed caret, a worded
|
|
560
|
+
hint, and a hover state that reacts across the whole header. */
|
|
561
|
+
.reg-head .caret {
|
|
562
|
+
display: inline-flex;
|
|
563
|
+
align-items: center;
|
|
564
|
+
justify-content: center;
|
|
565
|
+
width: 22px;
|
|
566
|
+
height: 22px;
|
|
567
|
+
flex: none;
|
|
568
|
+
border: 1px solid var(--stroke);
|
|
569
|
+
background: var(--panel-2);
|
|
570
|
+
}
|
|
571
|
+
.reg-head .caret::after { font-size: 14px; width: auto; color: var(--body); }
|
|
572
|
+
.reg-toggle {
|
|
573
|
+
font-family: var(--font-mono);
|
|
574
|
+
font-size: 12px;
|
|
575
|
+
color: var(--dim);
|
|
576
|
+
min-width: 88px;
|
|
577
|
+
text-align: right;
|
|
578
|
+
}
|
|
579
|
+
.reg-toggle::after { content: "Show details"; }
|
|
580
|
+
.regression[open] > .reg-head .reg-toggle::after { content: "Hide details"; }
|
|
581
|
+
.reg-head:hover { background: var(--panel-2); }
|
|
582
|
+
.reg-head:hover .caret { border-color: var(--ok-edge); }
|
|
583
|
+
.reg-head:hover .caret::after,
|
|
584
|
+
.reg-head:hover .reg-toggle { color: var(--accent); }
|
|
585
|
+
.reg-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
|
559
586
|
.reg-id { font-family: var(--font-mono); font-size: 14px; color: var(--fg); }
|
|
560
587
|
.reg-what {
|
|
561
588
|
font-size: 14px;
|
|
@@ -762,6 +789,8 @@ footer p { margin: 0; }
|
|
|
762
789
|
id instead. */
|
|
763
790
|
.reg-head { gap: 8px 12px; padding: 12px 14px; }
|
|
764
791
|
.reg-what { flex: 1 1 60%; min-width: 0; white-space: normal; }
|
|
792
|
+
/* The boxed caret alone carries the affordance when space is tight. */
|
|
793
|
+
.reg-toggle { display: none; }
|
|
765
794
|
.reg-body { padding: 14px; }
|
|
766
795
|
.trace-list { padding: 12px 12px 12px 26px; }
|
|
767
796
|
.transcript-body { padding: 0 12px 12px 28px; }
|
|
@@ -769,6 +798,10 @@ footer p { margin: 0; }
|
|
|
769
798
|
|
|
770
799
|
@media print {
|
|
771
800
|
body { background: #fff; color: #111; }
|
|
801
|
+
/* Paper can't be clicked: show every regression body. Best-effort: only
|
|
802
|
+
engines with ::details-content (Chromium 131+) honour it. */
|
|
803
|
+
.regression::details-content { content-visibility: visible; }
|
|
804
|
+
.reg-toggle { display: none; }
|
|
772
805
|
}
|
|
773
806
|
|
|
774
807
|
/* --- Tone utilities ------------------------------------------------------ */
|