Files
cleveragents-core/scripts/check-quality-gates.py
T
CoreRasurae cecca72b8e
CI / benchmark-publish (push) Has started running
CI / benchmark-regression (push) Has been skipped
CI / e2e_tests (push) Failing after 14s
CI / helm (push) Failing after 8s
CI / unit_tests (push) Failing after 21s
CI / typecheck (push) Failing after 32s
CI / build (push) Failing after 9s
CI / quality (push) Failing after 24s
CI / integration_tests (push) Failing after 15s
CI / security (push) Failing after 24s
CI / lint (push) Failing after 34s
CI / coverage (push) Has been skipped
CI / docker (push) Has been skipped
CI / push-validation (push) Successful in 20s
CI / status-check (push) Failing after 6s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Failing after 1m11s
CI / coverage (pull_request) Successful in 14m34s
CI / push-validation (pull_request) Successful in 36s
CI / typecheck (pull_request) Successful in 1m14s
CI / integration_tests (pull_request) Successful in 3m42s
CI / e2e_tests (pull_request) Successful in 4m26s
CI / unit_tests (pull_request) Successful in 6m29s
CI / build (pull_request) Successful in 59s
CI / lint (pull_request) Successful in 53s
CI / quality (pull_request) Successful in 56s
CI / security (pull_request) Successful in 1m19s
CI / helm (pull_request) Successful in 53s
CI / docker (pull_request) Successful in 1m46s
CI / status-check (pull_request) Successful in 3s
test(behave): Reduce the coverage level to 96.5%
2026-05-05 02:01:49 +00:00

193 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""Quality gate checker for CleverAgents CI pipeline.
Aggregates results from multiple quality tools and produces a summary
report. Returns non-zero exit code if any critical gate fails.
Usage:
python scripts/check-quality-gates.py [--coverage-min 85] [--complexity-max 10]
Gates checked:
1. Coverage >= threshold (default 85%)
2. No type errors (pyright clean)
3. No high-severity security issues (bandit)
4. No dead code (vulture clean)
5. No extreme complexity (radon grade F)
"""
import argparse
import json
import subprocess
import sys
from pathlib import Path
def check_coverage(min_coverage: float) -> tuple[bool, str]:
"""Check test coverage meets minimum threshold."""
result = subprocess.run(
["coverage", "report", "--format=total"],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
return False, f"Coverage command failed: {result.stderr.strip()}"
try:
total = float(result.stdout.strip())
except ValueError:
return False, f"Could not parse coverage output: {result.stdout.strip()}"
passed = total >= min_coverage
msg = f"Coverage: {total:.1f}% (minimum: {min_coverage}%)"
return passed, msg
def check_typecheck() -> tuple[bool, str]:
"""Check pyright type checking passes."""
result = subprocess.run(
["pyright", "--outputjson"],
capture_output=True,
text=True,
check=False,
)
try:
data = json.loads(result.stdout)
error_count = data.get("summary", {}).get("errorCount", -1)
if error_count == 0:
return True, "Type checking: 0 errors"
return False, f"Type checking: {error_count} errors"
except (json.JSONDecodeError, KeyError):
# Fallback to exit code
if result.returncode == 0:
return True, "Type checking: passed"
return False, f"Type checking: failed (exit code {result.returncode})"
def check_security() -> tuple[bool, str]:
"""Check bandit finds no high-severity issues."""
result = subprocess.run(
[
"bandit",
"-c",
"pyproject.toml",
"-r",
"src/cleveragents",
"--severity-level",
"high",
"--format",
"json",
],
capture_output=True,
text=True,
check=False,
)
try:
data = json.loads(result.stdout)
high_issues = len(data.get("results", []))
if high_issues == 0:
return True, "Security: 0 high-severity issues"
return False, f"Security: {high_issues} high-severity issues"
except json.JSONDecodeError:
if result.returncode == 0:
return True, "Security: passed"
return False, f"Security: failed (exit code {result.returncode})"
def check_dead_code() -> tuple[bool, str]:
"""Check vulture finds no dead code."""
result = subprocess.run(
[
"vulture",
"src/cleveragents",
"vulture_whitelist.py",
"--min-confidence",
"80",
"--exclude",
"src/cleveragents/discovery",
],
capture_output=True,
text=True,
check=False,
)
if result.returncode == 0:
return True, "Dead code: none detected"
lines = result.stdout.strip().split("\n")
return False, f"Dead code: {len(lines)} issue(s) found"
def check_complexity(max_grade: str = "E") -> tuple[bool, str]:
"""Check no functions exceed maximum complexity grade."""
result = subprocess.run(
[
"radon",
"cc",
"src/cleveragents",
"--min",
max_grade,
"--show-complexity",
"--json",
],
capture_output=True,
text=True,
check=False,
)
try:
data = json.loads(result.stdout)
total_blocks = sum(len(blocks) for blocks in data.values())
if total_blocks == 0:
return True, f"Complexity: no blocks with grade >= {max_grade}"
return False, f"Complexity: {total_blocks} block(s) with grade >= {max_grade}"
except json.JSONDecodeError:
return True, "Complexity: passed (no extreme complexity)"
def main() -> int:
"""Run all quality gates and report results."""
parser = argparse.ArgumentParser(description="Check quality gates")
parser.add_argument(
"--coverage-min", type=int, default=85, help="Minimum coverage percentage"
)
parser.add_argument(
"--complexity-max",
type=str,
default="F",
help="Maximum allowed complexity grade (F = fail only on worst)",
)
args = parser.parse_args()
# Ensure build directory exists
Path("build").mkdir(exist_ok=True)
gates: list[tuple[str, tuple[bool, str]]] = [
("Coverage", check_coverage(args.coverage_min)),
("Type Check", check_typecheck()),
("Security", check_security()),
("Dead Code", check_dead_code()),
("Complexity", check_complexity(args.complexity_max)),
]
print("=" * 60)
print("QUALITY GATE REPORT")
print("=" * 60)
all_passed = True
for name, (passed, message) in gates:
icon = "[+]" if passed else "[X]"
print(f" {icon} {name}: {message}")
if not passed:
all_passed = False
print("=" * 60)
if all_passed:
print("All quality gates PASSED")
else:
print("Some quality gates FAILED")
print("=" * 60)
return 0 if all_passed else 1
if __name__ == "__main__":
sys.exit(main())