440 lines
17 KiB
Python
Executable File
440 lines
17 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Allegro-Primus Self-Analyzer
|
|
Analyzes journal entries, identifies bottlenecks, suggests improvements.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional, Tuple, Any
|
|
from collections import defaultdict
|
|
import statistics
|
|
|
|
from journal import Journal, JournalEntry, JOURNAL_DIR
|
|
|
|
IMPROVEMENT_QUEUE = Path("/root/wizards/allegro-primus/improvement_queue.json")
|
|
|
|
|
|
class SelfAnalyzer:
|
|
"""Analyzes journal data to identify improvements."""
|
|
|
|
def __init__(self):
|
|
self.journal = Journal()
|
|
self._ensure_queue()
|
|
|
|
def _ensure_queue(self):
|
|
"""Ensure improvement queue exists."""
|
|
if not IMPROVEMENT_QUEUE.exists():
|
|
self._save_queue({
|
|
"created_at": datetime.now().isoformat(),
|
|
"pending": [],
|
|
"implemented": [],
|
|
"rejected": []
|
|
})
|
|
|
|
def _load_queue(self) -> Dict:
|
|
with open(IMPROVEMENT_QUEUE, 'r') as f:
|
|
return json.load(f)
|
|
|
|
def _save_queue(self, queue: Dict):
|
|
with open(IMPROVEMENT_QUEUE, 'w') as f:
|
|
json.dump(queue, f, indent=2)
|
|
|
|
def add_improvement(self, category: str, description: str, priority: str = "medium",
|
|
source: str = "analysis") -> str:
|
|
"""Add an improvement to the queue."""
|
|
queue = self._load_queue()
|
|
|
|
improvement = {
|
|
"id": f"imp_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{os.urandom(2).hex()}",
|
|
"category": category,
|
|
"description": description,
|
|
"priority": priority,
|
|
"source": source,
|
|
"created_at": datetime.now().isoformat(),
|
|
"status": "pending"
|
|
}
|
|
|
|
queue["pending"].append(improvement)
|
|
self._save_queue(queue)
|
|
|
|
return improvement["id"]
|
|
|
|
def complete_improvement(self, improvement_id: str, notes: str = ""):
|
|
"""Mark an improvement as implemented."""
|
|
queue = self._load_queue()
|
|
|
|
for imp in queue["pending"]:
|
|
if imp["id"] == improvement_id:
|
|
imp["status"] = "implemented"
|
|
imp["completed_at"] = datetime.now().isoformat()
|
|
imp["notes"] = notes
|
|
queue["implemented"].append(imp)
|
|
queue["pending"] = [p for p in queue["pending"] if p["id"] != improvement_id]
|
|
break
|
|
|
|
self._save_queue(queue)
|
|
|
|
def analyze_bottlenecks(self, days: int = 14) -> List[Dict]:
|
|
"""Identify bottlenecks from journal entries."""
|
|
entries = self.journal.get_entries(days=days)
|
|
bottlenecks = []
|
|
|
|
if len(entries) < 5:
|
|
return [{"status": "insufficient_data", "min_required": 5}]
|
|
|
|
# 1. High error rate patterns
|
|
error_patterns = self._analyze_error_patterns(entries)
|
|
if error_patterns:
|
|
bottlenecks.append({
|
|
"type": "error_pattern",
|
|
"description": f"Recurring errors: {', '.join(error_patterns[:3])}",
|
|
"severity": "high" if len(error_patterns) > 3 else "medium",
|
|
"suggestion": "Review error handling and add pre-validation checks"
|
|
})
|
|
|
|
# 2. Slow response patterns
|
|
slow_ops = self._identify_slow_operations(entries)
|
|
if slow_ops:
|
|
bottlenecks.append({
|
|
"type": "performance",
|
|
"description": f"Slow operations detected: {slow_ops}",
|
|
"severity": "medium",
|
|
"suggestion": "Consider caching or parallelization"
|
|
})
|
|
|
|
# 3. Repeated failures
|
|
failure_clusters = self._find_failure_clusters(entries)
|
|
if failure_clusters:
|
|
bottlenecks.append({
|
|
"type": "reliability",
|
|
"description": f"Failure clusters: {failure_clusters}",
|
|
"severity": "high",
|
|
"suggestion": "Add retry logic and better error recovery"
|
|
})
|
|
|
|
# 4. Tool usage inefficiency
|
|
tool_inefficiency = self._analyze_tool_inefficiency(entries)
|
|
if tool_inefficiency:
|
|
bottlenecks.append({
|
|
"type": "efficiency",
|
|
"description": tool_inefficiency,
|
|
"severity": "low",
|
|
"suggestion": "Optimize tool selection strategy"
|
|
})
|
|
|
|
return bottlenecks
|
|
|
|
def _analyze_error_patterns(self, entries: List[JournalEntry]) -> List[str]:
|
|
"""Extract common error patterns."""
|
|
error_counts = defaultdict(int)
|
|
for e in entries:
|
|
for err in e.errors:
|
|
# Normalize error messages
|
|
normalized = err.lower().replace('file not found', 'missing_file')
|
|
normalized = normalized.replace('permission denied', 'access_error')
|
|
normalized = normalized.replace('timeout', 'timeout_error')
|
|
error_counts[normalized[:50]] += 1
|
|
|
|
# Return errors that appear multiple times
|
|
return [err for err, count in error_counts.items() if count >= 2]
|
|
|
|
def _identify_slow_operations(self, entries: List[JournalEntry]) -> str:
|
|
"""Identify operations with slow response times."""
|
|
slow_threshold = 5000 # 5 seconds
|
|
slow_tasks = []
|
|
|
|
for e in entries:
|
|
if e.response_time_ms > slow_threshold:
|
|
task_type = self.journal._categorize_task(e.task)
|
|
slow_tasks.append(task_type)
|
|
|
|
if not slow_tasks:
|
|
return ""
|
|
|
|
task_counts = defaultdict(int)
|
|
for t in slow_tasks:
|
|
task_counts[t] += 1
|
|
|
|
top_slow = sorted(task_counts.items(), key=lambda x: x[1], reverse=True)[:2]
|
|
return ", ".join([f"{t}({c}x)" for t, c in top_slow])
|
|
|
|
def _find_failure_clusters(self, entries: List[JournalEntry]) -> List[str]:
|
|
"""Find clusters of failures."""
|
|
failures = [e for e in entries if not e.success]
|
|
if len(failures) < 3:
|
|
return []
|
|
|
|
# Group by task type
|
|
failure_types = defaultdict(int)
|
|
for f in failures:
|
|
task_type = self.journal._categorize_task(f.task)
|
|
failure_types[task_type] += 1
|
|
|
|
return [t for t, c in failure_types.items() if c >= 2]
|
|
|
|
def _analyze_tool_inefficiency(self, entries: List[JournalEntry]) -> str:
|
|
"""Analyze if tool usage is inefficient."""
|
|
total_tools = sum(len(e.tools_used) for e in entries)
|
|
avg_tools = total_tools / len(entries) if entries else 0
|
|
|
|
if avg_tools > 5:
|
|
return f"High average tool usage ({avg_tools:.1f} per cycle)"
|
|
return ""
|
|
|
|
def generate_recommendations(self, days: int = 14) -> List[Dict]:
|
|
"""Generate improvement recommendations."""
|
|
recommendations = []
|
|
entries = self.journal.get_entries(days=days)
|
|
|
|
if len(entries) < 5:
|
|
return [{"status": "insufficient_data", "min_required": 5}]
|
|
|
|
metrics = self.journal.calculate_metrics(days=days)
|
|
patterns = self.journal.get_patterns(days=days)
|
|
|
|
# 1. Success rate recommendations
|
|
if metrics.success_rate < 0.8:
|
|
recommendations.append({
|
|
"category": "reliability",
|
|
"priority": "high",
|
|
"issue": f"Success rate is {metrics.success_rate*100:.0f}%",
|
|
"recommendation": "Implement better error handling and validation",
|
|
"expected_impact": "Increase success rate to 90%+"
|
|
})
|
|
|
|
# 2. Response time recommendations
|
|
if metrics.avg_response_time_ms > 3000:
|
|
recommendations.append({
|
|
"category": "performance",
|
|
"priority": "medium",
|
|
"issue": f"Average response time is {metrics.avg_response_time_ms:.0f}ms",
|
|
"recommendation": "Add caching, optimize queries, use batch operations",
|
|
"expected_impact": "Reduce response time by 30-50%"
|
|
})
|
|
|
|
# 3. Learning recommendations
|
|
if metrics.total_lessons < metrics.total_cycles * 0.1:
|
|
recommendations.append({
|
|
"category": "learning",
|
|
"priority": "medium",
|
|
"issue": "Low lesson capture rate",
|
|
"recommendation": "Add structured reflection after each cycle",
|
|
"expected_impact": "Better pattern recognition and faster improvement"
|
|
})
|
|
|
|
# 4. Tool usage recommendations
|
|
tool_usage = defaultdict(int)
|
|
for e in entries:
|
|
for tool in e.tools_used:
|
|
tool_usage[tool] += 1
|
|
|
|
if len(tool_usage) < 3 and len(entries) > 10:
|
|
recommendations.append({
|
|
"category": "capability",
|
|
"priority": "low",
|
|
"issue": "Limited tool variety",
|
|
"recommendation": "Explore additional tools for different tasks",
|
|
"expected_impact": "More efficient task completion"
|
|
})
|
|
|
|
# 5. Pattern-based recommendations
|
|
if patterns.get('improvement_trend') == 'declining':
|
|
recommendations.append({
|
|
"category": "trend",
|
|
"priority": "high",
|
|
"issue": "Performance trend is declining",
|
|
"recommendation": "Review recent changes, focus on fundamentals",
|
|
"expected_impact": "Reverse negative trend"
|
|
})
|
|
|
|
return recommendations
|
|
|
|
def auto_generate_improvements(self, days: int = 14) -> List[str]:
|
|
"""Automatically generate improvements from analysis."""
|
|
recommendations = self.generate_recommendations(days=days)
|
|
bottlenecks = self.analyze_bottlenecks(days=days)
|
|
|
|
improvement_ids = []
|
|
|
|
# Convert recommendations to improvements
|
|
for rec in recommendations:
|
|
if 'priority' in rec:
|
|
imp_id = self.add_improvement(
|
|
category=rec['category'],
|
|
description=f"{rec['issue']}: {rec['recommendation']}",
|
|
priority=rec['priority'],
|
|
source="auto_analysis"
|
|
)
|
|
improvement_ids.append(imp_id)
|
|
|
|
# Convert bottlenecks to improvements
|
|
for bottleneck in bottlenecks:
|
|
if 'severity' in bottleneck:
|
|
imp_id = self.add_improvement(
|
|
category=bottleneck['type'],
|
|
description=bottleneck['description'],
|
|
priority=bottleneck['severity'],
|
|
source="bottleneck_detection"
|
|
)
|
|
improvement_ids.append(imp_id)
|
|
|
|
return improvement_ids
|
|
|
|
def generate_report(self, days: int = 14) -> Dict:
|
|
"""Generate comprehensive self-analysis report."""
|
|
entries = self.journal.get_entries(days=days)
|
|
queue = self._load_queue()
|
|
|
|
report = {
|
|
"generated_at": datetime.now().isoformat(),
|
|
"analysis_period_days": days,
|
|
"entries_analyzed": len(entries),
|
|
"summary": {
|
|
"metrics": {},
|
|
"patterns": {},
|
|
"bottlenecks": [],
|
|
"recommendations": []
|
|
},
|
|
"improvement_queue": {
|
|
"pending": len(queue["pending"]),
|
|
"implemented": len(queue["implemented"]),
|
|
"rejected": len(queue["rejected"])
|
|
}
|
|
}
|
|
|
|
if len(entries) >= 5:
|
|
metrics = self.journal.calculate_metrics(days=days)
|
|
report["summary"]["metrics"] = {
|
|
"total_cycles": metrics.total_cycles,
|
|
"success_rate": round(metrics.success_rate * 100, 2),
|
|
"avg_response_time_ms": round(metrics.avg_response_time_ms, 2),
|
|
"total_lessons": metrics.total_lessons
|
|
}
|
|
|
|
report["summary"]["patterns"] = self.journal.get_patterns(days=days)
|
|
report["summary"]["bottlenecks"] = self.analyze_bottlenecks(days=days)
|
|
report["summary"]["recommendations"] = self.generate_recommendations(days=days)
|
|
|
|
return report
|
|
|
|
def display_report(self, days: int = 14):
|
|
"""Display formatted analysis report."""
|
|
report = self.generate_report(days=days)
|
|
queue = self._load_queue()
|
|
|
|
print("\n" + "=" * 70)
|
|
print(" ALLEGRO-PRIMUS SELF-ANALYSIS REPORT")
|
|
print("=" * 70)
|
|
print(f" Generated: {report['generated_at']}")
|
|
print(f" Period: Last {days} days")
|
|
print(f" Entries Analyzed: {report['entries_analyzed']}")
|
|
print("-" * 70)
|
|
|
|
# Metrics
|
|
metrics = report["summary"]["metrics"]
|
|
if metrics:
|
|
print("\n 📊 PERFORMANCE METRICS")
|
|
print(f" Success Rate: {metrics.get('success_rate', 0):.1f}%")
|
|
print(f" Avg Response: {metrics.get('avg_response_time_ms', 0):.0f}ms")
|
|
print(f" Lessons Captured: {metrics.get('total_lessons', 0)}")
|
|
|
|
# Patterns
|
|
patterns = report["summary"]["patterns"]
|
|
if patterns and patterns.get("status") != "insufficient_data":
|
|
print("\n 📈 PATTERNS")
|
|
print(f" Trend: {patterns.get('improvement_trend', 'N/A')}")
|
|
if patterns.get("best_performing_hours"):
|
|
print(f" Best Hours: {patterns['best_performing_hours'][:2]}")
|
|
|
|
# Bottlenecks
|
|
bottlenecks = report["summary"]["bottlenecks"]
|
|
if bottlenecks and bottlenecks[0].get("status") != "insufficient_data":
|
|
print("\n ⚠️ BOTTLENECKS")
|
|
for b in bottlenecks[:3]:
|
|
if 'type' in b:
|
|
print(f" [{b['severity'].upper()}] {b['type']}: {b['description'][:50]}...")
|
|
|
|
# Recommendations
|
|
recommendations = report["summary"]["recommendations"]
|
|
if recommendations and recommendations[0].get("status") != "insufficient_data":
|
|
print("\n 💡 RECOMMENDATIONS")
|
|
for r in recommendations[:3]:
|
|
if 'category' in r:
|
|
print(f" [{r['priority'].upper()}] {r['category']}: {r['recommendation'][:50]}...")
|
|
|
|
# Queue Status
|
|
print("\n 📋 IMPROVEMENT QUEUE")
|
|
print(f" Pending: {queue['pending'].__len__()}")
|
|
print(f" Implemented: {queue['implemented'].__len__()}")
|
|
|
|
print("=" * 70)
|
|
|
|
|
|
def main():
|
|
"""CLI interface for self-analyzer."""
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description="AP Self-Analyzer")
|
|
parser.add_argument('action', choices=[
|
|
'report', 'bottlenecks', 'recommendations',
|
|
'auto', 'add', 'complete', 'queue', 'patterns'
|
|
])
|
|
parser.add_argument('--days', '-d', type=int, default=14)
|
|
parser.add_argument('--category', '-c', help='Category for add')
|
|
parser.add_argument('--description', '-desc', help='Description for add')
|
|
parser.add_argument('--priority', '-p', default='medium', choices=['low', 'medium', 'high'])
|
|
parser.add_argument('--id', help='Improvement ID for complete')
|
|
parser.add_argument('--notes', '-n', default='', help='Completion notes')
|
|
|
|
args = parser.parse_args()
|
|
|
|
analyzer = SelfAnalyzer()
|
|
|
|
if args.action == 'report':
|
|
analyzer.display_report(days=args.days)
|
|
|
|
elif args.action == 'bottlenecks':
|
|
bottlenecks = analyzer.analyze_bottlenecks(days=args.days)
|
|
print(json.dumps(bottlenecks, indent=2))
|
|
|
|
elif args.action == 'recommendations':
|
|
recs = analyzer.generate_recommendations(days=args.days)
|
|
print(json.dumps(recs, indent=2))
|
|
|
|
elif args.action == 'auto':
|
|
ids = analyzer.auto_generate_improvements(days=args.days)
|
|
print(f"Generated {len(ids)} improvements:")
|
|
for imp_id in ids:
|
|
print(f" - {imp_id}")
|
|
|
|
elif args.action == 'add':
|
|
if not args.category or not args.description:
|
|
print("Error: --category and --description required")
|
|
return
|
|
imp_id = analyzer.add_improvement(
|
|
args.category, args.description, args.priority
|
|
)
|
|
print(f"Added improvement: {imp_id}")
|
|
|
|
elif args.action == 'complete':
|
|
if not args.id:
|
|
print("Error: --id required")
|
|
return
|
|
analyzer.complete_improvement(args.id, args.notes)
|
|
print(f"Marked {args.id} as complete")
|
|
|
|
elif args.action == 'queue':
|
|
queue = analyzer._load_queue()
|
|
print(json.dumps(queue, indent=2))
|
|
|
|
elif args.action == 'patterns':
|
|
patterns = analyzer.journal.get_patterns(days=args.days)
|
|
print(json.dumps(patterns, indent=2))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|