#!/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)