Files
cowork-local/scripts/run_quality_gate.py
T

144 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""Unified CASAN Quality Gate Orchestrator (EPIC R10 - Quality Assurance).
Runs all verification gates to validate architecture, security, single responsibility,
and test suite compliance before merging PRs or cutting a release.
Verification Stages (CASAN):
1. [C] Clean Architecture Guard (scripts/check_imports.py)
2. [A] Atomic & Secrets Audit (scripts/audit_security.py)
3. [S] Single Responsibility / LOC Guard (scripts/check_loc.py)
4. [A/N] Automated Tests & No-Regression Suite (pytest)
"""
from __future__ import annotations
import argparse
import os
import subprocess
import sys
import time
from pathlib import Path
from typing import List, Tuple
# Ensure stdout handles UTF-8 on Windows consoles without codec crash
if hasattr(sys.stdout, "reconfigure"):
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
REPO_ROOT = Path(__file__).resolve().parent.parent
def run_stage(title: str, cmd: List[str], cwd: Path) -> Tuple[bool, float, str]:
"""Execute a single quality gate command and measure elapsed duration.
Returns:
A tuple of (success_boolean, elapsed_seconds, combined_output)
"""
print(f"\n>> Running Gate: {title} ...")
start_time = time.time()
try:
proc = subprocess.run(
cmd,
cwd=cwd,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
)
elapsed = time.time() - start_time
success = proc.returncode == 0
output = proc.stdout + ("\n" + proc.stderr if proc.stderr else "")
return success, elapsed, output
except Exception as exc:
elapsed = time.time() - start_time
return False, elapsed, f"Exception occurred while running {cmd}: {exc}"
def main() -> int:
"""Main CLI orchestrator for CASAN quality gates."""
parser = argparse.ArgumentParser(description="Run CASAN Quality Gates on the repository.")
parser.add_argument(
"--skip-tests",
action="store_true",
help="Skip running pytest (run static and architectural guards only)",
)
parser.add_argument(
"--verbose",
"-v",
action="store_true",
help="Print detailed command output for passing gates as well",
)
args = parser.parse_args()
print("=" * 75)
print("COWORK LOCAL - CASAN QUALITY GATE RUNNER")
print("=" * 75)
stages = [
(
"C - Clean Architecture Boundary Check",
[sys.executable, str(REPO_ROOT / "scripts" / "check_imports.py")],
),
(
"A - Secrets & Plaintext Credentials Audit",
[sys.executable, str(REPO_ROOT / "scripts" / "audit_security.py")],
),
(
"S - Single Responsibility LOC Limit (<= 400 LOC)",
[sys.executable, str(REPO_ROOT / "scripts" / "check_loc.py"), "--max-lines", "400"],
),
]
if not args.skip_tests:
stages.append(
(
"A/N - Automated Pytest Suite (No-Regression)",
[sys.executable, "-m", "pytest", "-q"],
)
)
results = []
all_passed = True
total_start = time.time()
for title, cmd in stages:
success, elapsed, output = run_stage(title, cmd, cwd=REPO_ROOT)
results.append((title, success, elapsed, output))
if success:
print(f" [PASS] {title} ({elapsed:.2f}s)")
if args.verbose:
print(output.strip())
else:
all_passed = False
print(f" [FAIL] {title} ({elapsed:.2f}s)")
print("\n--- Output ---")
print(output.strip())
print("--------------")
total_elapsed = time.time() - total_start
print("\n" + "=" * 75)
print("QUALITY GATE SUMMARY REPORT")
print("=" * 75)
for title, success, elapsed, _ in results:
status_str = "[PASS]" if success else "[FAIL]"
print(f" {status_str:<8} | {elapsed:>6.2f}s | {title}")
print("-" * 75)
print(f"Total Execution Time: {total_elapsed:.2f}s")
if all_passed:
print("\nALL CASAN QUALITY GATES PASSED! Ready for PR merge or release.")
return 0
else:
print("\nQUALITY GATE FAILED! Please resolve the issues above before proceeding.")
return 1
if __name__ == "__main__":
sys.exit(main())