Initial curated ITWorx_MCP_Hub source from 368fba691ea8d23403b4d56b53b5ec670d7c5020
This commit is contained in:
commit
cb5be898ea
663 files changed
+82674
No files matched your search
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check the repository dependency direction without importing application code."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import ast
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
SOURCE_ROOTS = {"apps", "services", "connectors", "packages"}
|
||||
SOURCE_SUFFIXES = {".py", ".pyi", ".js", ".jsx", ".ts", ".tsx"}
|
||||
IGNORED_PARTS = {"__pycache__", ".venv", "node_modules", "dist"}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Violation:
|
||||
path: Path
|
||||
line: int
|
||||
message: str
|
||||
|
||||
def format(self, root: Path) -> str:
|
||||
return f"{self.path.relative_to(root).as_posix()}:{self.line}: {self.message}"
|
||||
|
||||
|
||||
def _owner(path: Path, root: Path) -> tuple[str, str] | None:
|
||||
relative = path.relative_to(root)
|
||||
if len(relative.parts) < 2 or relative.parts[0] not in SOURCE_ROOTS:
|
||||
return None
|
||||
return relative.parts[0], relative.parts[1].replace("-", "_")
|
||||
|
||||
|
||||
def _target(module: str) -> tuple[str, str] | None:
|
||||
normalized = module.replace("-", "_")
|
||||
parts = normalized.split(".")
|
||||
if len(parts) < 2 or parts[0] not in SOURCE_ROOTS:
|
||||
return None
|
||||
return parts[0], parts[1]
|
||||
|
||||
|
||||
def _allowed(owner: tuple[str, str], target: tuple[str, str]) -> bool:
|
||||
owner_root, owner_name = owner
|
||||
target_root, target_name = target
|
||||
if owner_root == "packages":
|
||||
if owner_name == "hub_contracts":
|
||||
return target_root == "packages" and target_name == "hub_contracts"
|
||||
if owner_name in {"hub_core", "hub_policy", "connector_kit"}:
|
||||
return target_root == "packages" and target_name in {owner_name, "hub_contracts"}
|
||||
return target_root == "packages"
|
||||
if owner_root == "connectors":
|
||||
return target_root == "packages"
|
||||
if owner_root == "services":
|
||||
if owner_name == "runtime":
|
||||
return target_root in {"packages", "services", "connectors"}
|
||||
return target_root == "packages" or target == ("services", owner_name)
|
||||
if owner_root == "apps":
|
||||
return target_root == "packages" or target == ("apps", owner_name)
|
||||
return True
|
||||
|
||||
|
||||
def _python_imports(path: Path) -> list[tuple[int, str]]:
|
||||
try:
|
||||
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
||||
except (OSError, SyntaxError):
|
||||
return []
|
||||
imports: list[tuple[int, str]] = []
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Import):
|
||||
imports.extend((node.lineno, alias.name) for alias in node.names)
|
||||
elif isinstance(node, ast.ImportFrom) and node.level == 0 and node.module:
|
||||
imports.append((node.lineno, node.module))
|
||||
return imports
|
||||
|
||||
|
||||
def _js_imports(path: Path) -> list[tuple[int, str]]:
|
||||
pattern = re.compile(r"(?:from\s+|import\s*\(?\s*|require\(\s*)['\"]([^'\"]+)")
|
||||
return [
|
||||
(line_no, module)
|
||||
for line_no, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1)
|
||||
for module in pattern.findall(line)
|
||||
]
|
||||
|
||||
|
||||
def find_violations(root: Path, paths: list[Path] | None = None) -> list[Violation]:
|
||||
candidates = paths or [
|
||||
path
|
||||
for source_root in SOURCE_ROOTS
|
||||
for path in (root / source_root).rglob("*")
|
||||
if path.is_file()
|
||||
and path.suffix in SOURCE_SUFFIXES
|
||||
and not (set(path.parts) & IGNORED_PARTS)
|
||||
]
|
||||
violations: list[Violation] = []
|
||||
for path in candidates:
|
||||
owner = _owner(path, root)
|
||||
if owner is None:
|
||||
continue
|
||||
imports = _python_imports(path) if path.suffix in {".py", ".pyi"} else _js_imports(path)
|
||||
for line, module in imports:
|
||||
target = _target(module)
|
||||
if target and not _allowed(owner, target):
|
||||
violations.append(
|
||||
Violation(
|
||||
path, line, f"forbidden dependency {module!r} from {owner[0]}/{owner[1]}"
|
||||
)
|
||||
)
|
||||
return violations
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Check ITWorx MCP Hub import boundaries.")
|
||||
parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parents[1])
|
||||
args = parser.parse_args()
|
||||
root = args.root.resolve()
|
||||
violations = find_violations(root)
|
||||
if violations:
|
||||
for violation in violations:
|
||||
print(violation.format(root), file=sys.stderr)
|
||||
return 1
|
||||
print("Import-boundary check: PASS")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user