Refactor cockpit to use DockerTmuxController pattern
Based on claude-code-tools TmuxCLIController, this refactor: - Added DockerTmuxController class for robust tmux session management - Implements send_keys() with configurable delay_enter - Implements capture_pane() for output retrieval - Implements wait_for_prompt() for pattern-based completion detection - Implements wait_for_idle() for content-hash-based idle detection - Implements wait_for_shell_prompt() for shell prompt detection Also includes workflow improvements: - Pre-task git snapshot before agent execution - Post-task commit protocol in agent guidelines Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
313
lib/health_report_generator.py
Normal file
313
lib/health_report_generator.py
Normal file
@@ -0,0 +1,313 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Health Report Generator
|
||||
|
||||
Generates formatted health reports with:
|
||||
- 0-100 overall scores
|
||||
- Component breakdown
|
||||
- Specific issue examples
|
||||
- Actionable recommendations
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import List, Dict
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class HealthReportGenerator:
|
||||
"""Generate formatted health reports."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize report generator."""
|
||||
pass
|
||||
|
||||
def generate_dashboard_report(self, health_data: Dict) -> str:
|
||||
"""
|
||||
Generate formatted dashboard report.
|
||||
|
||||
Args:
|
||||
health_data: Health data from system orchestrator
|
||||
|
||||
Returns:
|
||||
Formatted dashboard string
|
||||
"""
|
||||
overall = health_data['overall_score']
|
||||
status = health_data['status'].upper()
|
||||
timestamp = datetime.fromtimestamp(health_data['timestamp']).strftime('%Y-%m-%d %H:%M UTC')
|
||||
|
||||
report = f"""
|
||||
╔════════════════════════════════════════════════════════════════════╗
|
||||
║ LUZIA SYSTEM HEALTH REPORT ║
|
||||
║ {timestamp:42} ║
|
||||
╚════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
OVERALL HEALTH SCORE: {overall:3.1f}/100 [{self._status_emoji(status)} {status}]
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
COMPONENT BREAKDOWN:
|
||||
|
||||
"""
|
||||
components = health_data.get('component_scores', {})
|
||||
for name, score in components.items():
|
||||
emoji = self._score_emoji(score)
|
||||
status_str = 'healthy' if score >= 80 else 'degraded' if score >= 60 else 'critical'
|
||||
report += f" {name:15} {score:6.1f}/100 {emoji} {status_str}\n"
|
||||
|
||||
report += "\n" + "━" * 70 + "\n\n"
|
||||
|
||||
return report
|
||||
|
||||
def generate_component_report(self, component_name: str, component_data: Dict) -> str:
|
||||
"""
|
||||
Generate detailed component report.
|
||||
|
||||
Args:
|
||||
component_name: Name of component (kg, conductor, etc)
|
||||
component_data: Component health data
|
||||
|
||||
Returns:
|
||||
Formatted component report
|
||||
"""
|
||||
report = f"\n{'=' * 70}\n"
|
||||
report += f"{component_name.upper()} COMPONENT REPORT\n"
|
||||
report += f"{'=' * 70}\n\n"
|
||||
|
||||
# Score
|
||||
score = component_data.get('health_score') or component_data.get('overall_score', 0)
|
||||
status = component_data.get('status', 'unknown').upper()
|
||||
report += f"Score: {score:3.1f}/100 [{status}]\n\n"
|
||||
|
||||
# Issues
|
||||
issues = component_data.get('issues', [])
|
||||
if issues:
|
||||
report += f"ISSUES FOUND ({len(issues)}):\n\n"
|
||||
for i, issue in enumerate(issues[:10], 1):
|
||||
if isinstance(issue, dict):
|
||||
report += f" [{i}] {issue.get('severity', 'UNKNOWN').upper()}\n"
|
||||
report += f" {issue.get('pattern', 'Unknown pattern')}\n"
|
||||
if 'example' in issue:
|
||||
example = issue['example']
|
||||
if len(example) > 80:
|
||||
example = example[:80] + "..."
|
||||
report += f" Example: {example}\n\n"
|
||||
else:
|
||||
report += f" [{i}] {issue}\n\n"
|
||||
|
||||
# Recommendations
|
||||
recommendations = component_data.get('recommendations', [])
|
||||
if recommendations:
|
||||
report += f"RECOMMENDATIONS:\n\n"
|
||||
for i, rec in enumerate(recommendations, 1):
|
||||
report += f" {i}. {rec}\n"
|
||||
report += "\n"
|
||||
|
||||
return report
|
||||
|
||||
def generate_summary_report(self, health_data: Dict) -> str:
|
||||
"""
|
||||
Generate executive summary report.
|
||||
|
||||
Args:
|
||||
health_data: Health data from system orchestrator
|
||||
|
||||
Returns:
|
||||
Summary report string
|
||||
"""
|
||||
overall = health_data['overall_score']
|
||||
timestamp = datetime.fromtimestamp(health_data['timestamp']).strftime('%Y-%m-%d %H:%M UTC')
|
||||
|
||||
report = f"""
|
||||
╔════════════════════════════════════════════════════════════════════╗
|
||||
║ SYSTEM HEALTH SUMMARY ║
|
||||
║ {timestamp:42} ║
|
||||
╚════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
OVERALL SCORE: {overall:3.1f}/100
|
||||
|
||||
COMPONENT STATUS:
|
||||
"""
|
||||
|
||||
components = health_data.get('component_scores', {})
|
||||
for name, score in components.items():
|
||||
emoji = self._score_emoji(score)
|
||||
report += f" ├─ {name:20} {score:6.1f}/100 {emoji}\n"
|
||||
|
||||
report += """
|
||||
NEXT STEPS:
|
||||
"""
|
||||
|
||||
# Provide actionable next steps based on score
|
||||
if overall >= 80:
|
||||
report += """
|
||||
✓ System is healthy - continue normal operations
|
||||
- Run weekly full audits for proactive monitoring
|
||||
- Review error patterns for systemic improvements
|
||||
"""
|
||||
elif overall >= 60:
|
||||
report += f"""
|
||||
⚠ System is degraded - {int(100 - overall)} points below healthy threshold
|
||||
- Address component issues in order of severity
|
||||
- Run luzia health --full for detailed analysis
|
||||
- Implement recommended fixes for each component
|
||||
"""
|
||||
else:
|
||||
report += """
|
||||
✗ System is critical - immediate action required
|
||||
- Run luzia health --full immediately
|
||||
- Address URGENT issues first
|
||||
- Contact administrator if problems persist
|
||||
"""
|
||||
|
||||
report += f"\nFor detailed analysis:\n luzia health --full\n\n"
|
||||
|
||||
return report
|
||||
|
||||
def generate_full_report(self, all_health_data: Dict) -> str:
|
||||
"""
|
||||
Generate comprehensive full system report.
|
||||
|
||||
Args:
|
||||
all_health_data: Complete health data dict
|
||||
|
||||
Returns:
|
||||
Full report string
|
||||
"""
|
||||
report = self.generate_dashboard_report(all_health_data)
|
||||
|
||||
# Add capacity section
|
||||
capacity = all_health_data.get('capacity', {})
|
||||
report += f"""
|
||||
SYSTEM CAPACITY:
|
||||
|
||||
Disk Usage: {capacity['disk']['usage_pct']:5.1f}% ({capacity['disk']['status']})
|
||||
Memory Usage: {capacity['memory']['usage_pct']:5.1f}% ({capacity['memory']['status']})
|
||||
CPU Load: {capacity['cpu']['load_pct']:5.1f}% ({capacity['cpu']['status']})
|
||||
Concurrency: {capacity['concurrency']['active_agents']}/{capacity['concurrency']['max_concurrent']} agents
|
||||
|
||||
"""
|
||||
|
||||
# Configuration status
|
||||
config = all_health_data.get('configuration', {})
|
||||
report += f"""
|
||||
CONFIGURATION STATUS:
|
||||
|
||||
Config File: {'✓' if config['config_file_valid'] else '✗'}
|
||||
Permissions: {'✓' if config['permissions_valid'] else '✗'}
|
||||
Databases: {'✓' if config['databases_accessible'] else '✗'}
|
||||
MCP Servers: {'✓' if config['mcp_servers_configured'] else '✗'}
|
||||
|
||||
"""
|
||||
|
||||
# Integration tests
|
||||
integration = all_health_data.get('integration', {})
|
||||
report += f"""
|
||||
INTEGRATION TESTS:
|
||||
|
||||
KG Query: {'✓' if integration['kg_query'] else '✗'}
|
||||
Conductor R/W: {'✓' if integration['conductor_rw'] else '✗'}
|
||||
Context Retrieval: {'✓' if integration['context_retrieval'] else '✗'}
|
||||
Bash Execution: {'✓' if integration['bash_execution'] else '✗'}
|
||||
|
||||
"""
|
||||
|
||||
# Issues summary
|
||||
all_issues = []
|
||||
all_issues.extend(capacity.get('issues', []))
|
||||
all_issues.extend(config.get('issues', []))
|
||||
all_issues.extend(integration.get('issues', []))
|
||||
|
||||
if all_issues:
|
||||
report += f"""
|
||||
ISSUES FOUND ({len(all_issues)}):
|
||||
|
||||
"""
|
||||
for issue in all_issues[:20]:
|
||||
report += f" • {issue}\n"
|
||||
|
||||
if len(all_issues) > 20:
|
||||
report += f"\n ... and {len(all_issues) - 20} more issues\n"
|
||||
|
||||
report += f"\n{'━' * 70}\n"
|
||||
|
||||
return report
|
||||
|
||||
def save_report(self, filename: str, content: str) -> Path:
|
||||
"""
|
||||
Save report to file.
|
||||
|
||||
Args:
|
||||
filename: Filename to save
|
||||
content: Report content
|
||||
|
||||
Returns:
|
||||
Path to saved file
|
||||
"""
|
||||
output_path = Path('/home/admin') / filename
|
||||
output_path.write_text(content)
|
||||
return output_path
|
||||
|
||||
def _status_emoji(self, status: str) -> str:
|
||||
"""Get emoji for status."""
|
||||
emojis = {
|
||||
'HEALTHY': '✅',
|
||||
'DEGRADED': '⚠️',
|
||||
'CRITICAL': '❌',
|
||||
'UNKNOWN': '❓'
|
||||
}
|
||||
return emojis.get(status, '❓')
|
||||
|
||||
def _score_emoji(self, score: float) -> str:
|
||||
"""Get emoji for score."""
|
||||
if score >= 80:
|
||||
return '✅'
|
||||
elif score >= 60:
|
||||
return '⚠️'
|
||||
else:
|
||||
return '❌'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
generator = HealthReportGenerator()
|
||||
|
||||
# Example health data
|
||||
sample_data = {
|
||||
'overall_score': 87,
|
||||
'status': 'healthy',
|
||||
'timestamp': 1704729600,
|
||||
'component_scores': {
|
||||
'kg': 92,
|
||||
'conductor': 84,
|
||||
'context': 89,
|
||||
'scripts': 95,
|
||||
'routines': 88,
|
||||
'capacity': 81,
|
||||
'configuration': 98,
|
||||
'integration': 100
|
||||
},
|
||||
'capacity': {
|
||||
'disk': {'usage_pct': 82, 'status': 'warning'},
|
||||
'memory': {'usage_pct': 65, 'status': 'healthy'},
|
||||
'cpu': {'load_pct': 45, 'status': 'healthy'},
|
||||
'concurrency': {'active_agents': 2, 'max_concurrent': 4},
|
||||
'issues': []
|
||||
},
|
||||
'configuration': {
|
||||
'config_file_valid': True,
|
||||
'permissions_valid': True,
|
||||
'databases_accessible': True,
|
||||
'mcp_servers_configured': True,
|
||||
'issues': []
|
||||
},
|
||||
'integration': {
|
||||
'kg_query': True,
|
||||
'conductor_rw': True,
|
||||
'context_retrieval': True,
|
||||
'bash_execution': True,
|
||||
'issues': []
|
||||
}
|
||||
}
|
||||
|
||||
print(generator.generate_dashboard_report(sample_data))
|
||||
print(generator.generate_summary_report(sample_data))
|
||||
Reference in New Issue
Block a user