#!/usr/bin/env python3 import argparse import re import subprocess import sys from pathlib import Path DEFAULT_EXCLUDED_PATHS = { "LICENSE", "Cargo.lock", "apps/ui/package-lock.json", "scripts/check-community-scope.py", "tests/unit/test_check_community_scope.py", } DEFAULT_EXCLUDED_PREFIXES = { "target/", "apps/ui/node_modules/", "apps/ui/dist/", } FORBIDDEN_PATTERNS = [ ("enterprise", re.compile(r"\benterprise\b", re.IGNORECASE)), ("cloud", re.compile(r"\bcloud\b", re.IGNORECASE)), ("commercial", re.compile(r"\bcommercial\b|коммер", re.IGNORECASE)), ("cloud-russian", re.compile(r"облач", re.IGNORECASE)), ("graphql", re.compile(r"\bgraphql\b", re.IGNORECASE)), ("grpc", re.compile(r"\bgrpc\b", re.IGNORECASE)), ("soap", re.compile(r"\bsoap\b", re.IGNORECASE)), ("websocket", re.compile(r"\bwebsocket\b", re.IGNORECASE)), ("short-lived-token", re.compile(r"short[-_ ]?lived", re.IGNORECASE)), ("one-time-token", re.compile(r"one[-_ ]?time", re.IGNORECASE)), ("sso", re.compile(r"\bsso\b", re.IGNORECASE)), ("two-factor", re.compile(r"two[-_ ]?factor|\b2fa\b|\bmfa\b", re.IGNORECASE)), ("billing", re.compile(r"\bbilling\b", re.IGNORECASE)), ("tenant", re.compile(r"\btenant\b", re.IGNORECASE)), ("stream-session", re.compile(r"stream session", re.IGNORECASE)), ("async-job", re.compile(r"async job", re.IGNORECASE)), ("mcp-auth", re.compile(r"mcp-auth", re.IGNORECASE)), ("protocol-capabilities", re.compile(r"protocol-capabilities", re.IGNORECASE)), ("machine-token", re.compile(r"machine token", re.IGNORECASE)), ("token-issuer", re.compile(r"token issuer", re.IGNORECASE)), ("request-variables", re.compile(r"request\.variables", re.IGNORECASE)), ] def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Fail if Community repository contains out-of-scope product markers." ) parser.add_argument( "--root", type=Path, default=Path(__file__).resolve().parents[1], help="Repository root. Defaults to parent of scripts/.", ) parser.add_argument( "--files", nargs="*", help="Optional explicit file list, relative to root. Defaults to git ls-files.", ) return parser.parse_args() def git_tracked_files(root: Path) -> list[str]: result = subprocess.run( ["git", "ls-files"], cwd=root, check=True, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) return [line for line in result.stdout.splitlines() if line] def should_skip_path(path: str) -> bool: if path in DEFAULT_EXCLUDED_PATHS: return True return any(path.startswith(prefix) for prefix in DEFAULT_EXCLUDED_PREFIXES) def is_binary(data: bytes) -> bool: return b"\0" in data[:4096] def scan_file(root: Path, relative_path: str) -> list[str]: if should_skip_path(relative_path): return [] path = root / relative_path if not path.is_file(): return [] data = path.read_bytes() if is_binary(data): return [] text = data.decode("utf-8", errors="replace") findings: list[str] = [] for line_no, line in enumerate(text.splitlines(), start=1): for label, pattern in FORBIDDEN_PATTERNS: if pattern.search(line): findings.append(f"{relative_path}:{line_no}: forbidden marker `{label}`") return findings def main() -> int: args = parse_args() root = args.root.resolve() files = args.files if args.files is not None else git_tracked_files(root) findings: list[str] = [] for relative_path in sorted(files): findings.extend(scan_file(root, relative_path)) if findings: print("Community scope check failed:", file=sys.stderr) for finding in findings: print(f"error: {finding}", file=sys.stderr) return 1 print(f"Community scope check passed ({len(files)} tracked files scanned)") return 0 if __name__ == "__main__": raise SystemExit(main())