127 lines
5.4 KiB
Python
127 lines
5.4 KiB
Python
#!/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()
|