Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ __pycache__/
# Testing
.coverage
coverage.xml
coverage.json
htmlcov/
.pytest_cache/
*.py[cod]
Expand Down
13 changes: 13 additions & 0 deletions guardian/meta/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""
Guardian Meta-Analysis Module

Self-analysis capabilities where Guardian uses its own LJPW framework
to analyze and improve itself.
"""

from guardian.meta.self_analyzer import SelfAnalyzer, CodeHealthReport

__all__ = [
'SelfAnalyzer',
'CodeHealthReport',
]
363 changes: 363 additions & 0 deletions guardian/meta/self_analyzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,363 @@
"""
Self-Analysis System for Guardian

Maps codebase health metrics to LJPW coordinates and uses the
intervention engine to suggest improvements.

This is Guardian analyzing Guardian - a meta-application of the framework.
"""

import os
import subprocess
import json
import logging
from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple
from pathlib import Path

from guardian.core.coordinates import Coordinates
from guardian.core.intervention import InterventionEngine

logger = logging.getLogger(__name__)


@dataclass
class CodeHealthReport:
"""Report of codebase health metrics"""

# Coverage metrics
test_coverage: float
coverage_by_module: Dict[str, float]

# Test metrics
total_tests: int
passing_tests: int
failing_tests: int
test_pass_rate: float

# Code quality
lines_of_code: int
documented_functions: int
documentation_ratio: float

# Complexity
average_complexity: float
high_complexity_functions: List[str]

# LJPW coordinates
coordinates: Coordinates

# Intervention recommendations
threat_level: str
concerns: List[str]
recommendations: List[Dict]


class SelfAnalyzer:
"""
Analyzes Guardian's own codebase health using LJPW framework

This meta-analysis system treats the codebase as an LJPW system
and uses Guardian's own intervention engine to suggest improvements.
"""

def __init__(self, project_root: Optional[Path] = None):
"""
Initialize self-analyzer

Args:
project_root: Root directory of the project (auto-detected if None)
"""
if project_root is None:
# Auto-detect project root
current = Path(__file__).resolve()
while current.parent != current:
if (current / 'guardian').exists() and (current / 'tests').exists():
project_root = current
break
current = current.parent

self.project_root = project_root
self.intervention_engine = InterventionEngine()
logger.info(f"Initialized SelfAnalyzer for project: {self.project_root}")

def analyze(self) -> CodeHealthReport:
"""
Perform complete self-analysis

Returns:
CodeHealthReport with metrics and recommendations
"""
logger.info("Starting Guardian self-analysis...")

# Gather metrics
coverage_metrics = self._get_coverage_metrics()
test_metrics = self._get_test_metrics()
quality_metrics = self._get_quality_metrics()

# Map to LJPW coordinates
coords = self._calculate_ljpw_coordinates(
coverage_metrics,
test_metrics,
quality_metrics
)

# Use intervention engine for recommendations
plan = self.intervention_engine.generate_intervention_plan(coords)

# Get threat analysis separately
threat_analysis = self.intervention_engine.analyze_threat(coords)

# Build report
report = CodeHealthReport(
test_coverage=coverage_metrics['overall'],
coverage_by_module=coverage_metrics['by_module'],
total_tests=test_metrics['total'],
passing_tests=test_metrics['passing'],
failing_tests=test_metrics['failing'],
test_pass_rate=test_metrics['pass_rate'],
lines_of_code=quality_metrics['loc'],
documented_functions=quality_metrics['documented'],
documentation_ratio=quality_metrics['doc_ratio'],
average_complexity=quality_metrics['complexity'],
high_complexity_functions=quality_metrics['complex_functions'],
coordinates=coords,
threat_level=threat_analysis['threat_level'],
concerns=plan.concerns,
recommendations=plan.priority_actions
)

logger.info(f"Self-analysis complete: {coords}")
return report

def _get_coverage_metrics(self) -> Dict:
"""Get test coverage metrics"""
try:
# Run pytest with coverage
result = subprocess.run(
['python', '-m', 'pytest', 'tests/', '--cov=guardian',
'--cov-report=json', '--quiet'],
cwd=self.project_root,
capture_output=True,
text=True,
timeout=120
)

# Read coverage report
coverage_file = self.project_root / 'coverage.json'
if coverage_file.exists():
with open(coverage_file) as f:
coverage_data = json.load(f)

overall = coverage_data['totals']['percent_covered'] / 100.0
by_module = {}

for file_path, data in coverage_data['files'].items():
if file_path.startswith('guardian/'):
module = file_path.replace('guardian/', '').replace('.py', '')
by_module[module] = data['summary']['percent_covered'] / 100.0

return {
'overall': overall,
'by_module': by_module
}
except Exception as e:
logger.warning(f"Could not get coverage metrics: {e}")

return {'overall': 0.0, 'by_module': {}}

def _get_test_metrics(self) -> Dict:
"""Get test execution metrics"""
try:
# Run pytest
result = subprocess.run(
['python', '-m', 'pytest', 'tests/', '--quiet', '--tb=no'],
cwd=self.project_root,
capture_output=True,
text=True,
timeout=120
)

# Parse output for test counts
output = result.stdout + result.stderr

# Look for pytest summary line
import re
match = re.search(r'(\d+) passed', output)
passing = int(match.group(1)) if match else 0

match = re.search(r'(\d+) failed', output)
failing = int(match.group(1)) if match else 0

total = passing + failing
pass_rate = passing / total if total > 0 else 0.0

return {
'total': total,
'passing': passing,
'failing': failing,
'pass_rate': pass_rate
}
except Exception as e:
logger.warning(f"Could not get test metrics: {e}")

return {'total': 0, 'passing': 0, 'failing': 0, 'pass_rate': 0.0}

def _get_quality_metrics(self) -> Dict:
"""Get code quality metrics"""
try:
guardian_dir = self.project_root / 'guardian'

# Count lines of code
loc = 0
py_files = list(guardian_dir.rglob('*.py'))
for py_file in py_files:
with open(py_file) as f:
loc += len(f.readlines())

# Count documented functions
documented = 0
total_functions = 0

for py_file in py_files:
with open(py_file) as f:
content = f.read()
# Simple heuristic: count def statements
import re
functions = re.findall(r'\n def \w+\(', content)
total_functions += len(functions)

# Count docstrings (""" after def)
for match in functions:
func_name = match.strip().split()[1].replace('(', '')
if f'{match}\n """' in content or f'{match}\n \'\'\'' in content:
documented += 1

doc_ratio = documented / total_functions if total_functions > 0 else 0.0

return {
'loc': loc,
'documented': documented,
'total_functions': total_functions,
'doc_ratio': doc_ratio,
'complexity': 5.0, # Placeholder for now
'complex_functions': []
}
except Exception as e:
logger.warning(f"Could not get quality metrics: {e}")

return {
'loc': 0,
'documented': 0,
'total_functions': 0,
'doc_ratio': 0.0,
'complexity': 0.0,
'complex_functions': []
}

def _calculate_ljpw_coordinates(
self,
coverage: Dict,
tests: Dict,
quality: Dict
) -> Coordinates:
"""
Map code health metrics to LJPW coordinates

Mapping philosophy:
- Love: Care for code (coverage, documentation, maintainability)
- Justice: Fairness in test distribution, consistent patterns
- Power: Execution capability (test pass rate, feature completeness)
- Wisdom: Strategic design (architecture, low complexity, long-term thinking)
"""
# Love: Care for the code
# High when test coverage is good and documentation exists
love = (
0.6 * coverage['overall'] +
0.3 * quality['doc_ratio'] +
0.1 # Base level
)
love = min(1.0, max(0.0, love))

# Justice: Fairness in testing
# High when tests are evenly distributed across modules
if coverage['by_module']:
coverages = list(coverage['by_module'].values())
variance = sum((c - coverage['overall'])**2 for c in coverages) / len(coverages)
fairness = 1.0 - min(1.0, variance * 4) # Scale variance to [0,1]
else:
fairness = 0.5

justice = (
0.7 * fairness +
0.3 * (1.0 if tests['failing'] == 0 else 0.5)
)
justice = min(1.0, max(0.0, justice))

# Power: Execution capability
# High when tests pass and features work
power = (
0.7 * tests['pass_rate'] +
0.3 * coverage['overall']
)
power = min(1.0, max(0.0, power))

# Wisdom: Strategic architecture
# High when complexity is low and documentation is good
complexity_score = max(0.0, 1.0 - quality['complexity'] / 20.0)
wisdom = (
0.4 * quality['doc_ratio'] +
0.4 * complexity_score +
0.2 * coverage['overall']
)
wisdom = min(1.0, max(0.0, wisdom))

coords = Coordinates(
love=love,
justice=justice,
power=power,
wisdom=wisdom
)

logger.info(f"Calculated LJPW coordinates: {coords}")
return coords

def print_report(self, report: CodeHealthReport) -> None:
"""
Print a formatted health report

Args:
report: The code health report to print
"""
print("\n" + "="*70)
print("GUARDIAN SELF-ANALYSIS REPORT")
print("="*70)

print(f"\n📊 CODE METRICS:")
print(f" Test Coverage: {report.test_coverage*100:.1f}%")
print(f" Tests Passing: {report.passing_tests}/{report.total_tests} ({report.test_pass_rate*100:.1f}%)")
print(f" Documentation: {report.documentation_ratio*100:.1f}%")
print(f" Lines of Code: {report.lines_of_code:,}")

print(f"\n🧭 LJPW COORDINATES:")
print(f" Love (Care): {report.coordinates.love:.3f}")
print(f" Justice (Fairness): {report.coordinates.justice:.3f}")
print(f" Power (Execution): {report.coordinates.power:.3f}")
print(f" Wisdom (Strategy): {report.coordinates.wisdom:.3f}")

print(f"\n⚠️ THREAT ASSESSMENT: {report.threat_level.upper()}")

if report.concerns:
print(f"\n🔍 CONCERNS:")
for concern in report.concerns:
print(f" • {concern}")

print(f"\n💡 RECOMMENDATIONS:")
for i, action in enumerate(report.recommendations[:5], 1):
print(f" {i}. {action['dimension'].upper()} (Priority: {action['priority']:.1f}/10)")
print(f" Current: {action['current_value']:.3f} → Target: +{action['target_increase']:.3f}")
print(f" Rationale: {action['rationale']}")
if action.get('tactics'):
print(f" Top Tactic: {action['tactics'][0]}")

print("\n" + "="*70)
Loading
Loading