kyorlin2001/code-analysis-tool
0
1from __future__ import annotations2 3from dataclasses import dataclass4from pathlib import Path5from typing import Any6 7from models.analysis_state import AnalysisState8 9 10@dataclass11class DependencyOutput:12 """13 Result of dependency analysis.14 """15 16 imports_by_language: dict[str, int]17 package_manifests: list[str]18 notes: list[str]19 findings: list[dict[str, Any]]20 21 22class DependencyAgent:23 """24 Performs lightweight dependency analysis.25 26 Responsibilities:27 - detect dependency manifest files28 - estimate import/package usage29 - produce early findings for downstream reporting30 """31 32 MANIFEST_FILES = {33 "requirements.txt",34 "pyproject.toml",35 "Pipfile",36 "Pipfile.lock",37 "setup.py",38 "package.json",39 "package-lock.json",40 "pnpm-lock.yaml",41 "yarn.lock",42 "go.mod",43 "Cargo.toml",44 "pom.xml",45 "build.gradle",46 "build.gradle.kts",47 "composer.json",48 }49 50 def run(self, state: AnalysisState) -> DependencyOutput:51 package_manifests = self._find_manifests(state.files)52 imports_by_language = self._estimate_imports(state.files)53 54 findings: list[dict[str, Any]] = []55 notes: list[str] = []56 57 if package_manifests:58 notes.append(f"Found {len(package_manifests)} dependency manifest file(s).")59 else:60 notes.append("No common dependency manifest files were detected.")61 62 if "Python" in imports_by_language:63 findings.append(64 {65 "severity": "info",66 "message": "Python source files detected; check for unused or stale dependencies.",67 }68 )69 70 output = DependencyOutput(71 imports_by_language=imports_by_language,72 package_manifests=package_manifests,73 notes=notes,74 findings=findings,75 )76 77 state.summaries["dependencies"] = self._format_summary(output)78 state.agent_outputs["dependencies"] = output79 state.findings.extend(findings)80 return output81 82 def _find_manifests(self, files: list[str]) -> list[str]:83 return [f for f in files if Path(f).name in self.MANIFEST_FILES]84 85 def _estimate_imports(self, files: list[str]) -> dict[str, int]:86 counts: dict[str, int] = {}87 88 for file_path in files:89 suffix = Path(file_path).suffix.lower()90 if suffix == ".py":91 counts["Python"] = counts.get("Python", 0) + 192 elif suffix in {".js", ".jsx"}:93 counts["JavaScript"] = counts.get("JavaScript", 0) + 194 elif suffix in {".ts", ".tsx"}:95 counts["TypeScript"] = counts.get("TypeScript", 0) + 196 elif suffix in {".java", ".kt"}:97 counts["JVM"] = counts.get("JVM", 0) + 198 elif suffix == ".go":99 counts["Go"] = counts.get("Go", 0) + 1100 elif suffix == ".rs":101 counts["Rust"] = counts.get("Rust", 0) + 1102 103 return counts104 105 def _format_summary(self, output: DependencyOutput) -> str:106 lines = ["Dependency analysis:"]107 if output.package_manifests:108 lines.append("Manifests:")109 for manifest in output.package_manifests:110 lines.append(f"- {manifest}")111 else:112 lines.append("No dependency manifests found.")113 114 if output.imports_by_language:115 lines.append("Source file counts by language:")116 for language, count in sorted(output.imports_by_language.items()):117 lines.append(f"- {language}: {count}")118 119 lines.extend(output.notes)120 return "\n".join(lines)