diff --git a/scripts/architecture_linter_v2.py b/scripts/architecture_linter_v2.py new file mode 100644 index 00000000..60bcd99a --- /dev/null +++ b/scripts/architecture_linter_v2.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +""" +[ARCH] Architecture Linter v2 +Part of the Gemini Sovereign Governance System. + +Enforces architectural boundaries, security, and documentation standards +across the Timmy Foundation fleet. +""" + +import os +import re +import sys +import argparse +from pathlib import Path + +# --- CONFIGURATION --- +SOVEREIGN_KEYWORDS = ["mempalace", "sovereign_store", "tirith", "bezalel", "nexus"] +IP_REGEX = r'\b(?:\d{1,3}\.){3}\d{1,3}\b' +API_KEY_REGEX = r'(?:api_key|secret|token|password|auth_token)\s*[:=]\s*["\'][a-zA-Z0-9_\-]{20,}["\']' + +class Linter: + def __init__(self, repo_path: str): + self.repo_path = Path(repo_path).resolve() + self.repo_name = self.repo_path.name + self.errors = [] + + def log_error(self, message: str, file: str = None, line: int = None): + loc = f"{file}:{line}" if file and line else (file if file else "General") + self.errors.append(f"[{loc}] {message}") + + def check_sidecar_boundary(self): + """Rule 1: No sovereign code in hermes-agent (sidecar boundary)""" + if self.repo_name == "hermes-agent": + for root, _, files in os.walk(self.repo_path): + if "node_modules" in root or ".git" in root: + continue + for file in files: + if file.endswith((".py", ".ts", ".js", ".tsx")): + path = Path(root) / file + content = path.read_text(errors="ignore") + for kw in SOVEREIGN_KEYWORDS: + if kw in content.lower(): + # Exception: imports or comments might be okay, but we're strict for now + self.log_error(f"Sovereign keyword '{kw}' found in hermes-agent. Violates sidecar boundary.", str(path.relative_to(self.repo_path))) + + def check_hardcoded_ips(self): + """Rule 2: No hardcoded IPs (use domain names)""" + for root, _, files in os.walk(self.repo_path): + if "node_modules" in root or ".git" in root: + continue + for file in files: + if file.endswith((".py", ".ts", ".js", ".tsx", ".yaml", ".yml", ".json")): + path = Path(root) / file + content = path.read_text(errors="ignore") + matches = re.finditer(IP_REGEX, content) + for match in matches: + ip = match.group() + if ip in ["127.0.0.1", "0.0.0.0"]: + continue + line_no = content.count('\n', 0, match.start()) + 1 + self.log_error(f"Hardcoded IP address '{ip}' found. Use domain names or environment variables.", str(path.relative_to(self.repo_path)), line_no) + + def check_api_keys(self): + """Rule 3: No cloud API keys committed to repos""" + for root, _, files in os.walk(self.repo_path): + if "node_modules" in root or ".git" in root: + continue + for file in files: + if file.endswith((".py", ".ts", ".js", ".tsx", ".yaml", ".yml", ".json", ".env")): + if file == ".env.example": + continue + path = Path(root) / file + content = path.read_text(errors="ignore") + matches = re.finditer(API_KEY_REGEX, content, re.IGNORECASE) + for match in matches: + line_no = content.count('\n', 0, match.start()) + 1 + self.log_error("Potential API key or secret found in code.", str(path.relative_to(self.repo_path)), line_no) + + def check_soul_canonical(self): + """Rule 4: SOUL.md exists and is canonical in exactly one location""" + soul_path = self.repo_path / "SOUL.md" + if self.repo_name == "timmy-config": + if not soul_path.exists(): + self.log_error("SOUL.md is missing from the canonical location (timmy-config root).") + else: + if soul_path.exists(): + self.log_error("SOUL.md found in non-canonical repo. It should only live in timmy-config.") + + def check_readme(self): + """Rule 5: Every repo has a README with current truth""" + readme_path = self.repo_path / "README.md" + if not readme_path.exists(): + self.log_error("README.md is missing.") + else: + content = readme_path.read_text(errors="ignore") + if len(content.strip()) < 50: + self.log_error("README.md is too short or empty. Provide current truth about the repo.") + + def run(self): + print(f"--- Gemini Linter: Auditing {self.repo_name} ---") + self.check_sidecar_boundary() + self.check_hardcoded_ips() + self.check_api_keys() + self.check_soul_canonical() + self.check_readme() + + if self.errors: + print(f"\n[FAILURE] Found {len(self.errors)} architectural violations:") + for err in self.errors: + print(f" - {err}") + return False + else: + print("\n[SUCCESS] Architecture is sound. Sovereignty maintained.") + return True + +def main(): + parser = argparse.ArgumentParser(description="Gemini Architecture Linter v2") + parser.add_argument("repo_path", nargs="?", default=".", help="Path to the repository to lint") + args = parser.parse_args() + + linter = Linter(args.repo_path) + success = linter.run() + sys.exit(0 if success else 1) + +if __name__ == "__main__": + main()