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.
Files changed (274) hide show
  1. coach_wizards/code_reviewer_README.md +60 -0
  2. coach_wizards/code_reviewer_wizard.py +180 -0
  3. {empathy_framework-3.7.0.dist-info → empathy_framework-3.8.0.dist-info}/METADATA +148 -11
  4. empathy_framework-3.8.0.dist-info/RECORD +333 -0
  5. {empathy_framework-3.7.0.dist-info → empathy_framework-3.8.0.dist-info}/top_level.txt +5 -1
  6. empathy_healthcare_plugin/monitors/__init__.py +9 -0
  7. empathy_healthcare_plugin/monitors/clinical_protocol_monitor.py +315 -0
  8. empathy_healthcare_plugin/monitors/monitoring/__init__.py +44 -0
  9. empathy_healthcare_plugin/monitors/monitoring/protocol_checker.py +300 -0
  10. empathy_healthcare_plugin/monitors/monitoring/protocol_loader.py +214 -0
  11. empathy_healthcare_plugin/monitors/monitoring/sensor_parsers.py +306 -0
  12. empathy_healthcare_plugin/monitors/monitoring/trajectory_analyzer.py +389 -0
  13. empathy_llm_toolkit/agent_factory/__init__.py +53 -0
  14. empathy_llm_toolkit/agent_factory/adapters/__init__.py +85 -0
  15. empathy_llm_toolkit/agent_factory/adapters/autogen_adapter.py +312 -0
  16. empathy_llm_toolkit/agent_factory/adapters/crewai_adapter.py +454 -0
  17. empathy_llm_toolkit/agent_factory/adapters/haystack_adapter.py +298 -0
  18. empathy_llm_toolkit/agent_factory/adapters/langchain_adapter.py +362 -0
  19. empathy_llm_toolkit/agent_factory/adapters/langgraph_adapter.py +333 -0
  20. empathy_llm_toolkit/agent_factory/adapters/native.py +228 -0
  21. empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +426 -0
  22. empathy_llm_toolkit/agent_factory/base.py +305 -0
  23. empathy_llm_toolkit/agent_factory/crews/__init__.py +67 -0
  24. empathy_llm_toolkit/agent_factory/crews/code_review.py +1113 -0
  25. empathy_llm_toolkit/agent_factory/crews/health_check.py +1246 -0
  26. empathy_llm_toolkit/agent_factory/crews/refactoring.py +1128 -0
  27. empathy_llm_toolkit/agent_factory/crews/security_audit.py +1018 -0
  28. empathy_llm_toolkit/agent_factory/decorators.py +286 -0
  29. empathy_llm_toolkit/agent_factory/factory.py +558 -0
  30. empathy_llm_toolkit/agent_factory/framework.py +192 -0
  31. empathy_llm_toolkit/agent_factory/memory_integration.py +324 -0
  32. empathy_llm_toolkit/agent_factory/resilient.py +320 -0
  33. empathy_llm_toolkit/cli/__init__.py +8 -0
  34. empathy_llm_toolkit/cli/sync_claude.py +487 -0
  35. empathy_llm_toolkit/code_health.py +150 -3
  36. empathy_llm_toolkit/config/__init__.py +29 -0
  37. empathy_llm_toolkit/config/unified.py +295 -0
  38. empathy_llm_toolkit/routing/__init__.py +32 -0
  39. empathy_llm_toolkit/routing/model_router.py +362 -0
  40. empathy_llm_toolkit/security/IMPLEMENTATION_SUMMARY.md +413 -0
  41. empathy_llm_toolkit/security/PHASE2_COMPLETE.md +384 -0
  42. empathy_llm_toolkit/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
  43. empathy_llm_toolkit/security/QUICK_REFERENCE.md +316 -0
  44. empathy_llm_toolkit/security/README.md +262 -0
  45. empathy_llm_toolkit/security/__init__.py +62 -0
  46. empathy_llm_toolkit/security/audit_logger.py +929 -0
  47. empathy_llm_toolkit/security/audit_logger_example.py +152 -0
  48. empathy_llm_toolkit/security/pii_scrubber.py +640 -0
  49. empathy_llm_toolkit/security/secrets_detector.py +678 -0
  50. empathy_llm_toolkit/security/secrets_detector_example.py +304 -0
  51. empathy_llm_toolkit/security/secure_memdocs.py +1192 -0
  52. empathy_llm_toolkit/security/secure_memdocs_example.py +278 -0
  53. empathy_llm_toolkit/wizards/__init__.py +38 -0
  54. empathy_llm_toolkit/wizards/base_wizard.py +364 -0
  55. empathy_llm_toolkit/wizards/customer_support_wizard.py +190 -0
  56. empathy_llm_toolkit/wizards/healthcare_wizard.py +362 -0
  57. empathy_llm_toolkit/wizards/patient_assessment_README.md +64 -0
  58. empathy_llm_toolkit/wizards/patient_assessment_wizard.py +193 -0
  59. empathy_llm_toolkit/wizards/technology_wizard.py +194 -0
  60. empathy_os/__init__.py +52 -52
  61. empathy_os/adaptive/__init__.py +13 -0
  62. empathy_os/adaptive/task_complexity.py +127 -0
  63. empathy_os/cache/__init__.py +117 -0
  64. empathy_os/cache/base.py +166 -0
  65. empathy_os/cache/dependency_manager.py +253 -0
  66. empathy_os/cache/hash_only.py +248 -0
  67. empathy_os/cache/hybrid.py +390 -0
  68. empathy_os/cache/storage.py +282 -0
  69. empathy_os/cli.py +118 -8
  70. empathy_os/cli_unified.py +121 -1
  71. empathy_os/config/__init__.py +63 -0
  72. empathy_os/config/xml_config.py +239 -0
  73. empathy_os/config.py +2 -1
  74. empathy_os/dashboard/__init__.py +15 -0
  75. empathy_os/dashboard/server.py +743 -0
  76. empathy_os/memory/__init__.py +195 -0
  77. empathy_os/memory/claude_memory.py +466 -0
  78. empathy_os/memory/config.py +224 -0
  79. empathy_os/memory/control_panel.py +1298 -0
  80. empathy_os/memory/edges.py +179 -0
  81. empathy_os/memory/graph.py +567 -0
  82. empathy_os/memory/long_term.py +1194 -0
  83. empathy_os/memory/nodes.py +179 -0
  84. empathy_os/memory/redis_bootstrap.py +540 -0
  85. empathy_os/memory/security/__init__.py +31 -0
  86. empathy_os/memory/security/audit_logger.py +930 -0
  87. empathy_os/memory/security/pii_scrubber.py +640 -0
  88. empathy_os/memory/security/secrets_detector.py +678 -0
  89. empathy_os/memory/short_term.py +2119 -0
  90. empathy_os/memory/storage/__init__.py +15 -0
  91. empathy_os/memory/summary_index.py +583 -0
  92. empathy_os/memory/unified.py +619 -0
  93. empathy_os/metrics/__init__.py +12 -0
  94. empathy_os/metrics/prompt_metrics.py +190 -0
  95. empathy_os/models/__init__.py +136 -0
  96. empathy_os/models/__main__.py +13 -0
  97. empathy_os/models/cli.py +655 -0
  98. empathy_os/models/empathy_executor.py +354 -0
  99. empathy_os/models/executor.py +252 -0
  100. empathy_os/models/fallback.py +671 -0
  101. empathy_os/models/provider_config.py +563 -0
  102. empathy_os/models/registry.py +382 -0
  103. empathy_os/models/tasks.py +302 -0
  104. empathy_os/models/telemetry.py +548 -0
  105. empathy_os/models/token_estimator.py +378 -0
  106. empathy_os/models/validation.py +274 -0
  107. empathy_os/monitoring/__init__.py +52 -0
  108. empathy_os/monitoring/alerts.py +23 -0
  109. empathy_os/monitoring/alerts_cli.py +268 -0
  110. empathy_os/monitoring/multi_backend.py +271 -0
  111. empathy_os/monitoring/otel_backend.py +363 -0
  112. empathy_os/optimization/__init__.py +19 -0
  113. empathy_os/optimization/context_optimizer.py +272 -0
  114. empathy_os/plugins/__init__.py +28 -0
  115. empathy_os/plugins/base.py +361 -0
  116. empathy_os/plugins/registry.py +268 -0
  117. empathy_os/project_index/__init__.py +30 -0
  118. empathy_os/project_index/cli.py +335 -0
  119. empathy_os/project_index/crew_integration.py +430 -0
  120. empathy_os/project_index/index.py +425 -0
  121. empathy_os/project_index/models.py +501 -0
  122. empathy_os/project_index/reports.py +473 -0
  123. empathy_os/project_index/scanner.py +538 -0
  124. empathy_os/prompts/__init__.py +61 -0
  125. empathy_os/prompts/config.py +77 -0
  126. empathy_os/prompts/context.py +177 -0
  127. empathy_os/prompts/parser.py +285 -0
  128. empathy_os/prompts/registry.py +313 -0
  129. empathy_os/prompts/templates.py +208 -0
  130. empathy_os/resilience/__init__.py +56 -0
  131. empathy_os/resilience/circuit_breaker.py +256 -0
  132. empathy_os/resilience/fallback.py +179 -0
  133. empathy_os/resilience/health.py +300 -0
  134. empathy_os/resilience/retry.py +209 -0
  135. empathy_os/resilience/timeout.py +135 -0
  136. empathy_os/routing/__init__.py +43 -0
  137. empathy_os/routing/chain_executor.py +433 -0
  138. empathy_os/routing/classifier.py +217 -0
  139. empathy_os/routing/smart_router.py +234 -0
  140. empathy_os/routing/wizard_registry.py +307 -0
  141. empathy_os/trust/__init__.py +28 -0
  142. empathy_os/trust/circuit_breaker.py +579 -0
  143. empathy_os/validation/__init__.py +19 -0
  144. empathy_os/validation/xml_validator.py +281 -0
  145. empathy_os/wizard_factory_cli.py +170 -0
  146. empathy_os/workflows/__init__.py +360 -0
  147. empathy_os/workflows/base.py +1660 -0
  148. empathy_os/workflows/bug_predict.py +962 -0
  149. empathy_os/workflows/code_review.py +960 -0
  150. empathy_os/workflows/code_review_adapters.py +310 -0
  151. empathy_os/workflows/code_review_pipeline.py +720 -0
  152. empathy_os/workflows/config.py +600 -0
  153. empathy_os/workflows/dependency_check.py +648 -0
  154. empathy_os/workflows/document_gen.py +1069 -0
  155. empathy_os/workflows/documentation_orchestrator.py +1205 -0
  156. empathy_os/workflows/health_check.py +679 -0
  157. empathy_os/workflows/keyboard_shortcuts/__init__.py +39 -0
  158. empathy_os/workflows/keyboard_shortcuts/generators.py +386 -0
  159. empathy_os/workflows/keyboard_shortcuts/parsers.py +414 -0
  160. empathy_os/workflows/keyboard_shortcuts/prompts.py +295 -0
  161. empathy_os/workflows/keyboard_shortcuts/schema.py +193 -0
  162. empathy_os/workflows/keyboard_shortcuts/workflow.py +505 -0
  163. empathy_os/workflows/manage_documentation.py +804 -0
  164. empathy_os/workflows/new_sample_workflow1.py +146 -0
  165. empathy_os/workflows/new_sample_workflow1_README.md +150 -0
  166. empathy_os/workflows/perf_audit.py +687 -0
  167. empathy_os/workflows/pr_review.py +748 -0
  168. empathy_os/workflows/progress.py +445 -0
  169. empathy_os/workflows/progress_server.py +322 -0
  170. empathy_os/workflows/refactor_plan.py +693 -0
  171. empathy_os/workflows/release_prep.py +808 -0
  172. empathy_os/workflows/research_synthesis.py +404 -0
  173. empathy_os/workflows/secure_release.py +585 -0
  174. empathy_os/workflows/security_adapters.py +297 -0
  175. empathy_os/workflows/security_audit.py +1046 -0
  176. empathy_os/workflows/step_config.py +234 -0
  177. empathy_os/workflows/test5.py +125 -0
  178. empathy_os/workflows/test5_README.md +158 -0
  179. empathy_os/workflows/test_gen.py +1855 -0
  180. empathy_os/workflows/test_lifecycle.py +526 -0
  181. empathy_os/workflows/test_maintenance.py +626 -0
  182. empathy_os/workflows/test_maintenance_cli.py +590 -0
  183. empathy_os/workflows/test_maintenance_crew.py +821 -0
  184. empathy_os/workflows/xml_enhanced_crew.py +285 -0
  185. empathy_software_plugin/cli/__init__.py +120 -0
  186. empathy_software_plugin/cli/inspect.py +362 -0
  187. empathy_software_plugin/cli.py +3 -1
  188. empathy_software_plugin/wizards/__init__.py +42 -0
  189. empathy_software_plugin/wizards/advanced_debugging_wizard.py +392 -0
  190. empathy_software_plugin/wizards/agent_orchestration_wizard.py +511 -0
  191. empathy_software_plugin/wizards/ai_collaboration_wizard.py +503 -0
  192. empathy_software_plugin/wizards/ai_context_wizard.py +441 -0
  193. empathy_software_plugin/wizards/ai_documentation_wizard.py +503 -0
  194. empathy_software_plugin/wizards/base_wizard.py +288 -0
  195. empathy_software_plugin/wizards/book_chapter_wizard.py +519 -0
  196. empathy_software_plugin/wizards/code_review_wizard.py +606 -0
  197. empathy_software_plugin/wizards/debugging/__init__.py +50 -0
  198. empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +414 -0
  199. empathy_software_plugin/wizards/debugging/config_loaders.py +442 -0
  200. empathy_software_plugin/wizards/debugging/fix_applier.py +469 -0
  201. empathy_software_plugin/wizards/debugging/language_patterns.py +383 -0
  202. empathy_software_plugin/wizards/debugging/linter_parsers.py +470 -0
  203. empathy_software_plugin/wizards/debugging/verification.py +369 -0
  204. empathy_software_plugin/wizards/enhanced_testing_wizard.py +537 -0
  205. empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +816 -0
  206. empathy_software_plugin/wizards/multi_model_wizard.py +501 -0
  207. empathy_software_plugin/wizards/pattern_extraction_wizard.py +422 -0
  208. empathy_software_plugin/wizards/pattern_retriever_wizard.py +400 -0
  209. empathy_software_plugin/wizards/performance/__init__.py +9 -0
  210. empathy_software_plugin/wizards/performance/bottleneck_detector.py +221 -0
  211. empathy_software_plugin/wizards/performance/profiler_parsers.py +278 -0
  212. empathy_software_plugin/wizards/performance/trajectory_analyzer.py +429 -0
  213. empathy_software_plugin/wizards/performance_profiling_wizard.py +305 -0
  214. empathy_software_plugin/wizards/prompt_engineering_wizard.py +425 -0
  215. empathy_software_plugin/wizards/rag_pattern_wizard.py +461 -0
  216. empathy_software_plugin/wizards/security/__init__.py +32 -0
  217. empathy_software_plugin/wizards/security/exploit_analyzer.py +290 -0
  218. empathy_software_plugin/wizards/security/owasp_patterns.py +241 -0
  219. empathy_software_plugin/wizards/security/vulnerability_scanner.py +604 -0
  220. empathy_software_plugin/wizards/security_analysis_wizard.py +322 -0
  221. empathy_software_plugin/wizards/security_learning_wizard.py +740 -0
  222. empathy_software_plugin/wizards/tech_debt_wizard.py +726 -0
  223. empathy_software_plugin/wizards/testing/__init__.py +27 -0
  224. empathy_software_plugin/wizards/testing/coverage_analyzer.py +459 -0
  225. empathy_software_plugin/wizards/testing/quality_analyzer.py +531 -0
  226. empathy_software_plugin/wizards/testing/test_suggester.py +533 -0
  227. empathy_software_plugin/wizards/testing_wizard.py +274 -0
  228. hot_reload/README.md +473 -0
  229. hot_reload/__init__.py +62 -0
  230. hot_reload/config.py +84 -0
  231. hot_reload/integration.py +228 -0
  232. hot_reload/reloader.py +298 -0
  233. hot_reload/watcher.py +179 -0
  234. hot_reload/websocket.py +176 -0
  235. scaffolding/README.md +589 -0
  236. scaffolding/__init__.py +35 -0
  237. scaffolding/__main__.py +14 -0
  238. scaffolding/cli.py +240 -0
  239. test_generator/__init__.py +38 -0
  240. test_generator/__main__.py +14 -0
  241. test_generator/cli.py +226 -0
  242. test_generator/generator.py +325 -0
  243. test_generator/risk_analyzer.py +216 -0
  244. workflow_patterns/__init__.py +33 -0
  245. workflow_patterns/behavior.py +249 -0
  246. workflow_patterns/core.py +76 -0
  247. workflow_patterns/output.py +99 -0
  248. workflow_patterns/registry.py +255 -0
  249. workflow_patterns/structural.py +288 -0
  250. workflow_scaffolding/__init__.py +11 -0
  251. workflow_scaffolding/__main__.py +12 -0
  252. workflow_scaffolding/cli.py +206 -0
  253. workflow_scaffolding/generator.py +265 -0
  254. agents/code_inspection/patterns/inspection/recurring_B112.json +0 -18
  255. agents/code_inspection/patterns/inspection/recurring_F541.json +0 -16
  256. agents/code_inspection/patterns/inspection/recurring_FORMAT.json +0 -25
  257. agents/code_inspection/patterns/inspection/recurring_bug_20250822_def456.json +0 -16
  258. agents/code_inspection/patterns/inspection/recurring_bug_20250915_abc123.json +0 -16
  259. agents/code_inspection/patterns/inspection/recurring_bug_20251212_3c5b9951.json +0 -16
  260. agents/code_inspection/patterns/inspection/recurring_bug_20251212_97c0f72f.json +0 -16
  261. agents/code_inspection/patterns/inspection/recurring_bug_20251212_a0871d53.json +0 -16
  262. agents/code_inspection/patterns/inspection/recurring_bug_20251212_a9b6ec41.json +0 -16
  263. agents/code_inspection/patterns/inspection/recurring_bug_null_001.json +0 -16
  264. agents/code_inspection/patterns/inspection/recurring_builtin.json +0 -16
  265. agents/compliance_anticipation_agent.py +0 -1422
  266. agents/compliance_db.py +0 -339
  267. agents/epic_integration_wizard.py +0 -530
  268. agents/notifications.py +0 -291
  269. agents/trust_building_behaviors.py +0 -872
  270. empathy_framework-3.7.0.dist-info/RECORD +0 -105
  271. {empathy_framework-3.7.0.dist-info → empathy_framework-3.8.0.dist-info}/WHEEL +0 -0
  272. {empathy_framework-3.7.0.dist-info → empathy_framework-3.8.0.dist-info}/entry_points.txt +0 -0
  273. {empathy_framework-3.7.0.dist-info → empathy_framework-3.8.0.dist-info}/licenses/LICENSE +0 -0
  274. /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