Overview

Here’s a compact, ready‑to‑paste cross‑module dependency validator, aligned with your existing docs/tools pattern.

Save as:

docs/tools/validate_module_dependencies.py

Run from repo root:

python3 docs/tools/validate_module_dependencies.py

import os
import json
 
ROOT = "docs"
EXCLUDED = {"spine","_template","assets","images","tools"}
 
# ---------------------------------------------------------
# DISCOVERY
# ---------------------------------------------------------
def find_modules():
    modules = {}
    for root,dirs,files in os.walk(ROOT):
        if any(ex in root for ex in EXCLUDED): continue
        if "module.json" in files:
            path = os.path.join(root,"module.json")
            name = os.path.basename(root)
            modules[name] = path
    return modules
 
# ---------------------------------------------------------
# LOAD + EXTRACT DEPENDENCIES
# ---------------------------------------------------------
def get_dependencies(path):
    try:
        with open(path,"r",encoding="utf-8") as f:
            data = json.load(f)
    except:
        return []
 
    m = data.get("module",{})
    deps = m.get("dependencies",[])
    if isinstance(deps,dict):
        # allow {"modules":[...]} style
        deps = deps.get("modules",[])
    if not isinstance(deps,list):
        return []
    return [d for d in deps if isinstance(d,str)]
 
# ---------------------------------------------------------
# VALIDATION
# ---------------------------------------------------------
def validate_dependencies(modules):
    missing = []
    edges = []
 
    # build edges
    for name,path in modules.items():
        deps = get_dependencies(path)
        for d in deps:
            edges.append((name,d))
            if d not in modules:
                missing.append((name,d))
 
    return edges, missing
 
# simple cycle detection on name graph
def detect_cycles(edges):
    graph = {}
    for a,b in edges:
        graph.setdefault(a,[]).append(b)
 
    visited = set()
    stack = set()
    cycles = []
 
    def dfs(node, path):
        if node in stack:
            # cycle found
            idx = path.index(node)
            cycles.append(path[idx:])
            return
        if node in visited:
            return
        visited.add(node)
        stack.add(node)
        for nxt in graph.get(node,[]):
            dfs(nxt, path+[nxt])
        stack.remove(node)
 
    for n in graph.keys():
        dfs(n,[n])
 
    # dedupe cycles by sorted tuple
    seen = set()
    uniq = []
    for c in cycles:
        key = tuple(c)
        if key not in seen:
            seen.add(key)
            uniq.append(c)
    return uniq
 
# ---------------------------------------------------------
# MAIN
# ---------------------------------------------------------
def main():
    print("\n=== TriadicFrameworks Cross‑Module Dependency Validator ===\n")
 
    modules = find_modules()
    edges, missing = validate_dependencies(modules)
    cycles = detect_cycles(edges)
 
    # Missing targets
    if missing:
        print("❌ Missing dependency targets:")
        for src,dst in missing:
            print(f"   - {src}{dst} (not found)")
    else:
        print("✔ No missing dependency targets.")
 
    # Cycles
    if cycles:
        print("\n⚠ Cyclic dependencies detected:")
        for c in cycles:
            chain = " → ".join(c + [c[0]])
            print(f"   - {chain}")
    else:
        print("\n✔ No cyclic dependencies detected.")
 
    print("\n=== Summary ===")
    print(f"Modules scanned: {len(modules)}")
    print(f"Dependency edges: {len(edges)}")
    print(f"Missing targets: {len(missing)}")
    print(f"Cycles: {len(cycles)}\n")
 
    if not missing and not cycles:
        print("✨ Cross‑module dependencies are consistent and acyclic.")
    else:
        print("⚠ Review missing targets and cycles for canon consistency.")
 
if __name__=="__main__":
    main()

Was this page helpful?