Skip to content

examiner

Full name: tenets.core.examiner.examiner

examiner

Main examiner module for comprehensive code analysis.

This module provides the core examination functionality, orchestrating various analysis components to provide deep insights into codebases. It coordinates between metrics calculation, complexity analysis, ownership tracking, and hotspot detection to deliver comprehensive examination results.

The Examiner class serves as the main entry point for all examination operations, handling file discovery, analysis orchestration, and result aggregation.

Classes

ExaminationResultdataclass

Python
ExaminationResult(
    root_path: Path,
    total_files: int = 0,
    total_lines: int = 0,
    languages: List[str] = list(),
    files: List[Any] = list(),
    metrics: Optional[MetricsReport] = None,
    complexity: Optional[ComplexityReport] = None,
    ownership: Optional[OwnershipReport] = None,
    hotspots: Optional[HotspotReport] = None,
    git_analysis: Optional[Any] = None,
    summary: Dict[str, Any] = dict(),
    timestamp: datetime = datetime.now(),
    duration: float = 0.0,
    config: Optional[TenetsConfig] = None,
    errors: List[str] = list(),
    excluded_files: List[str] = list(),
    excluded_count: int = 0,
    ignored_patterns: List[str] = list(),
)

Comprehensive examination results for a codebase.

This dataclass aggregates all examination findings including metrics, complexity analysis, ownership patterns, and detected hotspots. It provides a complete picture of codebase health and structure.

ATTRIBUTEDESCRIPTION
root_path

Root directory that was examined

TYPE:Path

total_files

Total number of files analyzed

TYPE:int

total_lines

Total lines of code across all files

TYPE:int

languages

List of programming languages detected

TYPE:List[str]

files

List of analyzed file objects

TYPE:List[Any]

metrics

Detailed metrics report

TYPE:Optional[MetricsReport]

complexity

Complexity analysis report

TYPE:Optional[ComplexityReport]

ownership

Code ownership report

TYPE:Optional[OwnershipReport]

hotspots

Detected hotspot report

TYPE:Optional[HotspotReport]

git_analysis

Git repository analysis if available

TYPE:Optional[Any]

summary

High-level summary statistics

TYPE:Dict[str, Any]

timestamp

When examination was performed

TYPE:datetime

duration

How long examination took in seconds

TYPE:float

config

Configuration used for examination

TYPE:Optional[TenetsConfig]

errors

Any errors encountered during examination

TYPE:List[str]

Attributes
has_issuesproperty
Python
has_issues: bool

Check if examination found any issues.

RETURNSDESCRIPTION
bool

True if any issues were detected

TYPE:bool

health_scoreproperty
Python
health_score: float

Calculate overall codebase health score.

Computes a health score from 0-100 based on various metrics including complexity, test coverage, documentation, and hotspots.

RETURNSDESCRIPTION
float

Health score between 0 and 100

TYPE:float

Methods:
to_dict
Python
to_dict() -> Dict[str, Any]

Convert examination results to dictionary.

Serializes all examination data into a dictionary format suitable for JSON export or further processing. Handles nested objects and datetime serialization.

RETURNSDESCRIPTION
Dict[str, Any]

Dict[str, Any]: Dictionary representation of examination results

Source code in tenets/core/examiner/examiner.py
Python
def to_dict(self) -> Dict[str, Any]:
    """Convert examination results to dictionary.

    Serializes all examination data into a dictionary format suitable
    for JSON export or further processing. Handles nested objects and
    datetime serialization.

    Returns:
        Dict[str, Any]: Dictionary representation of examination results
    """
    return {
        "root_path": str(self.root_path),
        "total_files": self.total_files,
        "total_lines": self.total_lines,
        "languages": self.languages,
        "metrics": self.metrics.to_dict() if self.metrics else None,
        "complexity": self.complexity.to_dict() if self.complexity else None,
        "ownership": self.ownership.to_dict() if self.ownership else None,
        "hotspots": self.hotspots.to_dict() if self.hotspots else None,
        "summary": self.summary,
        "timestamp": self.timestamp.isoformat(),
        "duration": self.duration,
        "errors": self.errors,
        "excluded_files": self.excluded_files,
        "excluded_count": self.excluded_count,
        "ignored_patterns": self.ignored_patterns,
    }
to_json
Python
to_json(indent: int = 2) -> str

Convert examination results to JSON string.

PARAMETERDESCRIPTION
indent

Number of spaces for JSON indentation

TYPE:intDEFAULT:2

RETURNSDESCRIPTION
str

JSON representation of examination results

TYPE:str

Source code in tenets/core/examiner/examiner.py
Python
def to_json(self, indent: int = 2) -> str:
    """Convert examination results to JSON string.

    Args:
        indent: Number of spaces for JSON indentation

    Returns:
        str: JSON representation of examination results
    """
    return json.dumps(self.to_dict(), indent=indent, default=str)

Examiner

Python
Examiner(config: TenetsConfig)

Main orchestrator for code examination operations.

The Examiner class coordinates all examination activities, managing the analysis pipeline from file discovery through final reporting. It integrates various analyzers and trackers to provide comprehensive codebase insights.

This class serves as the primary API for examination functionality, handling configuration, error recovery, and result aggregation.

ATTRIBUTEDESCRIPTION
config

Configuration object

logger

Logger instance

analyzer

Code analyzer instance

TYPE:CodeAnalyzer

scanner

File scanner instance

metrics_calculator

Metrics calculation instance

complexity_analyzer

Complexity analysis instance

ownership_tracker

Ownership tracking instance

hotspot_detector

Hotspot detection instance

Initialize the Examiner with configuration.

Sets up all required components for examination including analyzers, scanners, and specialized examination modules.

PARAMETERDESCRIPTION
config

TenetsConfig instance with examination settings

TYPE:TenetsConfig

Source code in tenets/core/examiner/examiner.py
Python
def __init__(self, config: TenetsConfig):
    """Initialize the Examiner with configuration.

    Sets up all required components for examination including
    analyzers, scanners, and specialized examination modules.

    Args:
        config: TenetsConfig instance with examination settings
    """
    self.config = config
    self.logger = get_logger(__name__)

    # Initialize core components
    self._analyzer = CodeAnalyzer(config)
    self.scanner = FileScanner(config)

    # Initialize examination components
    self.metrics_calculator = MetricsCalculator(config)
    self.complexity_analyzer = ComplexityAnalyzer(config)
    self.ownership_tracker = OwnershipTracker(config)
    self.hotspot_detector = HotspotDetector(config)

    # Initialize cache for file analysis results
    try:
        from tenets.core.cache import CacheManager

        self.cache = CacheManager(config)
    except Exception:
        # If cache manager fails, continue without caching
        self.cache = None
        self.logger.debug("Cache manager not available, proceeding without cache")

    self.logger.debug("Examiner initialized with config")
Methods:
examine_project
Python
examine_project(
    path: Path,
    deep: bool = False,
    include_git: bool = True,
    include_metrics: bool = True,
    include_complexity: bool = True,
    include_ownership: bool = True,
    include_hotspots: bool = True,
    include_patterns: Optional[List[str]] = None,
    exclude_patterns: Optional[List[str]] = None,
    max_files: Optional[int] = None,
) -> ExaminationResult

Perform comprehensive project examination.

Conducts a full examination of the specified project, running all requested analysis types and aggregating results into a comprehensive report.

PARAMETERDESCRIPTION
path

Path to project directory

TYPE:Path

deep

Whether to perform deep AST-based analysis

TYPE:boolDEFAULT:False

include_git

Whether to include git repository analysis

TYPE:boolDEFAULT:True

include_metrics

Whether to calculate code metrics

TYPE:boolDEFAULT:True

include_complexity

Whether to analyze code complexity

TYPE:boolDEFAULT:True

include_ownership

Whether to track code ownership

TYPE:boolDEFAULT:True

include_hotspots

Whether to detect code hotspots

TYPE:boolDEFAULT:True

include_patterns

File patterns to include (e.g., ['*.py'])

TYPE:Optional[List[str]]DEFAULT:None

exclude_patterns

File patterns to exclude (e.g., ['test_*'])

TYPE:Optional[List[str]]DEFAULT:None

max_files

Maximum number of files to analyze

TYPE:Optional[int]DEFAULT:None

RETURNSDESCRIPTION
ExaminationResult

Comprehensive examination findings

TYPE:ExaminationResult

RAISESDESCRIPTION
ValueError

If path doesn't exist or isn't a directory

Example

examiner = Examiner(config) result = examiner.examine_project( ... Path("./src"), ... deep=True, ... include_git=True ... ) print(f"Health score: {result.health_score}")

Source code in tenets/core/examiner/examiner.py
Python
def examine_project(
    self,
    path: Path,
    deep: bool = False,
    include_git: bool = True,
    include_metrics: bool = True,
    include_complexity: bool = True,
    include_ownership: bool = True,
    include_hotspots: bool = True,
    include_patterns: Optional[List[str]] = None,
    exclude_patterns: Optional[List[str]] = None,
    max_files: Optional[int] = None,
) -> ExaminationResult:
    """Perform comprehensive project examination.

    Conducts a full examination of the specified project, running
    all requested analysis types and aggregating results into a
    comprehensive report.

    Args:
        path: Path to project directory
        deep: Whether to perform deep AST-based analysis
        include_git: Whether to include git repository analysis
        include_metrics: Whether to calculate code metrics
        include_complexity: Whether to analyze code complexity
        include_ownership: Whether to track code ownership
        include_hotspots: Whether to detect code hotspots
        include_patterns: File patterns to include (e.g., ['*.py'])
        exclude_patterns: File patterns to exclude (e.g., ['test_*'])
        max_files: Maximum number of files to analyze

    Returns:
        ExaminationResult: Comprehensive examination findings

    Raises:
        ValueError: If path doesn't exist or isn't a directory

    Example:
        >>> examiner = Examiner(config)
        >>> result = examiner.examine_project(
        ...     Path("./src"),
        ...     deep=True,
        ...     include_git=True
        ... )
        >>> print(f"Health score: {result.health_score}")
    """
    start_time = datetime.now()

    # Validate path
    path = Path(path).resolve()
    if not path.exists():
        raise ValueError(f"Path does not exist: {path}")
    if not path.is_dir():
        raise ValueError(f"Path is not a directory: {path}")

    self.logger.info(f"Starting project examination: {path}")

    # Initialize result
    result = ExaminationResult(root_path=path, config=self.config, timestamp=start_time)

    try:
        # Step 1: Discover files
        self.logger.debug("Discovering files...")
        files = self._discover_files(
            path,
            include_patterns=include_patterns,
            exclude_patterns=exclude_patterns,
            max_files=max_files,
        )

        # Track excluded files and patterns for reporting
        # NOTE: Skip expensive rglob for performance - it was taking minutes on large projects
        # Just store the patterns that were used for exclusion
        result.excluded_files = []  # Skip tracking individual files for performance
        result.excluded_count = 0  # Will be estimated based on patterns
        result.ignored_patterns = exclude_patterns or []

        if not files:
            self.logger.warning("No files found to examine")
            result.errors.append("No files found matching criteria")
            return result

        # Step 2: Analyze files
        self.logger.debug(f"Analyzing {len(files)} files...")
        analyzed_files = self._analyze_files(files, deep=deep)
        result.files = analyzed_files

        # Extract basic stats
        result.total_files = len(analyzed_files)
        result.total_lines = sum(f.lines for f in analyzed_files if hasattr(f, "lines"))
        result.languages = self._extract_languages(analyzed_files)

        # Step 3: Git analysis (if requested)
        if include_git and self._is_git_repo(path):
            self.logger.debug("Performing git analysis...")
            result.git_analysis = self._analyze_git(path)

        # Step 4: Calculate metrics (if requested)
        if include_metrics:
            self.logger.debug("Calculating metrics...")
            result.metrics = self.metrics_calculator.calculate(analyzed_files)

        # Step 5: Analyze complexity (if requested)
        if include_complexity:
            self.logger.debug("Analyzing complexity...")
            result.complexity = self.complexity_analyzer.analyze(
                analyzed_files,
                threshold=self.config.ranking.threshold * 100,  # Convert to complexity scale
            )

        # Step 6: Track ownership (if requested)
        if include_ownership and include_git and self._is_git_repo(path):
            self.logger.debug("Tracking ownership...")
            result.ownership = self.ownership_tracker.track(path)

        # Step 7: Detect hotspots (if requested)
        if include_hotspots and include_git and self._is_git_repo(path):
            self.logger.debug("Detecting hotspots...")
            result.hotspots = self.hotspot_detector.detect(path, files=analyzed_files)

        # Step 8: Generate summary
        result.summary = self._generate_summary(result)

    except Exception as e:
        self.logger.error(f"Error during examination: {e}")
        result.errors.append(str(e))

    # Calculate duration
    result.duration = (datetime.now() - start_time).total_seconds()

    self.logger.info(
        f"Examination complete: {result.total_files} files, "
        f"{result.duration:.2f}s, health score: {result.health_score:.1f}"
    )

    return result
examine_file
Python
examine_file(file_path: Path, deep: bool = False) -> Dict[str, Any]

Examine a single file in detail.

Performs focused analysis on a single file, extracting all available metrics, complexity measures, and structural information.

PARAMETERDESCRIPTION
file_path

Path to the file to examine

TYPE:Path

deep

Whether to perform deep AST-based analysis

TYPE:boolDEFAULT:False

RETURNSDESCRIPTION
Dict[str, Any]

Dict[str, Any]: Detailed file examination results

RAISESDESCRIPTION
ValueError

If file doesn't exist or isn't a file

Example

examiner = Examiner(config) result = examiner.examine_file(Path("main.py"), deep=True) print(f"Complexity: {result['complexity']}")

Source code in tenets/core/examiner/examiner.py
Python
def examine_file(self, file_path: Path, deep: bool = False) -> Dict[str, Any]:
    """Examine a single file in detail.

    Performs focused analysis on a single file, extracting all
    available metrics, complexity measures, and structural information.

    Args:
        file_path: Path to the file to examine
        deep: Whether to perform deep AST-based analysis

    Returns:
        Dict[str, Any]: Detailed file examination results

    Raises:
        ValueError: If file doesn't exist or isn't a file

    Example:
        >>> examiner = Examiner(config)
        >>> result = examiner.examine_file(Path("main.py"), deep=True)
        >>> print(f"Complexity: {result['complexity']}")
    """
    file_path = Path(file_path).resolve()

    if not file_path.exists():
        raise ValueError(f"File does not exist: {file_path}")
    if not file_path.is_file():
        raise ValueError(f"Path is not a file: {file_path}")

    self.logger.debug(f"Examining file: {file_path}")

    # Analyze the file
    analysis = self.analyzer.analyze_file(str(file_path), deep=deep)

    # Calculate file-specific metrics
    file_metrics = self.metrics_calculator.calculate_file_metrics(analysis)

    # Get complexity details
    complexity_details = None
    if hasattr(analysis, "complexity"):
        complexity_details = self.complexity_analyzer.analyze_file(analysis)

    # Build result
    result = {
        "path": str(file_path),
        "name": file_path.name,
        "size": file_path.stat().st_size,
        "lines": getattr(analysis, "lines", 0),
        "language": getattr(analysis, "language", "unknown"),
        "complexity": complexity_details,
        "metrics": file_metrics,
        "imports": getattr(analysis, "imports", []),
        "functions": getattr(analysis, "functions", []),
        "classes": getattr(analysis, "classes", []),
        "analysis": analysis,
    }

    return result

Functions:

examine_directory

Python
examine_directory(
    path: Path, config: Optional[TenetsConfig] = None, **kwargs: Any
) -> ExaminationResult

Convenience function to examine a directory.

Creates an Examiner instance and performs a full examination of the specified directory with provided options.

PARAMETERDESCRIPTION
path

Directory path to examine

TYPE:Path

config

Optional configuration (uses defaults if None)

TYPE:Optional[TenetsConfig]DEFAULT:None

**kwargs

Additional arguments passed to examine_project()

TYPE:AnyDEFAULT:{}

RETURNSDESCRIPTION
ExaminationResult

Examination findings

TYPE:ExaminationResult

Example

result = examine_directory( ... Path("./src"), ... deep=True, ... include_git=True ... ) print(f"Found {result.total_files} files")

Source code in tenets/core/examiner/examiner.py
Python
def examine_directory(
    path: Path, config: Optional[TenetsConfig] = None, **kwargs: Any
) -> ExaminationResult:
    """Convenience function to examine a directory.

    Creates an Examiner instance and performs a full examination of
    the specified directory with provided options.

    Args:
        path: Directory path to examine
        config: Optional configuration (uses defaults if None)
        **kwargs: Additional arguments passed to examine_project()

    Returns:
        ExaminationResult: Examination findings

    Example:
        >>> result = examine_directory(
        ...     Path("./src"),
        ...     deep=True,
        ...     include_git=True
        ... )
        >>> print(f"Found {result.total_files} files")
    """
    if config is None:
        config = TenetsConfig()

    examiner = Examiner(config)
    return examiner.examine_project(path, **kwargs)