88 lines
2.8 KiB
Python
88 lines
2.8 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""
|
||
|
|
ci_automation_gate.py — Automated Quality Gate for Timmy Foundation CI.
|
||
|
|
|
||
|
|
Enforces:
|
||
|
|
1. The 10-line Rule — functions should ideally be under 10 lines (warn at 20, fail at 50).
|
||
|
|
2. Complexity Check — basic cyclomatic complexity check.
|
||
|
|
3. Auto-fixable Linting — trailing whitespace, missing final newlines.
|
||
|
|
|
||
|
|
Used as a pre-merge gate.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
import re
|
||
|
|
import argparse
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
class QualityGate:
|
||
|
|
def __init__(self, fix=False):
|
||
|
|
self.fix = fix
|
||
|
|
self.failures = 0
|
||
|
|
self.warnings = 0
|
||
|
|
|
||
|
|
def check_file(self, path: Path):
|
||
|
|
if path.suffix not in (".js", ".ts", ".py"):
|
||
|
|
return
|
||
|
|
|
||
|
|
with open(path, "r") as f:
|
||
|
|
lines = f.readlines()
|
||
|
|
|
||
|
|
new_lines = []
|
||
|
|
changed = False
|
||
|
|
|
||
|
|
# 1. Basic Linting
|
||
|
|
for line in lines:
|
||
|
|
cleaned = line.rstrip() + "\n"
|
||
|
|
if cleaned != line:
|
||
|
|
changed = True
|
||
|
|
new_lines.append(cleaned)
|
||
|
|
|
||
|
|
if lines and not lines[-1].endswith("\n"):
|
||
|
|
new_lines[-1] = new_lines[-1] + "\n"
|
||
|
|
changed = True
|
||
|
|
|
||
|
|
if changed and self.fix:
|
||
|
|
with open(path, "w") as f:
|
||
|
|
f.writelines(new_lines)
|
||
|
|
print(f" [FIXED] {path}: Cleaned whitespace and newlines.")
|
||
|
|
elif changed:
|
||
|
|
print(f" [WARN] {path}: Has trailing whitespace or missing final newline.")
|
||
|
|
self.warnings += 1
|
||
|
|
|
||
|
|
# 2. Function Length Check (Simple regex-based)
|
||
|
|
content = "".join(new_lines)
|
||
|
|
if path.suffix in (".js", ".ts"):
|
||
|
|
# Match function blocks
|
||
|
|
functions = re.findall(r"function\s+\w+\s*\(.*?\)\s*\{([\s\S]*?)\}", content)
|
||
|
|
for i, func in enumerate(functions):
|
||
|
|
length = func.count("\n")
|
||
|
|
if length > 50:
|
||
|
|
print(f" [FAIL] {path}: Function {i} is too long ({length} lines).")
|
||
|
|
self.failures += 1
|
||
|
|
elif length > 20:
|
||
|
|
print(f" [WARN] {path}: Function {i} is getting long ({length} lines).")
|
||
|
|
self.warnings += 1
|
||
|
|
|
||
|
|
def run(self, directory: str):
|
||
|
|
print(f"--- Quality Gate: {directory} ---")
|
||
|
|
for root, _, files in os.walk(directory):
|
||
|
|
if "node_modules" in root or ".git" in root:
|
||
|
|
continue
|
||
|
|
for file in files:
|
||
|
|
self.check_file(Path(root) / file)
|
||
|
|
|
||
|
|
print(f"\nGate complete. Failures: {self.failures}, Warnings: {self.warnings}")
|
||
|
|
if self.failures > 0:
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
parser = argparse.ArgumentParser()
|
||
|
|
parser.add_argument("dir", nargs="?", default=".")
|
||
|
|
parser.add_argument("--fix", action="store_true")
|
||
|
|
args = parser.parse_args()
|
||
|
|
|
||
|
|
gate = QualityGate(fix=args.fix)
|
||
|
|
gate.run(args.dir)
|