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