empathy-framework 3.7.0__py3-none-any.whl → 3.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coach_wizards/code_reviewer_README.md +60 -0
- coach_wizards/code_reviewer_wizard.py +180 -0
- {empathy_framework-3.7.0.dist-info → empathy_framework-3.8.0.dist-info}/METADATA +148 -11
- empathy_framework-3.8.0.dist-info/RECORD +333 -0
- {empathy_framework-3.7.0.dist-info → empathy_framework-3.8.0.dist-info}/top_level.txt +5 -1
- empathy_healthcare_plugin/monitors/__init__.py +9 -0
- empathy_healthcare_plugin/monitors/clinical_protocol_monitor.py +315 -0
- empathy_healthcare_plugin/monitors/monitoring/__init__.py +44 -0
- empathy_healthcare_plugin/monitors/monitoring/protocol_checker.py +300 -0
- empathy_healthcare_plugin/monitors/monitoring/protocol_loader.py +214 -0
- empathy_healthcare_plugin/monitors/monitoring/sensor_parsers.py +306 -0
- empathy_healthcare_plugin/monitors/monitoring/trajectory_analyzer.py +389 -0
- empathy_llm_toolkit/agent_factory/__init__.py +53 -0
- empathy_llm_toolkit/agent_factory/adapters/__init__.py +85 -0
- empathy_llm_toolkit/agent_factory/adapters/autogen_adapter.py +312 -0
- empathy_llm_toolkit/agent_factory/adapters/crewai_adapter.py +454 -0
- empathy_llm_toolkit/agent_factory/adapters/haystack_adapter.py +298 -0
- empathy_llm_toolkit/agent_factory/adapters/langchain_adapter.py +362 -0
- empathy_llm_toolkit/agent_factory/adapters/langgraph_adapter.py +333 -0
- empathy_llm_toolkit/agent_factory/adapters/native.py +228 -0
- empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +426 -0
- empathy_llm_toolkit/agent_factory/base.py +305 -0
- empathy_llm_toolkit/agent_factory/crews/__init__.py +67 -0
- empathy_llm_toolkit/agent_factory/crews/code_review.py +1113 -0
- empathy_llm_toolkit/agent_factory/crews/health_check.py +1246 -0
- empathy_llm_toolkit/agent_factory/crews/refactoring.py +1128 -0
- empathy_llm_toolkit/agent_factory/crews/security_audit.py +1018 -0
- empathy_llm_toolkit/agent_factory/decorators.py +286 -0
- empathy_llm_toolkit/agent_factory/factory.py +558 -0
- empathy_llm_toolkit/agent_factory/framework.py +192 -0
- empathy_llm_toolkit/agent_factory/memory_integration.py +324 -0
- empathy_llm_toolkit/agent_factory/resilient.py +320 -0
- empathy_llm_toolkit/cli/__init__.py +8 -0
- empathy_llm_toolkit/cli/sync_claude.py +487 -0
- empathy_llm_toolkit/code_health.py +150 -3
- empathy_llm_toolkit/config/__init__.py +29 -0
- empathy_llm_toolkit/config/unified.py +295 -0
- empathy_llm_toolkit/routing/__init__.py +32 -0
- empathy_llm_toolkit/routing/model_router.py +362 -0
- empathy_llm_toolkit/security/IMPLEMENTATION_SUMMARY.md +413 -0
- empathy_llm_toolkit/security/PHASE2_COMPLETE.md +384 -0
- empathy_llm_toolkit/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- empathy_llm_toolkit/security/QUICK_REFERENCE.md +316 -0
- empathy_llm_toolkit/security/README.md +262 -0
- empathy_llm_toolkit/security/__init__.py +62 -0
- empathy_llm_toolkit/security/audit_logger.py +929 -0
- empathy_llm_toolkit/security/audit_logger_example.py +152 -0
- empathy_llm_toolkit/security/pii_scrubber.py +640 -0
- empathy_llm_toolkit/security/secrets_detector.py +678 -0
- empathy_llm_toolkit/security/secrets_detector_example.py +304 -0
- empathy_llm_toolkit/security/secure_memdocs.py +1192 -0
- empathy_llm_toolkit/security/secure_memdocs_example.py +278 -0
- empathy_llm_toolkit/wizards/__init__.py +38 -0
- empathy_llm_toolkit/wizards/base_wizard.py +364 -0
- empathy_llm_toolkit/wizards/customer_support_wizard.py +190 -0
- empathy_llm_toolkit/wizards/healthcare_wizard.py +362 -0
- empathy_llm_toolkit/wizards/patient_assessment_README.md +64 -0
- empathy_llm_toolkit/wizards/patient_assessment_wizard.py +193 -0
- empathy_llm_toolkit/wizards/technology_wizard.py +194 -0
- empathy_os/__init__.py +52 -52
- empathy_os/adaptive/__init__.py +13 -0
- empathy_os/adaptive/task_complexity.py +127 -0
- empathy_os/cache/__init__.py +117 -0
- empathy_os/cache/base.py +166 -0
- empathy_os/cache/dependency_manager.py +253 -0
- empathy_os/cache/hash_only.py +248 -0
- empathy_os/cache/hybrid.py +390 -0
- empathy_os/cache/storage.py +282 -0
- empathy_os/cli.py +118 -8
- empathy_os/cli_unified.py +121 -1
- empathy_os/config/__init__.py +63 -0
- empathy_os/config/xml_config.py +239 -0
- empathy_os/config.py +2 -1
- empathy_os/dashboard/__init__.py +15 -0
- empathy_os/dashboard/server.py +743 -0
- empathy_os/memory/__init__.py +195 -0
- empathy_os/memory/claude_memory.py +466 -0
- empathy_os/memory/config.py +224 -0
- empathy_os/memory/control_panel.py +1298 -0
- empathy_os/memory/edges.py +179 -0
- empathy_os/memory/graph.py +567 -0
- empathy_os/memory/long_term.py +1194 -0
- empathy_os/memory/nodes.py +179 -0
- empathy_os/memory/redis_bootstrap.py +540 -0
- empathy_os/memory/security/__init__.py +31 -0
- empathy_os/memory/security/audit_logger.py +930 -0
- empathy_os/memory/security/pii_scrubber.py +640 -0
- empathy_os/memory/security/secrets_detector.py +678 -0
- empathy_os/memory/short_term.py +2119 -0
- empathy_os/memory/storage/__init__.py +15 -0
- empathy_os/memory/summary_index.py +583 -0
- empathy_os/memory/unified.py +619 -0
- empathy_os/metrics/__init__.py +12 -0
- empathy_os/metrics/prompt_metrics.py +190 -0
- empathy_os/models/__init__.py +136 -0
- empathy_os/models/__main__.py +13 -0
- empathy_os/models/cli.py +655 -0
- empathy_os/models/empathy_executor.py +354 -0
- empathy_os/models/executor.py +252 -0
- empathy_os/models/fallback.py +671 -0
- empathy_os/models/provider_config.py +563 -0
- empathy_os/models/registry.py +382 -0
- empathy_os/models/tasks.py +302 -0
- empathy_os/models/telemetry.py +548 -0
- empathy_os/models/token_estimator.py +378 -0
- empathy_os/models/validation.py +274 -0
- empathy_os/monitoring/__init__.py +52 -0
- empathy_os/monitoring/alerts.py +23 -0
- empathy_os/monitoring/alerts_cli.py +268 -0
- empathy_os/monitoring/multi_backend.py +271 -0
- empathy_os/monitoring/otel_backend.py +363 -0
- empathy_os/optimization/__init__.py +19 -0
- empathy_os/optimization/context_optimizer.py +272 -0
- empathy_os/plugins/__init__.py +28 -0
- empathy_os/plugins/base.py +361 -0
- empathy_os/plugins/registry.py +268 -0
- empathy_os/project_index/__init__.py +30 -0
- empathy_os/project_index/cli.py +335 -0
- empathy_os/project_index/crew_integration.py +430 -0
- empathy_os/project_index/index.py +425 -0
- empathy_os/project_index/models.py +501 -0
- empathy_os/project_index/reports.py +473 -0
- empathy_os/project_index/scanner.py +538 -0
- empathy_os/prompts/__init__.py +61 -0
- empathy_os/prompts/config.py +77 -0
- empathy_os/prompts/context.py +177 -0
- empathy_os/prompts/parser.py +285 -0
- empathy_os/prompts/registry.py +313 -0
- empathy_os/prompts/templates.py +208 -0
- empathy_os/resilience/__init__.py +56 -0
- empathy_os/resilience/circuit_breaker.py +256 -0
- empathy_os/resilience/fallback.py +179 -0
- empathy_os/resilience/health.py +300 -0
- empathy_os/resilience/retry.py +209 -0
- empathy_os/resilience/timeout.py +135 -0
- empathy_os/routing/__init__.py +43 -0
- empathy_os/routing/chain_executor.py +433 -0
- empathy_os/routing/classifier.py +217 -0
- empathy_os/routing/smart_router.py +234 -0
- empathy_os/routing/wizard_registry.py +307 -0
- empathy_os/trust/__init__.py +28 -0
- empathy_os/trust/circuit_breaker.py +579 -0
- empathy_os/validation/__init__.py +19 -0
- empathy_os/validation/xml_validator.py +281 -0
- empathy_os/wizard_factory_cli.py +170 -0
- empathy_os/workflows/__init__.py +360 -0
- empathy_os/workflows/base.py +1660 -0
- empathy_os/workflows/bug_predict.py +962 -0
- empathy_os/workflows/code_review.py +960 -0
- empathy_os/workflows/code_review_adapters.py +310 -0
- empathy_os/workflows/code_review_pipeline.py +720 -0
- empathy_os/workflows/config.py +600 -0
- empathy_os/workflows/dependency_check.py +648 -0
- empathy_os/workflows/document_gen.py +1069 -0
- empathy_os/workflows/documentation_orchestrator.py +1205 -0
- empathy_os/workflows/health_check.py +679 -0
- empathy_os/workflows/keyboard_shortcuts/__init__.py +39 -0
- empathy_os/workflows/keyboard_shortcuts/generators.py +386 -0
- empathy_os/workflows/keyboard_shortcuts/parsers.py +414 -0
- empathy_os/workflows/keyboard_shortcuts/prompts.py +295 -0
- empathy_os/workflows/keyboard_shortcuts/schema.py +193 -0
- empathy_os/workflows/keyboard_shortcuts/workflow.py +505 -0
- empathy_os/workflows/manage_documentation.py +804 -0
- empathy_os/workflows/new_sample_workflow1.py +146 -0
- empathy_os/workflows/new_sample_workflow1_README.md +150 -0
- empathy_os/workflows/perf_audit.py +687 -0
- empathy_os/workflows/pr_review.py +748 -0
- empathy_os/workflows/progress.py +445 -0
- empathy_os/workflows/progress_server.py +322 -0
- empathy_os/workflows/refactor_plan.py +693 -0
- empathy_os/workflows/release_prep.py +808 -0
- empathy_os/workflows/research_synthesis.py +404 -0
- empathy_os/workflows/secure_release.py +585 -0
- empathy_os/workflows/security_adapters.py +297 -0
- empathy_os/workflows/security_audit.py +1046 -0
- empathy_os/workflows/step_config.py +234 -0
- empathy_os/workflows/test5.py +125 -0
- empathy_os/workflows/test5_README.md +158 -0
- empathy_os/workflows/test_gen.py +1855 -0
- empathy_os/workflows/test_lifecycle.py +526 -0
- empathy_os/workflows/test_maintenance.py +626 -0
- empathy_os/workflows/test_maintenance_cli.py +590 -0
- empathy_os/workflows/test_maintenance_crew.py +821 -0
- empathy_os/workflows/xml_enhanced_crew.py +285 -0
- empathy_software_plugin/cli/__init__.py +120 -0
- empathy_software_plugin/cli/inspect.py +362 -0
- empathy_software_plugin/cli.py +3 -1
- empathy_software_plugin/wizards/__init__.py +42 -0
- empathy_software_plugin/wizards/advanced_debugging_wizard.py +392 -0
- empathy_software_plugin/wizards/agent_orchestration_wizard.py +511 -0
- empathy_software_plugin/wizards/ai_collaboration_wizard.py +503 -0
- empathy_software_plugin/wizards/ai_context_wizard.py +441 -0
- empathy_software_plugin/wizards/ai_documentation_wizard.py +503 -0
- empathy_software_plugin/wizards/base_wizard.py +288 -0
- empathy_software_plugin/wizards/book_chapter_wizard.py +519 -0
- empathy_software_plugin/wizards/code_review_wizard.py +606 -0
- empathy_software_plugin/wizards/debugging/__init__.py +50 -0
- empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +414 -0
- empathy_software_plugin/wizards/debugging/config_loaders.py +442 -0
- empathy_software_plugin/wizards/debugging/fix_applier.py +469 -0
- empathy_software_plugin/wizards/debugging/language_patterns.py +383 -0
- empathy_software_plugin/wizards/debugging/linter_parsers.py +470 -0
- empathy_software_plugin/wizards/debugging/verification.py +369 -0
- empathy_software_plugin/wizards/enhanced_testing_wizard.py +537 -0
- empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +816 -0
- empathy_software_plugin/wizards/multi_model_wizard.py +501 -0
- empathy_software_plugin/wizards/pattern_extraction_wizard.py +422 -0
- empathy_software_plugin/wizards/pattern_retriever_wizard.py +400 -0
- empathy_software_plugin/wizards/performance/__init__.py +9 -0
- empathy_software_plugin/wizards/performance/bottleneck_detector.py +221 -0
- empathy_software_plugin/wizards/performance/profiler_parsers.py +278 -0
- empathy_software_plugin/wizards/performance/trajectory_analyzer.py +429 -0
- empathy_software_plugin/wizards/performance_profiling_wizard.py +305 -0
- empathy_software_plugin/wizards/prompt_engineering_wizard.py +425 -0
- empathy_software_plugin/wizards/rag_pattern_wizard.py +461 -0
- empathy_software_plugin/wizards/security/__init__.py +32 -0
- empathy_software_plugin/wizards/security/exploit_analyzer.py +290 -0
- empathy_software_plugin/wizards/security/owasp_patterns.py +241 -0
- empathy_software_plugin/wizards/security/vulnerability_scanner.py +604 -0
- empathy_software_plugin/wizards/security_analysis_wizard.py +322 -0
- empathy_software_plugin/wizards/security_learning_wizard.py +740 -0
- empathy_software_plugin/wizards/tech_debt_wizard.py +726 -0
- empathy_software_plugin/wizards/testing/__init__.py +27 -0
- empathy_software_plugin/wizards/testing/coverage_analyzer.py +459 -0
- empathy_software_plugin/wizards/testing/quality_analyzer.py +531 -0
- empathy_software_plugin/wizards/testing/test_suggester.py +533 -0
- empathy_software_plugin/wizards/testing_wizard.py +274 -0
- hot_reload/README.md +473 -0
- hot_reload/__init__.py +62 -0
- hot_reload/config.py +84 -0
- hot_reload/integration.py +228 -0
- hot_reload/reloader.py +298 -0
- hot_reload/watcher.py +179 -0
- hot_reload/websocket.py +176 -0
- scaffolding/README.md +589 -0
- scaffolding/__init__.py +35 -0
- scaffolding/__main__.py +14 -0
- scaffolding/cli.py +240 -0
- test_generator/__init__.py +38 -0
- test_generator/__main__.py +14 -0
- test_generator/cli.py +226 -0
- test_generator/generator.py +325 -0
- test_generator/risk_analyzer.py +216 -0
- workflow_patterns/__init__.py +33 -0
- workflow_patterns/behavior.py +249 -0
- workflow_patterns/core.py +76 -0
- workflow_patterns/output.py +99 -0
- workflow_patterns/registry.py +255 -0
- workflow_patterns/structural.py +288 -0
- workflow_scaffolding/__init__.py +11 -0
- workflow_scaffolding/__main__.py +12 -0
- workflow_scaffolding/cli.py +206 -0
- workflow_scaffolding/generator.py +265 -0
- agents/code_inspection/patterns/inspection/recurring_B112.json +0 -18
- agents/code_inspection/patterns/inspection/recurring_F541.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_FORMAT.json +0 -25
- agents/code_inspection/patterns/inspection/recurring_bug_20250822_def456.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20250915_abc123.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_3c5b9951.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_97c0f72f.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_a0871d53.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_20251212_a9b6ec41.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_bug_null_001.json +0 -16
- agents/code_inspection/patterns/inspection/recurring_builtin.json +0 -16
- agents/compliance_anticipation_agent.py +0 -1422
- agents/compliance_db.py +0 -339
- agents/epic_integration_wizard.py +0 -530
- agents/notifications.py +0 -291
- agents/trust_building_behaviors.py +0 -872
- empathy_framework-3.7.0.dist-info/RECORD +0 -105
- {empathy_framework-3.7.0.dist-info → empathy_framework-3.8.0.dist-info}/WHEEL +0 -0
- {empathy_framework-3.7.0.dist-info → empathy_framework-3.8.0.dist-info}/entry_points.txt +0 -0
- {empathy_framework-3.7.0.dist-info → empathy_framework-3.8.0.dist-info}/licenses/LICENSE +0 -0
- /empathy_os/{monitoring.py → agent_monitoring.py} +0 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"""Project Index Reports - Generate actionable reports from index data.
|
|
2
|
+
|
|
3
|
+
Reports for project management, sprint planning, and architecture decisions.
|
|
4
|
+
|
|
5
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
6
|
+
Licensed under Fair Source 0.9
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from .models import FileRecord, ProjectSummary, TestRequirement
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ReportGenerator:
|
|
16
|
+
"""Generates reports from project index data.
|
|
17
|
+
|
|
18
|
+
Reports are designed for:
|
|
19
|
+
- Human consumption (markdown)
|
|
20
|
+
- Agent/crew consumption (structured data)
|
|
21
|
+
- Dashboard display (summary metrics)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, summary: ProjectSummary, records: list[FileRecord]):
|
|
25
|
+
self.summary = summary
|
|
26
|
+
self.records = records
|
|
27
|
+
self._source_records = [r for r in records if r.category.value == "source"]
|
|
28
|
+
|
|
29
|
+
# ===== Test Gap Reports =====
|
|
30
|
+
|
|
31
|
+
def test_gap_report(self) -> dict[str, Any]:
|
|
32
|
+
"""Generate comprehensive test gap report.
|
|
33
|
+
|
|
34
|
+
Used by test-gen workflow and agents.
|
|
35
|
+
"""
|
|
36
|
+
needing_tests = [
|
|
37
|
+
r
|
|
38
|
+
for r in self._source_records
|
|
39
|
+
if r.test_requirement == TestRequirement.REQUIRED and not r.tests_exist
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
# Prioritize by impact
|
|
43
|
+
prioritized = sorted(needing_tests, key=lambda r: -r.impact_score)
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
"report_type": "test_gap",
|
|
47
|
+
"generated_at": datetime.now().isoformat(),
|
|
48
|
+
"summary": {
|
|
49
|
+
"total_files_needing_tests": len(needing_tests),
|
|
50
|
+
"total_loc_untested": sum(r.lines_of_code for r in needing_tests),
|
|
51
|
+
"high_impact_untested": len([r for r in needing_tests if r.impact_score >= 5.0]),
|
|
52
|
+
},
|
|
53
|
+
"priority_files": [
|
|
54
|
+
{
|
|
55
|
+
"path": r.path,
|
|
56
|
+
"impact_score": r.impact_score,
|
|
57
|
+
"lines_of_code": r.lines_of_code,
|
|
58
|
+
"imported_by_count": r.imported_by_count,
|
|
59
|
+
"reason": f"High impact ({r.impact_score:.1f}), {r.lines_of_code} LOC",
|
|
60
|
+
}
|
|
61
|
+
for r in prioritized[:20]
|
|
62
|
+
],
|
|
63
|
+
"by_directory": self._group_by_directory(needing_tests),
|
|
64
|
+
"recommendations": self._test_recommendations(needing_tests),
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
def _test_recommendations(self, needing_tests: list[FileRecord]) -> list[str]:
|
|
68
|
+
"""Generate test recommendations."""
|
|
69
|
+
recommendations = []
|
|
70
|
+
|
|
71
|
+
high_impact = [r for r in needing_tests if r.impact_score >= 5.0]
|
|
72
|
+
if high_impact:
|
|
73
|
+
recommendations.append(
|
|
74
|
+
f"PRIORITY: {len(high_impact)} high-impact files need tests. "
|
|
75
|
+
f"Start with: {', '.join(r.name for r in high_impact[:3])}",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if len(needing_tests) > 50:
|
|
79
|
+
recommendations.append(
|
|
80
|
+
f"Consider batch test generation - {len(needing_tests)} files need tests",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return recommendations
|
|
84
|
+
|
|
85
|
+
# ===== Staleness Reports =====
|
|
86
|
+
|
|
87
|
+
def staleness_report(self) -> dict[str, Any]:
|
|
88
|
+
"""Generate test staleness report.
|
|
89
|
+
|
|
90
|
+
Identifies files where code changed but tests didn't update.
|
|
91
|
+
"""
|
|
92
|
+
stale = [r for r in self._source_records if r.is_stale]
|
|
93
|
+
stale_sorted = sorted(stale, key=lambda r: -r.staleness_days)
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
"report_type": "staleness",
|
|
97
|
+
"generated_at": datetime.now().isoformat(),
|
|
98
|
+
"summary": {
|
|
99
|
+
"stale_file_count": len(stale),
|
|
100
|
+
"avg_staleness_days": (
|
|
101
|
+
sum(r.staleness_days for r in stale) / len(stale) if stale else 0
|
|
102
|
+
),
|
|
103
|
+
"max_staleness_days": max((r.staleness_days for r in stale), default=0),
|
|
104
|
+
},
|
|
105
|
+
"stale_files": [
|
|
106
|
+
{
|
|
107
|
+
"path": r.path,
|
|
108
|
+
"staleness_days": r.staleness_days,
|
|
109
|
+
"last_modified": r.last_modified.isoformat() if r.last_modified else None,
|
|
110
|
+
"test_file": r.test_file_path,
|
|
111
|
+
"tests_last_modified": (
|
|
112
|
+
r.tests_last_modified.isoformat() if r.tests_last_modified else None
|
|
113
|
+
),
|
|
114
|
+
}
|
|
115
|
+
for r in stale_sorted[:20]
|
|
116
|
+
],
|
|
117
|
+
"recommendations": [
|
|
118
|
+
f"Update tests for: {r.path} ({r.staleness_days} days stale)"
|
|
119
|
+
for r in stale_sorted[:5]
|
|
120
|
+
],
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# ===== Coverage Reports =====
|
|
124
|
+
|
|
125
|
+
def coverage_report(self) -> dict[str, Any]:
|
|
126
|
+
"""Generate coverage analysis report."""
|
|
127
|
+
with_coverage = [r for r in self._source_records if r.coverage_percent > 0]
|
|
128
|
+
low_coverage = [r for r in with_coverage if r.coverage_percent < 50]
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
"report_type": "coverage",
|
|
132
|
+
"generated_at": datetime.now().isoformat(),
|
|
133
|
+
"summary": {
|
|
134
|
+
"avg_coverage": self.summary.test_coverage_avg,
|
|
135
|
+
"files_with_data": len(with_coverage),
|
|
136
|
+
"files_below_50pct": len(low_coverage),
|
|
137
|
+
},
|
|
138
|
+
"low_coverage_files": [
|
|
139
|
+
{
|
|
140
|
+
"path": r.path,
|
|
141
|
+
"coverage_percent": r.coverage_percent,
|
|
142
|
+
"impact_score": r.impact_score,
|
|
143
|
+
}
|
|
144
|
+
for r in sorted(low_coverage, key=lambda r: r.coverage_percent)[:20]
|
|
145
|
+
],
|
|
146
|
+
"coverage_by_directory": self._coverage_by_directory(),
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
def _coverage_by_directory(self) -> dict[str, float]:
|
|
150
|
+
"""Calculate average coverage by directory."""
|
|
151
|
+
dir_coverage: dict[str, list[float]] = {}
|
|
152
|
+
|
|
153
|
+
for r in self._source_records:
|
|
154
|
+
if r.coverage_percent > 0:
|
|
155
|
+
parts = r.path.split("/")
|
|
156
|
+
dir_name = parts[0] if len(parts) > 1 else "."
|
|
157
|
+
if dir_name not in dir_coverage:
|
|
158
|
+
dir_coverage[dir_name] = []
|
|
159
|
+
dir_coverage[dir_name].append(r.coverage_percent)
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
dir_name: sum(coverages) / len(coverages)
|
|
163
|
+
for dir_name, coverages in dir_coverage.items()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# ===== Project Health Report =====
|
|
167
|
+
|
|
168
|
+
def health_report(self) -> dict[str, Any]:
|
|
169
|
+
"""Generate overall project health report.
|
|
170
|
+
|
|
171
|
+
Comprehensive view for project managers and architects.
|
|
172
|
+
"""
|
|
173
|
+
health_score = self._calculate_health_score()
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
"report_type": "health",
|
|
177
|
+
"generated_at": datetime.now().isoformat(),
|
|
178
|
+
"health_score": health_score,
|
|
179
|
+
"health_grade": self._health_grade(health_score),
|
|
180
|
+
"summary": {
|
|
181
|
+
"total_files": self.summary.total_files,
|
|
182
|
+
"source_files": self.summary.source_files,
|
|
183
|
+
"test_files": self.summary.test_files,
|
|
184
|
+
"test_coverage_avg": self.summary.test_coverage_avg,
|
|
185
|
+
"test_to_code_ratio": self.summary.test_to_code_ratio,
|
|
186
|
+
"stale_file_count": self.summary.stale_file_count,
|
|
187
|
+
"files_needing_attention": self.summary.files_needing_attention,
|
|
188
|
+
},
|
|
189
|
+
"strengths": self._identify_strengths(),
|
|
190
|
+
"concerns": self._identify_concerns(),
|
|
191
|
+
"action_items": self._generate_action_items(),
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
def _calculate_health_score(self) -> float:
|
|
195
|
+
"""Calculate overall health score (0-100)."""
|
|
196
|
+
score = 50.0 # Base score
|
|
197
|
+
|
|
198
|
+
# Coverage bonus/penalty (up to +/- 25 points)
|
|
199
|
+
if self.summary.test_coverage_avg >= 80:
|
|
200
|
+
score += 25
|
|
201
|
+
elif self.summary.test_coverage_avg >= 60:
|
|
202
|
+
score += 15
|
|
203
|
+
elif self.summary.test_coverage_avg >= 40:
|
|
204
|
+
score += 5
|
|
205
|
+
elif self.summary.test_coverage_avg < 20:
|
|
206
|
+
score -= 15
|
|
207
|
+
|
|
208
|
+
# Test existence bonus/penalty (up to +/- 15 points)
|
|
209
|
+
if self.summary.files_requiring_tests > 0:
|
|
210
|
+
test_ratio = self.summary.files_with_tests / self.summary.files_requiring_tests
|
|
211
|
+
score += (test_ratio - 0.5) * 30 # 0% = -15, 50% = 0, 100% = +15
|
|
212
|
+
|
|
213
|
+
# Staleness penalty (up to -10 points)
|
|
214
|
+
if self.summary.source_files > 0:
|
|
215
|
+
stale_ratio = self.summary.stale_file_count / self.summary.source_files
|
|
216
|
+
score -= stale_ratio * 20
|
|
217
|
+
|
|
218
|
+
# Documentation bonus (up to +10 points)
|
|
219
|
+
if self.summary.files_with_docstrings_pct >= 80:
|
|
220
|
+
score += 10
|
|
221
|
+
elif self.summary.files_with_docstrings_pct >= 50:
|
|
222
|
+
score += 5
|
|
223
|
+
|
|
224
|
+
return max(0, min(100, score))
|
|
225
|
+
|
|
226
|
+
def _health_grade(self, score: float) -> str:
|
|
227
|
+
"""Convert score to letter grade."""
|
|
228
|
+
if score >= 90:
|
|
229
|
+
return "A"
|
|
230
|
+
if score >= 80:
|
|
231
|
+
return "B"
|
|
232
|
+
if score >= 70:
|
|
233
|
+
return "C"
|
|
234
|
+
if score >= 60:
|
|
235
|
+
return "D"
|
|
236
|
+
return "F"
|
|
237
|
+
|
|
238
|
+
def _identify_strengths(self) -> list[str]:
|
|
239
|
+
"""Identify project strengths."""
|
|
240
|
+
strengths = []
|
|
241
|
+
|
|
242
|
+
if self.summary.test_coverage_avg >= 70:
|
|
243
|
+
strengths.append(f"Good test coverage ({self.summary.test_coverage_avg:.1f}%)")
|
|
244
|
+
|
|
245
|
+
if self.summary.files_with_docstrings_pct >= 70:
|
|
246
|
+
strengths.append(
|
|
247
|
+
f"Well documented ({self.summary.files_with_docstrings_pct:.1f}% with docstrings)",
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
if self.summary.files_with_type_hints_pct >= 70:
|
|
251
|
+
strengths.append(
|
|
252
|
+
f"Strong typing ({self.summary.files_with_type_hints_pct:.1f}% with type hints)",
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
if self.summary.stale_file_count == 0:
|
|
256
|
+
strengths.append("All tests are up to date")
|
|
257
|
+
|
|
258
|
+
return strengths
|
|
259
|
+
|
|
260
|
+
def _identify_concerns(self) -> list[str]:
|
|
261
|
+
"""Identify project concerns."""
|
|
262
|
+
concerns = []
|
|
263
|
+
|
|
264
|
+
if self.summary.test_coverage_avg < 50:
|
|
265
|
+
concerns.append(f"Low test coverage ({self.summary.test_coverage_avg:.1f}%)")
|
|
266
|
+
|
|
267
|
+
if self.summary.files_without_tests > 10:
|
|
268
|
+
concerns.append(f"{self.summary.files_without_tests} source files without tests")
|
|
269
|
+
|
|
270
|
+
if self.summary.stale_file_count > 5:
|
|
271
|
+
concerns.append(f"{self.summary.stale_file_count} files have stale tests")
|
|
272
|
+
|
|
273
|
+
if self.summary.critical_untested_files:
|
|
274
|
+
concerns.append(
|
|
275
|
+
f"{len(self.summary.critical_untested_files)} high-impact files lack tests",
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
return concerns
|
|
279
|
+
|
|
280
|
+
def _generate_action_items(self) -> list[dict[str, Any]]:
|
|
281
|
+
"""Generate prioritized action items."""
|
|
282
|
+
items = []
|
|
283
|
+
|
|
284
|
+
# Critical untested files
|
|
285
|
+
for path in self.summary.critical_untested_files[:3]:
|
|
286
|
+
items.append(
|
|
287
|
+
{
|
|
288
|
+
"priority": "high",
|
|
289
|
+
"action": f"Add tests for {path}",
|
|
290
|
+
"reason": "High-impact file without tests",
|
|
291
|
+
},
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Stale tests
|
|
295
|
+
for path in self.summary.most_stale_files[:3]:
|
|
296
|
+
items.append(
|
|
297
|
+
{
|
|
298
|
+
"priority": "medium",
|
|
299
|
+
"action": f"Update tests for {path}",
|
|
300
|
+
"reason": "Tests are stale",
|
|
301
|
+
},
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
return items
|
|
305
|
+
|
|
306
|
+
# ===== Sprint Planning Report =====
|
|
307
|
+
|
|
308
|
+
def sprint_planning_report(self, sprint_capacity: int = 10) -> dict[str, Any]:
|
|
309
|
+
"""Generate sprint planning report.
|
|
310
|
+
|
|
311
|
+
Suggests files to address based on priority and capacity.
|
|
312
|
+
"""
|
|
313
|
+
attention_files = [r for r in self._source_records if r.needs_attention]
|
|
314
|
+
prioritized = sorted(attention_files, key=lambda r: -r.impact_score)
|
|
315
|
+
|
|
316
|
+
# Select files up to sprint capacity
|
|
317
|
+
sprint_files = prioritized[:sprint_capacity]
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
"report_type": "sprint_planning",
|
|
321
|
+
"generated_at": datetime.now().isoformat(),
|
|
322
|
+
"sprint_capacity": sprint_capacity,
|
|
323
|
+
"suggested_work": [
|
|
324
|
+
{
|
|
325
|
+
"path": r.path,
|
|
326
|
+
"impact_score": r.impact_score,
|
|
327
|
+
"reasons": r.attention_reasons,
|
|
328
|
+
"estimated_effort": self._estimate_effort(r),
|
|
329
|
+
}
|
|
330
|
+
for r in sprint_files
|
|
331
|
+
],
|
|
332
|
+
"backlog": [
|
|
333
|
+
{"path": r.path, "reasons": r.attention_reasons}
|
|
334
|
+
for r in prioritized[sprint_capacity : sprint_capacity + 10]
|
|
335
|
+
],
|
|
336
|
+
"metrics_to_track": [
|
|
337
|
+
"Test coverage change",
|
|
338
|
+
"Files needing attention (before/after)",
|
|
339
|
+
"Staleness reduction",
|
|
340
|
+
],
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
def _estimate_effort(self, record: FileRecord) -> str:
|
|
344
|
+
"""Estimate effort to address file."""
|
|
345
|
+
loc = record.lines_of_code
|
|
346
|
+
|
|
347
|
+
if not record.tests_exist:
|
|
348
|
+
if loc < 50:
|
|
349
|
+
return "small (1-2 hours)"
|
|
350
|
+
if loc < 200:
|
|
351
|
+
return "medium (2-4 hours)"
|
|
352
|
+
return "large (4+ hours)"
|
|
353
|
+
if record.is_stale:
|
|
354
|
+
return "small (update existing tests)"
|
|
355
|
+
return "varies"
|
|
356
|
+
|
|
357
|
+
# ===== Markdown Reports =====
|
|
358
|
+
|
|
359
|
+
def to_markdown(self, report_type: str = "health") -> str:
|
|
360
|
+
"""Generate markdown formatted report.
|
|
361
|
+
|
|
362
|
+
For human consumption or documentation.
|
|
363
|
+
"""
|
|
364
|
+
if report_type == "health":
|
|
365
|
+
return self._health_markdown()
|
|
366
|
+
if report_type == "test_gap":
|
|
367
|
+
return self._test_gap_markdown()
|
|
368
|
+
if report_type == "staleness":
|
|
369
|
+
return self._staleness_markdown()
|
|
370
|
+
return self._health_markdown()
|
|
371
|
+
|
|
372
|
+
def _health_markdown(self) -> str:
|
|
373
|
+
"""Generate health report in markdown."""
|
|
374
|
+
report = self.health_report()
|
|
375
|
+
|
|
376
|
+
lines = [
|
|
377
|
+
"# Project Health Report",
|
|
378
|
+
"",
|
|
379
|
+
f"**Generated:** {report['generated_at']}",
|
|
380
|
+
f"**Health Score:** {report['health_score']:.1f}/100 ({report['health_grade']})",
|
|
381
|
+
"",
|
|
382
|
+
"## Summary",
|
|
383
|
+
"",
|
|
384
|
+
f"- **Total Files:** {report['summary']['total_files']}",
|
|
385
|
+
f"- **Source Files:** {report['summary']['source_files']}",
|
|
386
|
+
f"- **Test Files:** {report['summary']['test_files']}",
|
|
387
|
+
f"- **Average Coverage:** {report['summary']['test_coverage_avg']:.1f}%",
|
|
388
|
+
f"- **Files Needing Attention:** {report['summary']['files_needing_attention']}",
|
|
389
|
+
"",
|
|
390
|
+
]
|
|
391
|
+
|
|
392
|
+
if report["strengths"]:
|
|
393
|
+
lines.extend(["## Strengths", ""])
|
|
394
|
+
for strength in report["strengths"]:
|
|
395
|
+
lines.append(f"- {strength}")
|
|
396
|
+
lines.append("")
|
|
397
|
+
|
|
398
|
+
if report["concerns"]:
|
|
399
|
+
lines.extend(["## Concerns", ""])
|
|
400
|
+
for concern in report["concerns"]:
|
|
401
|
+
lines.append(f"- {concern}")
|
|
402
|
+
lines.append("")
|
|
403
|
+
|
|
404
|
+
if report["action_items"]:
|
|
405
|
+
lines.extend(["## Action Items", ""])
|
|
406
|
+
for item in report["action_items"]:
|
|
407
|
+
lines.append(f"- [{item['priority'].upper()}] {item['action']}")
|
|
408
|
+
lines.append("")
|
|
409
|
+
|
|
410
|
+
return "\n".join(lines)
|
|
411
|
+
|
|
412
|
+
def _test_gap_markdown(self) -> str:
|
|
413
|
+
"""Generate test gap report in markdown."""
|
|
414
|
+
report = self.test_gap_report()
|
|
415
|
+
|
|
416
|
+
lines = [
|
|
417
|
+
"# Test Gap Report",
|
|
418
|
+
"",
|
|
419
|
+
f"**Generated:** {report['generated_at']}",
|
|
420
|
+
"",
|
|
421
|
+
"## Summary",
|
|
422
|
+
"",
|
|
423
|
+
f"- **Files Needing Tests:** {report['summary']['total_files_needing_tests']}",
|
|
424
|
+
f"- **Lines of Code Untested:** {report['summary']['total_loc_untested']}",
|
|
425
|
+
f"- **High Impact Untested:** {report['summary']['high_impact_untested']}",
|
|
426
|
+
"",
|
|
427
|
+
"## Priority Files",
|
|
428
|
+
"",
|
|
429
|
+
]
|
|
430
|
+
|
|
431
|
+
for i, f in enumerate(report["priority_files"][:10], 1):
|
|
432
|
+
lines.append(f"{i}. `{f['path']}` - {f['reason']}")
|
|
433
|
+
|
|
434
|
+
lines.append("")
|
|
435
|
+
|
|
436
|
+
return "\n".join(lines)
|
|
437
|
+
|
|
438
|
+
def _staleness_markdown(self) -> str:
|
|
439
|
+
"""Generate staleness report in markdown."""
|
|
440
|
+
report = self.staleness_report()
|
|
441
|
+
|
|
442
|
+
lines = [
|
|
443
|
+
"# Test Staleness Report",
|
|
444
|
+
"",
|
|
445
|
+
f"**Generated:** {report['generated_at']}",
|
|
446
|
+
"",
|
|
447
|
+
"## Summary",
|
|
448
|
+
"",
|
|
449
|
+
f"- **Stale Files:** {report['summary']['stale_file_count']}",
|
|
450
|
+
f"- **Average Staleness:** {report['summary']['avg_staleness_days']:.1f} days",
|
|
451
|
+
f"- **Maximum Staleness:** {report['summary']['max_staleness_days']} days",
|
|
452
|
+
"",
|
|
453
|
+
"## Stale Files",
|
|
454
|
+
"",
|
|
455
|
+
]
|
|
456
|
+
|
|
457
|
+
for f in report["stale_files"][:10]:
|
|
458
|
+
lines.append(f"- `{f['path']}` - {f['staleness_days']} days stale")
|
|
459
|
+
|
|
460
|
+
lines.append("")
|
|
461
|
+
|
|
462
|
+
return "\n".join(lines)
|
|
463
|
+
|
|
464
|
+
# ===== Utility =====
|
|
465
|
+
|
|
466
|
+
def _group_by_directory(self, records: list[FileRecord]) -> dict[str, int]:
|
|
467
|
+
"""Group records by top-level directory."""
|
|
468
|
+
counts: dict[str, int] = {}
|
|
469
|
+
for r in records:
|
|
470
|
+
parts = r.path.split("/")
|
|
471
|
+
dir_name = parts[0] if len(parts) > 1 else "."
|
|
472
|
+
counts[dir_name] = counts.get(dir_name, 0) + 1
|
|
473
|
+
return counts
|