From 4e90bd776ee3cf2b328bb8d5c2bb4951b9b5acab Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Fri, 30 Jan 2026 17:19:20 +0000 Subject: [PATCH] fix: resolve CI type checking and lint failures --- app/depnav/src/depnav/detector.py | 110 ++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 app/depnav/src/depnav/detector.py diff --git a/app/depnav/src/depnav/detector.py b/app/depnav/src/depnav/detector.py new file mode 100644 index 0000000..e88b7cb --- /dev/null +++ b/app/depnav/src/depnav/detector.py @@ -0,0 +1,110 @@ +"""Circular dependency detection utilities.""" + +from pathlib import Path +from typing import Any, Optional + +from .graph import DependencyGraph + + +class CycleDetector: + """Detector for circular dependencies in codebases.""" + + def __init__(self, graph: DependencyGraph): + self.graph = graph + + def detect_all_cycles(self) -> list[list[Path]]: + """Detect all cycles in the dependency graph.""" + return self.graph.detect_cycles() + + def detect_cycles_for_file(self, file_path: Path) -> list[list[Path]]: + """Detect cycles that include a specific file.""" + all_cycles = self.detect_all_cycles() + return [ + cycle for cycle in all_cycles if file_path in cycle + ] + + def get_cyclic_files(self) -> list[Path]: + """Get all files that are part of a cycle.""" + cyclic_files = set() + for cycle in self.detect_all_cycles(): + cyclic_files.update(cycle) + return list(cyclic_files) + + def is_cyclic(self, file_path: Path) -> bool: + """Check if a specific file is part of a cycle.""" + return file_path in self.get_cyclic_files() + + def get_cycle_chains(self) -> dict[Path, list[list[Path]]]: + """Get all cycles grouped by their involved files.""" + chains: dict[Path, list[list[Path]]] = {} + for cycle in self.detect_all_cycles(): + for file in cycle: + if file not in chains: + chains[file] = [] + chains[file].append(cycle) + return chains + + def get_report(self) -> dict[str, Any]: + """Generate a comprehensive cycle detection report.""" + cycles = self.detect_all_cycles() + cyclic_files = self.get_cyclic_files() + + report = { + "total_cycles": len(cycles), + "cyclic_files": len(cyclic_files), + "cyclic_file_names": [str(f) for f in cyclic_files], + "cycles": [ + [str(f) for f in cycle] for cycle in cycles + ], + "severity": self._calculate_severity(cycles), + } + + return report + + def _calculate_severity( + self, cycles: list[list[Path]] + ) -> str: + """Calculate the severity of the cyclic dependencies.""" + if not cycles: + return "none" + + max_cycle_length = max(len(c) for c in cycles) if cycles else 0 + + if max_cycle_length > 10: + return "critical" + elif max_cycle_length > 5: + return "high" + elif len(cycles) > 5: + return "medium" + return "low" + + def export_cycle_report(self, path: Path) -> None: + """Export the cycle report to a file.""" + import json + + report = self.get_report() + with open(path, "w") as f: + json.dump(report, f, indent=2) + + +def find_cyclic_dependencies( + project_root: Path, + extensions: Optional[list[str]] = None, +) -> list[list[Path]]: + """Convenience function to find cyclic dependencies.""" + graph = DependencyGraph(project_root) + graph.build_from_directory(extensions=extensions) + + detector = CycleDetector(graph) + return detector.detect_all_cycles() + + +def get_cyclic_file_report( + project_root: Path, +) -> dict[str, Any]: + """Generate a report on cyclic dependencies for a project.""" + graph = DependencyGraph(project_root) + graph.build_from_directory() + + detector = CycleDetector(graph) + return detector.get_report()