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¶
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.
| ATTRIBUTE | DESCRIPTION |
|---|---|
root_path | Root directory that was examined TYPE: |
total_files | Total number of files analyzed TYPE: |
total_lines | Total lines of code across all files TYPE: |
languages | List of programming languages detected |
files | List of analyzed file objects |
metrics | Detailed metrics report TYPE: |
complexity | Complexity analysis report TYPE: |
ownership | Code ownership report TYPE: |
hotspots | Detected hotspot report TYPE: |
git_analysis | Git repository analysis if available |
summary | High-level summary statistics |
timestamp | When examination was performed TYPE: |
duration | How long examination took in seconds TYPE: |
config | Configuration used for examination TYPE: |
errors | Any errors encountered during examination |
Attributes¶
has_issuesproperty¶
Check if examination found any issues.
| RETURNS | DESCRIPTION |
|---|---|
bool | True if any issues were detected TYPE: |
health_scoreproperty¶
Calculate overall codebase health score.
Computes a health score from 0-100 based on various metrics including complexity, test coverage, documentation, and hotspots.
| RETURNS | DESCRIPTION |
|---|---|
float | Health score between 0 and 100 TYPE: |
Methods:¶
to_dict¶
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 | DESCRIPTION |
|---|---|
Dict[str, Any] | Dict[str, Any]: Dictionary representation of examination results |
Source code in tenets/core/examiner/examiner.py
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,
}
Examiner¶
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.
| ATTRIBUTE | DESCRIPTION |
|---|---|
config | Configuration object |
logger | Logger instance |
analyzer | Code analyzer instance TYPE: |
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.
| PARAMETER | DESCRIPTION |
|---|---|
config | TenetsConfig instance with examination settings TYPE: |
Source code in tenets/core/examiner/examiner.py
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¶
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.
| PARAMETER | DESCRIPTION |
|---|---|
path | Path to project directory TYPE: |
deep | Whether to perform deep AST-based analysis TYPE: |
include_git | Whether to include git repository analysis TYPE: |
include_metrics | Whether to calculate code metrics TYPE: |
include_complexity | Whether to analyze code complexity TYPE: |
include_ownership | Whether to track code ownership TYPE: |
include_hotspots | Whether to detect code hotspots TYPE: |
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 | DESCRIPTION |
|---|---|
ExaminationResult | Comprehensive examination findings TYPE: |
| RAISES | DESCRIPTION |
|---|---|
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
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¶
Examine a single file in detail.
Performs focused analysis on a single file, extracting all available metrics, complexity measures, and structural information.
| PARAMETER | DESCRIPTION |
|---|---|
file_path | Path to the file to examine TYPE: |
deep | Whether to perform deep AST-based analysis TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
Dict[str, Any] | Dict[str, Any]: Detailed file examination results |
| RAISES | DESCRIPTION |
|---|---|
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
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¶
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.
| PARAMETER | DESCRIPTION |
|---|---|
path | Directory path to examine TYPE: |
config | Optional configuration (uses defaults if None) TYPE: |
**kwargs | Additional arguments passed to examine_project() TYPE: |
| RETURNS | DESCRIPTION |
|---|---|
ExaminationResult | Examination findings TYPE: |
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
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)