architectai-mcp / services /project_service.py
JawadBenali's picture
Initial deployment - ArchitectAI MCP
2ba8e82
import os
import ast
import logging
from pathlib import Path
from typing import List, Dict, Any
from services.architecture_service import ArchitectureVisitor
class ProjectAnalyzer:
"""
Orchestrates the analysis of an entire directory.
Aggregates results from multiple files into one structure.
"""
def __init__(self, root_path: Path):
self.root_path = root_path
self.aggregated_structure: List[Dict[str, Any]] = []
self.errors: List[str] = []
def analyze(self) -> List[Dict[str, Any]]:
"""
Walks through the directory, parses every .py file, and merges findings.
"""
logging.info(f"πŸ“‚ Starting project analysis at: {self.root_path}")
# 1. Walk through all files recursively
for file_path in self.root_path.rglob("*.py"):
# Skip virtual environments, git, and caches
# This is CRITICAL to avoid freezing on big projects
parts = file_path.parts
if any(p.startswith(".") or p in ["venv", "env", "__pycache__", "node_modules"] for p in parts):
continue
self._analyze_single_file(file_path)
logging.info(f"βœ… Project analysis complete. Found {len(self.aggregated_structure)} components.")
return self.aggregated_structure
def _analyze_single_file(self, file_path: Path):
"""
Parses a single file using ArchitectureVisitor and appends classes to the main list.
"""
try:
code = file_path.read_text(encoding='utf-8', errors='replace')
tree = ast.parse(code)
# Re-use our existing visitor!
visitor = ArchitectureVisitor()
visitor.visit(tree)
# Tag each component with its source file (Visual Context)
# This is useful so you know WHERE a class is defined
for item in visitor.structure:
item['source_file'] = file_path.name
# Optional: Prefix module name to avoid name collisions?
# For now, let's keep it simple.
# Merge into main list
self.aggregated_structure.extend(visitor.structure)
except Exception as e:
error_msg = f"Failed to parse {file_path.name}: {e}"
logging.warning(error_msg)
self.errors.append(error_msg)