evalgate-sdk 3.5.1__tar.gz → 3.6.0__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.
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/CHANGELOG.md +5 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/PKG-INFO +1 -1
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/pyproject.toml +1 -1
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/__init__.py +10 -0
- evalgate_sdk-3.6.0/src/evalgate_sdk/_version.py +3 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/__init__.py +17 -0
- evalgate_sdk-3.6.0/src/evalgate_sdk/cli/agent_evidence_utils.py +87 -0
- evalgate_sdk-3.6.0/src/evalgate_sdk/cli/check_runner.py +291 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/cli_constants.py +5 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/commands.py +201 -157
- evalgate_sdk-3.6.0/src/evalgate_sdk/cli/evidence_persistence.py +391 -0
- evalgate_sdk-3.6.0/src/evalgate_sdk/cli/failure_modes.py +131 -0
- evalgate_sdk-3.6.0/src/evalgate_sdk/cli/gate_evaluator.py +291 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/judge_commands.py +2 -2
- evalgate_sdk-3.6.0/src/evalgate_sdk/cli/mcp.py +820 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/new_commands.py +118 -5
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/regression_gate.py +91 -7
- evalgate_sdk-3.6.0/src/evalgate_sdk/cli/run_cli.py +718 -0
- evalgate_sdk-3.6.0/src/evalgate_sdk/cli/sprint4_commands.py +38 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/client.py +34 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/types.py +52 -6
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_cli_hardening_351.py +51 -15
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_cli_new_commands.py +4 -0
- evalgate_sdk-3.6.0/tests/test_cli_sprint4.py +277 -0
- evalgate_sdk-3.6.0/tests/test_provider_keys.py +150 -0
- evalgate_sdk-3.5.1/src/evalgate_sdk/_version.py +0 -3
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/.gitignore +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/README.md +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/assertions.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/auto.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/auto_experiment_runner.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/auto_failure_analyzer.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/auto_mutation_engine.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/batch.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cache.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/ci_context.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/api.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/api_command_config.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/config.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/env.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/formatters/types.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/golden_commands.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/manifest.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/policy_packs.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/profiles.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/render/__init__.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/render/snippet.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/render/sort.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/report/__init__.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/report/build_check_report.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/traces.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/workflow_commands.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cli/workspace.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/cluster.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/collector.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/constants.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/context.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/errors.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/export.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/formatters/__init__.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/formatters/github.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/formatters/human.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/formatters/json_fmt.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/formatters/pr_comment.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/golden.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/integrations/__init__.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/integrations/anthropic.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/integrations/autogen.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/integrations/crewai.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/integrations/langchain.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/integrations/openai.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/integrations/openai_eval.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/knowledge.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/local.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/logger.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/matchers.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/otel.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/pagination.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/py.typed +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/pytest_plugin.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/reason_codes.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/regression.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/replay_decision.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/__init__.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/adapters/__init__.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/adapters/config_to_dsl.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/adapters/testsuite_to_dsl.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/context.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/eval.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/execution_mode.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/executor.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/registry.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/run_report.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/runtime/types.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/snapshot.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/streaming.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/synthesize.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/testing.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/utils/__init__.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/utils/input_hash.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/src/evalgate_sdk/workflows.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/__init__.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_assertions.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_assertions_async.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_auto_mutation_engine_parity.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_auto_session_methods.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_batch_collector.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_ci_context.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_cli_golden_commands.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_client.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_collector.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_contract_payloads.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_decorators_and_integrations.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_errors.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_execution_mode.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_formatters.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_knowledge_methods.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_local_storage.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_new_modules.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_openapi_contracts.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_otel.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_parity_gap_modules.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_parity_gaps.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_prod_hardening.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_pytest_plugin.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_run_report.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_runtime_dsl.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_sdk_contract_scaffold.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_testing.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_types.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_workflows.py +0 -0
- {evalgate_sdk-3.5.1 → evalgate_sdk-3.6.0}/tests/test_workflows_parity.py +0 -0
|
@@ -9,6 +9,11 @@ Version numbering is aligned with the TypeScript SDK (`@evalgate/sdk`) and the p
|
|
|
9
9
|
|
|
10
10
|
**Version history note:** The Python SDK jumped from 1.0.0 → 1.9.x → 2.0.0 to stay in sync with the TypeScript SDK. The TypeScript SDK had many releases (1.1–1.9) before the Python SDK existed. We now align both SDKs on the same major.minor version.
|
|
11
11
|
|
|
12
|
+
## Unreleased
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **Production-loop scaffold parity** — `evalgate init --template production-loop`, `evalgate verify --local`, configured `discover`, JSON `validate`, and JSON/dry-run `gate` now work together from a clean Python SDK install.
|
|
16
|
+
|
|
12
17
|
## [3.5.1] - 2026-06-26
|
|
13
18
|
|
|
14
19
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: evalgate-sdk
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.6.0
|
|
4
4
|
Summary: EvalGate Python SDK — CI for AI behavior. Traces, evaluations, assertions, and regression gates for LLM apps.
|
|
5
5
|
Project-URL: Homepage, https://evalgate.com
|
|
6
6
|
Project-URL: Documentation, https://github.com/evalgate/ai-evaluation-platform#readme
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "evalgate-sdk"
|
|
7
|
-
version = "3.
|
|
7
|
+
version = "3.6.0"
|
|
8
8
|
description = "EvalGate Python SDK — CI for AI behavior. Traces, evaluations, assertions, and regression gates for LLM apps."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -392,12 +392,17 @@ from evalgate_sdk.types import (
|
|
|
392
392
|
AutoPlanPreviewResponse,
|
|
393
393
|
AutoSessionWorkflowProfile,
|
|
394
394
|
CamelModel,
|
|
395
|
+
CreateProviderKeyParams,
|
|
395
396
|
EvaluationIntegritySnapshot,
|
|
396
397
|
EvaluationRunDetail,
|
|
397
398
|
KnowledgeHintSummary,
|
|
398
399
|
KnowledgeResponse,
|
|
400
|
+
ListProviderKeysParams,
|
|
401
|
+
ProviderKey,
|
|
399
402
|
QualityBreakdown,
|
|
400
403
|
QualityScore,
|
|
404
|
+
RotateProviderKeyResult,
|
|
405
|
+
UpdateProviderKeyParams,
|
|
401
406
|
)
|
|
402
407
|
from evalgate_sdk.utils.input_hash import normalize_input, sha256_input
|
|
403
408
|
from evalgate_sdk.workflows import WorkflowTracer, create_workflow_tracer, trace_workflow_step
|
|
@@ -700,6 +705,11 @@ __all__ = [
|
|
|
700
705
|
"CamelModel",
|
|
701
706
|
"QualityScore",
|
|
702
707
|
"QualityBreakdown",
|
|
708
|
+
"CreateProviderKeyParams",
|
|
709
|
+
"ListProviderKeysParams",
|
|
710
|
+
"ProviderKey",
|
|
711
|
+
"RotateProviderKeyResult",
|
|
712
|
+
"UpdateProviderKeyParams",
|
|
703
713
|
# Knowledge API
|
|
704
714
|
"KnowledgeAPI",
|
|
705
715
|
# Collector (T2)
|
|
@@ -76,6 +76,7 @@ def get_app():
|
|
|
76
76
|
share,
|
|
77
77
|
sync,
|
|
78
78
|
upgrade,
|
|
79
|
+
verify,
|
|
79
80
|
version,
|
|
80
81
|
)
|
|
81
82
|
from evalgate_sdk.cli.judge_commands import judge
|
|
@@ -103,6 +104,7 @@ def get_app():
|
|
|
103
104
|
app.command("sync")(sync)
|
|
104
105
|
app.command("configure")(configure)
|
|
105
106
|
app.command("upgrade")(upgrade)
|
|
107
|
+
app.command("verify")(verify)
|
|
106
108
|
app.command("version")(version)
|
|
107
109
|
app.command("impact-analysis")(impact_analysis)
|
|
108
110
|
app.command("migrate")(migrate)
|
|
@@ -132,6 +134,21 @@ def get_app():
|
|
|
132
134
|
app.command("replay-decision")(replay_decision)
|
|
133
135
|
app.add_typer(auto_app, name="auto")
|
|
134
136
|
|
|
137
|
+
from evalgate_sdk.cli.sprint4_commands import failure_modes_command, mcp_command, run_cli_command
|
|
138
|
+
|
|
139
|
+
app.command(
|
|
140
|
+
"run-cli",
|
|
141
|
+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
142
|
+
)(run_cli_command)
|
|
143
|
+
app.command(
|
|
144
|
+
"mcp",
|
|
145
|
+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
146
|
+
)(mcp_command)
|
|
147
|
+
app.command(
|
|
148
|
+
"failure-modes",
|
|
149
|
+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
150
|
+
)(failure_modes_command)
|
|
151
|
+
|
|
135
152
|
return app
|
|
136
153
|
|
|
137
154
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Utilities for agent evidence capture (hashing, redaction, JSON I/O).
|
|
2
|
+
|
|
3
|
+
Port of ``cli/agent-evidence-utils.ts``.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import hashlib
|
|
9
|
+
import json
|
|
10
|
+
import re
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
SECRET_PATTERNS: list[re.Pattern[str]] = [
|
|
15
|
+
re.compile(
|
|
16
|
+
r"\b((?:api[_-]?key|access[_-]?token|auth[_-]?token|token|secret|password|client[_-]?secret)\s*[:=]\s*)([\"']?)([^\s\"',;]+)",
|
|
17
|
+
re.I,
|
|
18
|
+
),
|
|
19
|
+
re.compile(r"(Bearer\s+)[A-Za-z0-9._~+/=-]+", re.I),
|
|
20
|
+
re.compile(r"\b(sk-[A-Za-z0-9_-]{20,})\b"),
|
|
21
|
+
re.compile(r"\b(gh[pousr]_[A-Za-z0-9_]{20,})\b"),
|
|
22
|
+
re.compile(r"\b(xox[baprs]-[A-Za-z0-9-]{20,})\b"),
|
|
23
|
+
re.compile(r"\b(npm_[A-Za-z0-9]{20,})\b"),
|
|
24
|
+
re.compile(r"\b(AKIA[0-9A-Z]{16})\b"),
|
|
25
|
+
re.compile(r"\b(eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b"),
|
|
26
|
+
re.compile(r"(postgres(?:ql)?:\/\/[^:\s/@]+:)([^@\s]+)(@[^\s]+)", re.I),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def sha256_hex(value: str) -> str:
|
|
31
|
+
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _sort_value(value: Any) -> Any:
|
|
35
|
+
if isinstance(value, list):
|
|
36
|
+
return [_sort_value(item) for item in value]
|
|
37
|
+
if isinstance(value, dict):
|
|
38
|
+
return {key: _sort_value(value[key]) for key in sorted(value)}
|
|
39
|
+
return value
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def canonicalize(value: Any) -> str:
|
|
43
|
+
return json.dumps(_sort_value(value), separators=(",", ":"), default=str)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def stable_hash(value: Any) -> str:
|
|
47
|
+
return sha256_hex(canonicalize(value))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def redact_text(value: str) -> dict[str, Any]:
|
|
51
|
+
text = value
|
|
52
|
+
secrets_found = 0
|
|
53
|
+
for pattern in SECRET_PATTERNS:
|
|
54
|
+
|
|
55
|
+
def _replace(match: re.Match[str]) -> str:
|
|
56
|
+
nonlocal secrets_found
|
|
57
|
+
secrets_found += 1
|
|
58
|
+
groups = match.groups()
|
|
59
|
+
first = groups[0] if groups else ""
|
|
60
|
+
if first.lower().startswith("bearer"):
|
|
61
|
+
return f"{first}[REDACTED]"
|
|
62
|
+
if first.lower().startswith("postgres"):
|
|
63
|
+
suffix = groups[2] if len(groups) > 2 else ""
|
|
64
|
+
return f"{first}[REDACTED]{suffix}"
|
|
65
|
+
if len(groups) >= 3 and first:
|
|
66
|
+
quote = groups[1] if len(groups) > 1 else ""
|
|
67
|
+
return f"{first}{quote}[REDACTED]"
|
|
68
|
+
return "[REDACTED]"
|
|
69
|
+
|
|
70
|
+
text = pattern.sub(_replace, text)
|
|
71
|
+
return {"text": text, "secretsFound": secrets_found}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def bounded(value: str, max_bytes: int = 64 * 1024) -> str:
|
|
75
|
+
encoded = value.encode("utf-8")
|
|
76
|
+
if len(encoded) <= max_bytes:
|
|
77
|
+
return value
|
|
78
|
+
truncated = encoded[:max_bytes].decode("utf-8", errors="ignore")
|
|
79
|
+
return f"{truncated}\n[TRUNCATED]"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def read_json_file(file_path: str) -> dict[str, Any]:
|
|
83
|
+
raw = Path(file_path).read_text(encoding="utf-8-sig")
|
|
84
|
+
parsed = json.loads(raw)
|
|
85
|
+
if not isinstance(parsed, dict):
|
|
86
|
+
raise ValueError(f"{file_path} must contain a JSON object")
|
|
87
|
+
return parsed
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""evalgate check runner — CI/CD evaluation gate.
|
|
2
|
+
|
|
3
|
+
Port of ``cli/check.ts`` (core path: fetch quality, evaluate gate, build report).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import json
|
|
10
|
+
from dataclasses import asdict, dataclass, is_dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Literal
|
|
13
|
+
|
|
14
|
+
from evalgate_sdk.cli.api import fetch_quality_latest, fetch_run_details
|
|
15
|
+
from evalgate_sdk.cli.cli_constants import EXIT
|
|
16
|
+
from evalgate_sdk.cli.config import load_config, merge_config_with_args
|
|
17
|
+
from evalgate_sdk.cli.gate_evaluator import CheckGateArgs, evaluate_gate, gate_result_to_dict
|
|
18
|
+
from evalgate_sdk.cli.report.build_check_report import build_check_report
|
|
19
|
+
from evalgate_sdk.formatters.human import format_human
|
|
20
|
+
from evalgate_sdk.formatters.json_fmt import format_json
|
|
21
|
+
|
|
22
|
+
FormatType = Literal["human", "json", "github"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class CheckArgs:
|
|
27
|
+
base_url: str
|
|
28
|
+
api_key: str
|
|
29
|
+
min_score: float
|
|
30
|
+
max_drop: float | None = None
|
|
31
|
+
warn_drop: float | None = None
|
|
32
|
+
min_n: int | None = None
|
|
33
|
+
allow_weak_evidence: bool = False
|
|
34
|
+
evaluation_id: str = ""
|
|
35
|
+
policy: str | None = None
|
|
36
|
+
baseline: str = "published"
|
|
37
|
+
format: FormatType = "human"
|
|
38
|
+
explain: bool = False
|
|
39
|
+
dry_run: bool = False
|
|
40
|
+
allow_empty: bool = False
|
|
41
|
+
max_cost_usd: float | None = None
|
|
42
|
+
max_latency_ms: float | None = None
|
|
43
|
+
max_cost_delta_usd: float | None = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _to_camel(key: str) -> str:
|
|
47
|
+
parts = key.split("_")
|
|
48
|
+
return parts[0] + "".join(part[:1].upper() + part[1:] for part in parts[1:])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _serialize(value: Any) -> Any:
|
|
52
|
+
if is_dataclass(value) and not isinstance(value, type):
|
|
53
|
+
return {_to_camel(k): _serialize(v) for k, v in asdict(value).items() if v is not None}
|
|
54
|
+
if isinstance(value, list):
|
|
55
|
+
return [_serialize(item) for item in value]
|
|
56
|
+
if isinstance(value, dict):
|
|
57
|
+
return {_to_camel(k): _serialize(v) for k, v in value.items() if v is not None}
|
|
58
|
+
return value
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def parse_check_args(argv: list[str]) -> tuple[CheckArgs | None, int, str | None]:
|
|
62
|
+
args: dict[str, str] = {}
|
|
63
|
+
index = 0
|
|
64
|
+
while index < len(argv):
|
|
65
|
+
arg = argv[index]
|
|
66
|
+
if arg.startswith("--"):
|
|
67
|
+
key = arg[2:]
|
|
68
|
+
next_value = argv[index + 1] if index + 1 < len(argv) else None
|
|
69
|
+
if next_value is not None and not next_value.startswith("--"):
|
|
70
|
+
args[key] = next_value
|
|
71
|
+
index += 2
|
|
72
|
+
else:
|
|
73
|
+
args[key] = "true"
|
|
74
|
+
index += 1
|
|
75
|
+
else:
|
|
76
|
+
index += 1
|
|
77
|
+
|
|
78
|
+
base_url = args.get("baseUrl") or args.get("base-url") or ""
|
|
79
|
+
api_key = args.get("apiKey") or args.get("api-key") or ""
|
|
80
|
+
min_score = int(args.get("minScore") or args.get("min-score") or "0")
|
|
81
|
+
max_drop = (
|
|
82
|
+
int(args["maxDrop"]) if args.get("maxDrop") else (int(args["max-drop"]) if args.get("max-drop") else None)
|
|
83
|
+
)
|
|
84
|
+
warn_drop = (
|
|
85
|
+
int(args["warnDrop"]) if args.get("warnDrop") else (int(args["warn-drop"]) if args.get("warn-drop") else None)
|
|
86
|
+
)
|
|
87
|
+
min_n = int(args["minN"]) if args.get("minN") else (int(args["min-n"]) if args.get("min-n") else None)
|
|
88
|
+
allow_weak_evidence = args.get("allowWeakEvidence") in {"true", "1"} or args.get("allow-weak-evidence") in {
|
|
89
|
+
"true",
|
|
90
|
+
"1",
|
|
91
|
+
}
|
|
92
|
+
evaluation_id = args.get("evaluationId") or args.get("evaluation-id") or ""
|
|
93
|
+
policy = args.get("policy")
|
|
94
|
+
format_raw = args.get("format") or "human"
|
|
95
|
+
fmt: FormatType = "json" if format_raw == "json" else ("github" if format_raw == "github" else "human")
|
|
96
|
+
explain = args.get("explain") in {"true", "1"}
|
|
97
|
+
dry_run = args.get("dry-run") == "true" or args.get("dryRun") == "true"
|
|
98
|
+
allow_empty = args.get("allow-empty") == "true" or args.get("allowEmpty") == "true"
|
|
99
|
+
profile = args.get("profile")
|
|
100
|
+
baseline = args.get("baseline") or "published"
|
|
101
|
+
if baseline not in {"published", "previous", "production", "auto"}:
|
|
102
|
+
baseline = "published"
|
|
103
|
+
max_cost_usd = (
|
|
104
|
+
float(args.get("max-cost-usd") or args.get("maxCostUsd") or "nan")
|
|
105
|
+
if (args.get("max-cost-usd") or args.get("maxCostUsd"))
|
|
106
|
+
else None
|
|
107
|
+
)
|
|
108
|
+
max_latency_ms = (
|
|
109
|
+
int(args.get("max-latency-ms") or args.get("maxLatencyMs") or "0")
|
|
110
|
+
if (args.get("max-latency-ms") or args.get("maxLatencyMs"))
|
|
111
|
+
else None
|
|
112
|
+
)
|
|
113
|
+
max_cost_delta_usd = (
|
|
114
|
+
float(args.get("max-cost-delta-usd") or args.get("maxCostDeltaUsd") or "nan")
|
|
115
|
+
if (args.get("max-cost-delta-usd") or args.get("maxCostDeltaUsd"))
|
|
116
|
+
else None
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
config = load_config()
|
|
120
|
+
merged = merge_config_with_args(
|
|
121
|
+
config,
|
|
122
|
+
{
|
|
123
|
+
"evaluation_id": evaluation_id or None,
|
|
124
|
+
"base_url": base_url or None,
|
|
125
|
+
"min_score": str(min_score) if args.get("minScore") or args.get("min-score") else None,
|
|
126
|
+
"max_drop": str(max_drop) if max_drop is not None else None,
|
|
127
|
+
"warn_drop": str(warn_drop) if warn_drop is not None else None,
|
|
128
|
+
"min_n": str(min_n) if min_n is not None else None,
|
|
129
|
+
"allow_weak_evidence": "true" if allow_weak_evidence else None,
|
|
130
|
+
"baseline": baseline,
|
|
131
|
+
"profile": profile,
|
|
132
|
+
},
|
|
133
|
+
)
|
|
134
|
+
if not evaluation_id and merged.get("evaluation_id"):
|
|
135
|
+
evaluation_id = str(merged["evaluation_id"])
|
|
136
|
+
if merged.get("base_url"):
|
|
137
|
+
base_url = str(merged["base_url"])
|
|
138
|
+
if merged.get("min_score") is not None and not (args.get("minScore") or args.get("min-score")):
|
|
139
|
+
min_score = int(merged["min_score"])
|
|
140
|
+
if merged.get("max_drop") is not None and max_drop is None:
|
|
141
|
+
max_drop = int(merged["max_drop"])
|
|
142
|
+
if merged.get("warn_drop") is not None and warn_drop is None:
|
|
143
|
+
warn_drop = int(merged["warn_drop"])
|
|
144
|
+
if merged.get("min_n") is not None and min_n is None:
|
|
145
|
+
min_n = int(merged["min_n"])
|
|
146
|
+
if merged.get("allow_weak_evidence") is not None and not (
|
|
147
|
+
args.get("allowWeakEvidence") or args.get("allow-weak-evidence")
|
|
148
|
+
):
|
|
149
|
+
allow_weak_evidence = bool(merged["allow_weak_evidence"])
|
|
150
|
+
if merged.get("baseline") and not args.get("baseline"):
|
|
151
|
+
baseline = str(merged["baseline"])
|
|
152
|
+
|
|
153
|
+
import os
|
|
154
|
+
|
|
155
|
+
if not base_url:
|
|
156
|
+
from evalgate_sdk.constants import DEFAULT_BASE_URL
|
|
157
|
+
|
|
158
|
+
base_url = os.environ.get("EVALGATE_BASE_URL") or DEFAULT_BASE_URL
|
|
159
|
+
if not api_key:
|
|
160
|
+
api_key = os.environ.get("EVALGATE_API_KEY") or ""
|
|
161
|
+
|
|
162
|
+
if not api_key:
|
|
163
|
+
return None, EXIT.BAD_ARGS, "Error: --apiKey or EVALGATE_API_KEY is required"
|
|
164
|
+
if not evaluation_id:
|
|
165
|
+
return None, EXIT.BAD_ARGS, "Run evalgate init and paste your evaluationId, or pass --evaluationId."
|
|
166
|
+
if min_score < 0 or min_score > 100:
|
|
167
|
+
return None, EXIT.BAD_ARGS, "Error: --minScore must be 0-100"
|
|
168
|
+
if min_n is not None and min_n < 1:
|
|
169
|
+
return None, EXIT.BAD_ARGS, "Error: --minN must be a positive number"
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
CheckArgs(
|
|
173
|
+
base_url=base_url,
|
|
174
|
+
api_key=api_key,
|
|
175
|
+
min_score=min_score,
|
|
176
|
+
max_drop=max_drop,
|
|
177
|
+
warn_drop=warn_drop,
|
|
178
|
+
min_n=min_n,
|
|
179
|
+
allow_weak_evidence=allow_weak_evidence,
|
|
180
|
+
evaluation_id=evaluation_id,
|
|
181
|
+
policy=policy,
|
|
182
|
+
baseline=baseline,
|
|
183
|
+
format=fmt,
|
|
184
|
+
explain=explain,
|
|
185
|
+
dry_run=dry_run,
|
|
186
|
+
allow_empty=allow_empty,
|
|
187
|
+
max_cost_usd=max_cost_usd,
|
|
188
|
+
max_latency_ms=max_latency_ms,
|
|
189
|
+
max_cost_delta_usd=max_cost_delta_usd,
|
|
190
|
+
),
|
|
191
|
+
EXIT.PASS,
|
|
192
|
+
None,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
async def run_check(args: CheckArgs) -> int:
|
|
197
|
+
quality_result = await fetch_quality_latest(args.base_url, args.api_key, args.evaluation_id, args.baseline)
|
|
198
|
+
if not quality_result.get("ok"):
|
|
199
|
+
status = quality_result.get("status", 0)
|
|
200
|
+
body = quality_result.get("body", "")
|
|
201
|
+
if status == 0:
|
|
202
|
+
print(f"EvalGate gate ERROR: Network failure — {body}")
|
|
203
|
+
else:
|
|
204
|
+
print(f"EvalGate gate ERROR: API returned {status} — {body}")
|
|
205
|
+
return EXIT.API_ERROR
|
|
206
|
+
|
|
207
|
+
quality = quality_result["data"]
|
|
208
|
+
request_id = quality_result.get("request_id")
|
|
209
|
+
evaluation_run_id = quality.get("evaluationRunId") or quality.get("evaluation_run_id")
|
|
210
|
+
|
|
211
|
+
run_details = None
|
|
212
|
+
if evaluation_run_id is not None:
|
|
213
|
+
run_res = await fetch_run_details(args.base_url, args.api_key, args.evaluation_id, int(evaluation_run_id))
|
|
214
|
+
if run_res.get("ok"):
|
|
215
|
+
run_details = run_res.get("data")
|
|
216
|
+
|
|
217
|
+
gate_args = CheckGateArgs(
|
|
218
|
+
min_score=args.min_score,
|
|
219
|
+
max_drop=args.max_drop,
|
|
220
|
+
warn_drop=args.warn_drop,
|
|
221
|
+
min_n=args.min_n,
|
|
222
|
+
allow_weak_evidence=args.allow_weak_evidence,
|
|
223
|
+
policy=args.policy,
|
|
224
|
+
baseline=args.baseline,
|
|
225
|
+
allow_empty=args.allow_empty,
|
|
226
|
+
max_cost_usd=args.max_cost_usd,
|
|
227
|
+
max_latency_ms=args.max_latency_ms,
|
|
228
|
+
max_cost_delta_usd=args.max_cost_delta_usd,
|
|
229
|
+
)
|
|
230
|
+
gate_result = evaluate_gate(gate_args, quality)
|
|
231
|
+
gate_dict = gate_result_to_dict(gate_result)
|
|
232
|
+
|
|
233
|
+
report = build_check_report(
|
|
234
|
+
evaluation_id=args.evaluation_id,
|
|
235
|
+
quality=quality,
|
|
236
|
+
gate_result=gate_dict,
|
|
237
|
+
base_url=args.base_url,
|
|
238
|
+
run_details=run_details,
|
|
239
|
+
request_id=request_id,
|
|
240
|
+
policy=args.policy,
|
|
241
|
+
min_score=args.min_score,
|
|
242
|
+
max_drop=args.max_drop,
|
|
243
|
+
warn_drop=args.warn_drop,
|
|
244
|
+
min_n=args.min_n,
|
|
245
|
+
allow_weak_evidence=args.allow_weak_evidence,
|
|
246
|
+
baseline=args.baseline,
|
|
247
|
+
max_cost_usd=args.max_cost_usd,
|
|
248
|
+
max_latency_ms=args.max_latency_ms,
|
|
249
|
+
max_cost_delta_usd=args.max_cost_delta_usd,
|
|
250
|
+
explain=args.explain,
|
|
251
|
+
)
|
|
252
|
+
report_dict = _serialize(report)
|
|
253
|
+
|
|
254
|
+
report_dir = Path.cwd() / ".evalgate"
|
|
255
|
+
try:
|
|
256
|
+
report_dir.mkdir(parents=True, exist_ok=True)
|
|
257
|
+
(report_dir / "last-report.json").write_text(json.dumps(report_dict, indent=2) + "\n", encoding="utf-8")
|
|
258
|
+
except OSError as exc:
|
|
259
|
+
print(f"EvalGate: failed to write .evalgate/last-report.json: {exc}")
|
|
260
|
+
return EXIT.API_ERROR
|
|
261
|
+
|
|
262
|
+
if args.format == "json":
|
|
263
|
+
print(format_json(report_dict))
|
|
264
|
+
elif args.format == "github":
|
|
265
|
+
from evalgate_sdk.formatters.github import format_github
|
|
266
|
+
|
|
267
|
+
print(format_github(report_dict))
|
|
268
|
+
else:
|
|
269
|
+
print(format_human(report_dict))
|
|
270
|
+
|
|
271
|
+
if not gate_result.passed:
|
|
272
|
+
print("\nNext: evalgate explain")
|
|
273
|
+
|
|
274
|
+
if args.dry_run:
|
|
275
|
+
print(f"\n[dry-run] Gate exits with code {gate_result.exit_code} (severity={gate_result.severity})")
|
|
276
|
+
return gate_result.exit_code
|
|
277
|
+
|
|
278
|
+
return gate_result.exit_code
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def run_check_sync(argv: list[str]) -> int:
|
|
282
|
+
parsed, exit_code, message = parse_check_args(argv)
|
|
283
|
+
if parsed is None:
|
|
284
|
+
if message:
|
|
285
|
+
print(message)
|
|
286
|
+
return exit_code
|
|
287
|
+
try:
|
|
288
|
+
loop = asyncio.get_running_loop()
|
|
289
|
+
except RuntimeError:
|
|
290
|
+
return asyncio.run(run_check(parsed))
|
|
291
|
+
return loop.run_until_complete(run_check(parsed))
|