empathy-framework 3.2.3__py3-none-any.whl → 3.8.2__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 (328) hide show
  1. coach_wizards/__init__.py +11 -12
  2. coach_wizards/accessibility_wizard.py +12 -12
  3. coach_wizards/api_wizard.py +12 -12
  4. coach_wizards/base_wizard.py +26 -20
  5. coach_wizards/cicd_wizard.py +15 -13
  6. coach_wizards/code_reviewer_README.md +60 -0
  7. coach_wizards/code_reviewer_wizard.py +180 -0
  8. coach_wizards/compliance_wizard.py +12 -12
  9. coach_wizards/database_wizard.py +12 -12
  10. coach_wizards/debugging_wizard.py +12 -12
  11. coach_wizards/documentation_wizard.py +12 -12
  12. coach_wizards/generate_wizards.py +1 -2
  13. coach_wizards/localization_wizard.py +101 -19
  14. coach_wizards/migration_wizard.py +12 -12
  15. coach_wizards/monitoring_wizard.py +12 -12
  16. coach_wizards/observability_wizard.py +12 -12
  17. coach_wizards/performance_wizard.py +12 -12
  18. coach_wizards/prompt_engineering_wizard.py +22 -25
  19. coach_wizards/refactoring_wizard.py +12 -12
  20. coach_wizards/scaling_wizard.py +12 -12
  21. coach_wizards/security_wizard.py +12 -12
  22. coach_wizards/testing_wizard.py +12 -12
  23. {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/METADATA +513 -58
  24. empathy_framework-3.8.2.dist-info/RECORD +333 -0
  25. empathy_framework-3.8.2.dist-info/entry_points.txt +22 -0
  26. {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/top_level.txt +5 -1
  27. empathy_healthcare_plugin/__init__.py +1 -2
  28. empathy_healthcare_plugin/monitors/__init__.py +9 -0
  29. empathy_healthcare_plugin/monitors/clinical_protocol_monitor.py +315 -0
  30. empathy_healthcare_plugin/monitors/monitoring/__init__.py +44 -0
  31. empathy_healthcare_plugin/monitors/monitoring/protocol_checker.py +300 -0
  32. empathy_healthcare_plugin/monitors/monitoring/protocol_loader.py +214 -0
  33. empathy_healthcare_plugin/monitors/monitoring/sensor_parsers.py +306 -0
  34. empathy_healthcare_plugin/monitors/monitoring/trajectory_analyzer.py +389 -0
  35. empathy_llm_toolkit/__init__.py +7 -7
  36. empathy_llm_toolkit/agent_factory/__init__.py +53 -0
  37. empathy_llm_toolkit/agent_factory/adapters/__init__.py +85 -0
  38. empathy_llm_toolkit/agent_factory/adapters/autogen_adapter.py +312 -0
  39. empathy_llm_toolkit/agent_factory/adapters/crewai_adapter.py +454 -0
  40. empathy_llm_toolkit/agent_factory/adapters/haystack_adapter.py +298 -0
  41. empathy_llm_toolkit/agent_factory/adapters/langchain_adapter.py +362 -0
  42. empathy_llm_toolkit/agent_factory/adapters/langgraph_adapter.py +333 -0
  43. empathy_llm_toolkit/agent_factory/adapters/native.py +228 -0
  44. empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +426 -0
  45. empathy_llm_toolkit/agent_factory/base.py +305 -0
  46. empathy_llm_toolkit/agent_factory/crews/__init__.py +67 -0
  47. empathy_llm_toolkit/agent_factory/crews/code_review.py +1113 -0
  48. empathy_llm_toolkit/agent_factory/crews/health_check.py +1246 -0
  49. empathy_llm_toolkit/agent_factory/crews/refactoring.py +1128 -0
  50. empathy_llm_toolkit/agent_factory/crews/security_audit.py +1018 -0
  51. empathy_llm_toolkit/agent_factory/decorators.py +286 -0
  52. empathy_llm_toolkit/agent_factory/factory.py +558 -0
  53. empathy_llm_toolkit/agent_factory/framework.py +192 -0
  54. empathy_llm_toolkit/agent_factory/memory_integration.py +324 -0
  55. empathy_llm_toolkit/agent_factory/resilient.py +320 -0
  56. empathy_llm_toolkit/claude_memory.py +14 -15
  57. empathy_llm_toolkit/cli/__init__.py +8 -0
  58. empathy_llm_toolkit/cli/sync_claude.py +487 -0
  59. empathy_llm_toolkit/code_health.py +177 -22
  60. empathy_llm_toolkit/config/__init__.py +29 -0
  61. empathy_llm_toolkit/config/unified.py +295 -0
  62. empathy_llm_toolkit/contextual_patterns.py +11 -12
  63. empathy_llm_toolkit/core.py +51 -49
  64. empathy_llm_toolkit/git_pattern_extractor.py +16 -12
  65. empathy_llm_toolkit/levels.py +6 -13
  66. empathy_llm_toolkit/pattern_confidence.py +14 -18
  67. empathy_llm_toolkit/pattern_resolver.py +10 -12
  68. empathy_llm_toolkit/pattern_summary.py +13 -11
  69. empathy_llm_toolkit/providers.py +194 -28
  70. empathy_llm_toolkit/routing/__init__.py +32 -0
  71. empathy_llm_toolkit/routing/model_router.py +362 -0
  72. empathy_llm_toolkit/security/IMPLEMENTATION_SUMMARY.md +413 -0
  73. empathy_llm_toolkit/security/PHASE2_COMPLETE.md +384 -0
  74. empathy_llm_toolkit/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
  75. empathy_llm_toolkit/security/QUICK_REFERENCE.md +316 -0
  76. empathy_llm_toolkit/security/README.md +262 -0
  77. empathy_llm_toolkit/security/__init__.py +62 -0
  78. empathy_llm_toolkit/security/audit_logger.py +929 -0
  79. empathy_llm_toolkit/security/audit_logger_example.py +152 -0
  80. empathy_llm_toolkit/security/pii_scrubber.py +640 -0
  81. empathy_llm_toolkit/security/secrets_detector.py +678 -0
  82. empathy_llm_toolkit/security/secrets_detector_example.py +304 -0
  83. empathy_llm_toolkit/security/secure_memdocs.py +1192 -0
  84. empathy_llm_toolkit/security/secure_memdocs_example.py +278 -0
  85. empathy_llm_toolkit/session_status.py +18 -20
  86. empathy_llm_toolkit/state.py +20 -21
  87. empathy_llm_toolkit/wizards/__init__.py +38 -0
  88. empathy_llm_toolkit/wizards/base_wizard.py +364 -0
  89. empathy_llm_toolkit/wizards/customer_support_wizard.py +190 -0
  90. empathy_llm_toolkit/wizards/healthcare_wizard.py +362 -0
  91. empathy_llm_toolkit/wizards/patient_assessment_README.md +64 -0
  92. empathy_llm_toolkit/wizards/patient_assessment_wizard.py +193 -0
  93. empathy_llm_toolkit/wizards/technology_wizard.py +194 -0
  94. empathy_os/__init__.py +76 -77
  95. empathy_os/adaptive/__init__.py +13 -0
  96. empathy_os/adaptive/task_complexity.py +127 -0
  97. empathy_os/{monitoring.py → agent_monitoring.py} +27 -27
  98. empathy_os/cache/__init__.py +117 -0
  99. empathy_os/cache/base.py +166 -0
  100. empathy_os/cache/dependency_manager.py +253 -0
  101. empathy_os/cache/hash_only.py +248 -0
  102. empathy_os/cache/hybrid.py +390 -0
  103. empathy_os/cache/storage.py +282 -0
  104. empathy_os/cli.py +515 -109
  105. empathy_os/cli_unified.py +189 -42
  106. empathy_os/config/__init__.py +63 -0
  107. empathy_os/config/xml_config.py +239 -0
  108. empathy_os/config.py +87 -36
  109. empathy_os/coordination.py +48 -54
  110. empathy_os/core.py +90 -99
  111. empathy_os/cost_tracker.py +20 -23
  112. empathy_os/dashboard/__init__.py +15 -0
  113. empathy_os/dashboard/server.py +743 -0
  114. empathy_os/discovery.py +9 -11
  115. empathy_os/emergence.py +20 -21
  116. empathy_os/exceptions.py +18 -30
  117. empathy_os/feedback_loops.py +27 -30
  118. empathy_os/levels.py +31 -34
  119. empathy_os/leverage_points.py +27 -28
  120. empathy_os/logging_config.py +11 -12
  121. empathy_os/memory/__init__.py +195 -0
  122. empathy_os/memory/claude_memory.py +466 -0
  123. empathy_os/memory/config.py +224 -0
  124. empathy_os/memory/control_panel.py +1298 -0
  125. empathy_os/memory/edges.py +179 -0
  126. empathy_os/memory/graph.py +567 -0
  127. empathy_os/memory/long_term.py +1194 -0
  128. empathy_os/memory/nodes.py +179 -0
  129. empathy_os/memory/redis_bootstrap.py +540 -0
  130. empathy_os/memory/security/__init__.py +31 -0
  131. empathy_os/memory/security/audit_logger.py +930 -0
  132. empathy_os/memory/security/pii_scrubber.py +640 -0
  133. empathy_os/memory/security/secrets_detector.py +678 -0
  134. empathy_os/memory/short_term.py +2119 -0
  135. empathy_os/memory/storage/__init__.py +15 -0
  136. empathy_os/memory/summary_index.py +583 -0
  137. empathy_os/memory/unified.py +619 -0
  138. empathy_os/metrics/__init__.py +12 -0
  139. empathy_os/metrics/prompt_metrics.py +190 -0
  140. empathy_os/models/__init__.py +136 -0
  141. empathy_os/models/__main__.py +13 -0
  142. empathy_os/models/cli.py +655 -0
  143. empathy_os/models/empathy_executor.py +354 -0
  144. empathy_os/models/executor.py +252 -0
  145. empathy_os/models/fallback.py +671 -0
  146. empathy_os/models/provider_config.py +563 -0
  147. empathy_os/models/registry.py +382 -0
  148. empathy_os/models/tasks.py +302 -0
  149. empathy_os/models/telemetry.py +548 -0
  150. empathy_os/models/token_estimator.py +378 -0
  151. empathy_os/models/validation.py +274 -0
  152. empathy_os/monitoring/__init__.py +52 -0
  153. empathy_os/monitoring/alerts.py +23 -0
  154. empathy_os/monitoring/alerts_cli.py +268 -0
  155. empathy_os/monitoring/multi_backend.py +271 -0
  156. empathy_os/monitoring/otel_backend.py +363 -0
  157. empathy_os/optimization/__init__.py +19 -0
  158. empathy_os/optimization/context_optimizer.py +272 -0
  159. empathy_os/pattern_library.py +29 -28
  160. empathy_os/persistence.py +30 -34
  161. empathy_os/platform_utils.py +261 -0
  162. empathy_os/plugins/__init__.py +28 -0
  163. empathy_os/plugins/base.py +361 -0
  164. empathy_os/plugins/registry.py +268 -0
  165. empathy_os/project_index/__init__.py +30 -0
  166. empathy_os/project_index/cli.py +335 -0
  167. empathy_os/project_index/crew_integration.py +430 -0
  168. empathy_os/project_index/index.py +425 -0
  169. empathy_os/project_index/models.py +501 -0
  170. empathy_os/project_index/reports.py +473 -0
  171. empathy_os/project_index/scanner.py +538 -0
  172. empathy_os/prompts/__init__.py +61 -0
  173. empathy_os/prompts/config.py +77 -0
  174. empathy_os/prompts/context.py +177 -0
  175. empathy_os/prompts/parser.py +285 -0
  176. empathy_os/prompts/registry.py +313 -0
  177. empathy_os/prompts/templates.py +208 -0
  178. empathy_os/redis_config.py +144 -58
  179. empathy_os/redis_memory.py +53 -56
  180. empathy_os/resilience/__init__.py +56 -0
  181. empathy_os/resilience/circuit_breaker.py +256 -0
  182. empathy_os/resilience/fallback.py +179 -0
  183. empathy_os/resilience/health.py +300 -0
  184. empathy_os/resilience/retry.py +209 -0
  185. empathy_os/resilience/timeout.py +135 -0
  186. empathy_os/routing/__init__.py +43 -0
  187. empathy_os/routing/chain_executor.py +433 -0
  188. empathy_os/routing/classifier.py +217 -0
  189. empathy_os/routing/smart_router.py +234 -0
  190. empathy_os/routing/wizard_registry.py +307 -0
  191. empathy_os/templates.py +12 -11
  192. empathy_os/trust/__init__.py +28 -0
  193. empathy_os/trust/circuit_breaker.py +579 -0
  194. empathy_os/trust_building.py +44 -36
  195. empathy_os/validation/__init__.py +19 -0
  196. empathy_os/validation/xml_validator.py +281 -0
  197. empathy_os/wizard_factory_cli.py +170 -0
  198. empathy_os/{workflows.py → workflow_commands.py} +123 -31
  199. empathy_os/workflows/__init__.py +360 -0
  200. empathy_os/workflows/base.py +1660 -0
  201. empathy_os/workflows/bug_predict.py +962 -0
  202. empathy_os/workflows/code_review.py +960 -0
  203. empathy_os/workflows/code_review_adapters.py +310 -0
  204. empathy_os/workflows/code_review_pipeline.py +720 -0
  205. empathy_os/workflows/config.py +600 -0
  206. empathy_os/workflows/dependency_check.py +648 -0
  207. empathy_os/workflows/document_gen.py +1069 -0
  208. empathy_os/workflows/documentation_orchestrator.py +1205 -0
  209. empathy_os/workflows/health_check.py +679 -0
  210. empathy_os/workflows/keyboard_shortcuts/__init__.py +39 -0
  211. empathy_os/workflows/keyboard_shortcuts/generators.py +386 -0
  212. empathy_os/workflows/keyboard_shortcuts/parsers.py +414 -0
  213. empathy_os/workflows/keyboard_shortcuts/prompts.py +295 -0
  214. empathy_os/workflows/keyboard_shortcuts/schema.py +193 -0
  215. empathy_os/workflows/keyboard_shortcuts/workflow.py +505 -0
  216. empathy_os/workflows/manage_documentation.py +804 -0
  217. empathy_os/workflows/new_sample_workflow1.py +146 -0
  218. empathy_os/workflows/new_sample_workflow1_README.md +150 -0
  219. empathy_os/workflows/perf_audit.py +687 -0
  220. empathy_os/workflows/pr_review.py +748 -0
  221. empathy_os/workflows/progress.py +445 -0
  222. empathy_os/workflows/progress_server.py +322 -0
  223. empathy_os/workflows/refactor_plan.py +693 -0
  224. empathy_os/workflows/release_prep.py +808 -0
  225. empathy_os/workflows/research_synthesis.py +404 -0
  226. empathy_os/workflows/secure_release.py +585 -0
  227. empathy_os/workflows/security_adapters.py +297 -0
  228. empathy_os/workflows/security_audit.py +1046 -0
  229. empathy_os/workflows/step_config.py +234 -0
  230. empathy_os/workflows/test5.py +125 -0
  231. empathy_os/workflows/test5_README.md +158 -0
  232. empathy_os/workflows/test_gen.py +1855 -0
  233. empathy_os/workflows/test_lifecycle.py +526 -0
  234. empathy_os/workflows/test_maintenance.py +626 -0
  235. empathy_os/workflows/test_maintenance_cli.py +590 -0
  236. empathy_os/workflows/test_maintenance_crew.py +821 -0
  237. empathy_os/workflows/xml_enhanced_crew.py +285 -0
  238. empathy_software_plugin/__init__.py +1 -2
  239. empathy_software_plugin/cli/__init__.py +120 -0
  240. empathy_software_plugin/cli/inspect.py +362 -0
  241. empathy_software_plugin/cli.py +35 -26
  242. empathy_software_plugin/plugin.py +4 -8
  243. empathy_software_plugin/wizards/__init__.py +42 -0
  244. empathy_software_plugin/wizards/advanced_debugging_wizard.py +392 -0
  245. empathy_software_plugin/wizards/agent_orchestration_wizard.py +511 -0
  246. empathy_software_plugin/wizards/ai_collaboration_wizard.py +503 -0
  247. empathy_software_plugin/wizards/ai_context_wizard.py +441 -0
  248. empathy_software_plugin/wizards/ai_documentation_wizard.py +503 -0
  249. empathy_software_plugin/wizards/base_wizard.py +288 -0
  250. empathy_software_plugin/wizards/book_chapter_wizard.py +519 -0
  251. empathy_software_plugin/wizards/code_review_wizard.py +606 -0
  252. empathy_software_plugin/wizards/debugging/__init__.py +50 -0
  253. empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +414 -0
  254. empathy_software_plugin/wizards/debugging/config_loaders.py +442 -0
  255. empathy_software_plugin/wizards/debugging/fix_applier.py +469 -0
  256. empathy_software_plugin/wizards/debugging/language_patterns.py +383 -0
  257. empathy_software_plugin/wizards/debugging/linter_parsers.py +470 -0
  258. empathy_software_plugin/wizards/debugging/verification.py +369 -0
  259. empathy_software_plugin/wizards/enhanced_testing_wizard.py +537 -0
  260. empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +816 -0
  261. empathy_software_plugin/wizards/multi_model_wizard.py +501 -0
  262. empathy_software_plugin/wizards/pattern_extraction_wizard.py +422 -0
  263. empathy_software_plugin/wizards/pattern_retriever_wizard.py +400 -0
  264. empathy_software_plugin/wizards/performance/__init__.py +9 -0
  265. empathy_software_plugin/wizards/performance/bottleneck_detector.py +221 -0
  266. empathy_software_plugin/wizards/performance/profiler_parsers.py +278 -0
  267. empathy_software_plugin/wizards/performance/trajectory_analyzer.py +429 -0
  268. empathy_software_plugin/wizards/performance_profiling_wizard.py +305 -0
  269. empathy_software_plugin/wizards/prompt_engineering_wizard.py +425 -0
  270. empathy_software_plugin/wizards/rag_pattern_wizard.py +461 -0
  271. empathy_software_plugin/wizards/security/__init__.py +32 -0
  272. empathy_software_plugin/wizards/security/exploit_analyzer.py +290 -0
  273. empathy_software_plugin/wizards/security/owasp_patterns.py +241 -0
  274. empathy_software_plugin/wizards/security/vulnerability_scanner.py +604 -0
  275. empathy_software_plugin/wizards/security_analysis_wizard.py +322 -0
  276. empathy_software_plugin/wizards/security_learning_wizard.py +740 -0
  277. empathy_software_plugin/wizards/tech_debt_wizard.py +726 -0
  278. empathy_software_plugin/wizards/testing/__init__.py +27 -0
  279. empathy_software_plugin/wizards/testing/coverage_analyzer.py +459 -0
  280. empathy_software_plugin/wizards/testing/quality_analyzer.py +531 -0
  281. empathy_software_plugin/wizards/testing/test_suggester.py +533 -0
  282. empathy_software_plugin/wizards/testing_wizard.py +274 -0
  283. hot_reload/README.md +473 -0
  284. hot_reload/__init__.py +62 -0
  285. hot_reload/config.py +84 -0
  286. hot_reload/integration.py +228 -0
  287. hot_reload/reloader.py +298 -0
  288. hot_reload/watcher.py +179 -0
  289. hot_reload/websocket.py +176 -0
  290. scaffolding/README.md +589 -0
  291. scaffolding/__init__.py +35 -0
  292. scaffolding/__main__.py +14 -0
  293. scaffolding/cli.py +240 -0
  294. test_generator/__init__.py +38 -0
  295. test_generator/__main__.py +14 -0
  296. test_generator/cli.py +226 -0
  297. test_generator/generator.py +325 -0
  298. test_generator/risk_analyzer.py +216 -0
  299. workflow_patterns/__init__.py +33 -0
  300. workflow_patterns/behavior.py +249 -0
  301. workflow_patterns/core.py +76 -0
  302. workflow_patterns/output.py +99 -0
  303. workflow_patterns/registry.py +255 -0
  304. workflow_patterns/structural.py +288 -0
  305. workflow_scaffolding/__init__.py +11 -0
  306. workflow_scaffolding/__main__.py +12 -0
  307. workflow_scaffolding/cli.py +206 -0
  308. workflow_scaffolding/generator.py +265 -0
  309. agents/code_inspection/patterns/inspection/recurring_B112.json +0 -18
  310. agents/code_inspection/patterns/inspection/recurring_F541.json +0 -16
  311. agents/code_inspection/patterns/inspection/recurring_FORMAT.json +0 -25
  312. agents/code_inspection/patterns/inspection/recurring_bug_20250822_def456.json +0 -16
  313. agents/code_inspection/patterns/inspection/recurring_bug_20250915_abc123.json +0 -16
  314. agents/code_inspection/patterns/inspection/recurring_bug_20251212_3c5b9951.json +0 -16
  315. agents/code_inspection/patterns/inspection/recurring_bug_20251212_97c0f72f.json +0 -16
  316. agents/code_inspection/patterns/inspection/recurring_bug_20251212_a0871d53.json +0 -16
  317. agents/code_inspection/patterns/inspection/recurring_bug_20251212_a9b6ec41.json +0 -16
  318. agents/code_inspection/patterns/inspection/recurring_bug_null_001.json +0 -16
  319. agents/code_inspection/patterns/inspection/recurring_builtin.json +0 -16
  320. agents/compliance_anticipation_agent.py +0 -1427
  321. agents/epic_integration_wizard.py +0 -541
  322. agents/trust_building_behaviors.py +0 -891
  323. empathy_framework-3.2.3.dist-info/RECORD +0 -104
  324. empathy_framework-3.2.3.dist-info/entry_points.txt +0 -7
  325. empathy_llm_toolkit/htmlcov/status.json +0 -1
  326. empathy_llm_toolkit/security/htmlcov/status.json +0 -1
  327. {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/WHEEL +0 -0
  328. {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,531 @@
1
+ """Test Quality Analyzer for Enhanced Testing Wizard
2
+
3
+ Analyzes test quality including flakiness detection, assertion quality,
4
+ test isolation, and execution performance.
5
+
6
+ Copyright 2025 Smart-AI-Memory
7
+ Licensed under Fair Source License 0.9
8
+ """
9
+
10
+ import re
11
+ from dataclasses import dataclass
12
+ from enum import Enum
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+
17
+ class TestQualityIssue(Enum):
18
+ """Types of test quality issues"""
19
+
20
+ FLAKY = "flaky" # Test has inconsistent results
21
+ NO_ASSERTIONS = "no_assertions" # Test doesn't verify anything
22
+ WEAK_ASSERTIONS = "weak_assertions" # Only uses assertTrue/assertFalse
23
+ SLOW = "slow" # Takes >1 second
24
+ NOT_ISOLATED = "not_isolated" # Depends on external state
25
+ HARDCODED_VALUES = "hardcoded" # Uses magic numbers/strings
26
+ SLEEP_USAGE = "sleep_usage" # Uses time.sleep()
27
+ RANDOM_USAGE = "random_usage" # Uses random without seed
28
+
29
+
30
+ @dataclass
31
+ class TestFunction:
32
+ """Represents a single test function"""
33
+
34
+ name: str
35
+ file_path: str
36
+ line_number: int
37
+ assertions_count: int
38
+ execution_time: float | None = None
39
+ is_async: bool = False
40
+ uses_fixtures: list[str] | None = None
41
+ issues: list[TestQualityIssue] | None = None
42
+
43
+ def __post_init__(self):
44
+ if self.uses_fixtures is None:
45
+ self.uses_fixtures = []
46
+ if self.issues is None:
47
+ self.issues = []
48
+
49
+ @property
50
+ def quality_score(self) -> float:
51
+ """Calculate quality score (0-100)
52
+
53
+ Factors:
54
+ - Has assertions: +30
55
+ - Good assertion count (2-10): +20
56
+ - Fast (<1s): +20
57
+ - No issues: +30
58
+ """
59
+ score = 0.0
60
+
61
+ # Assertions
62
+ if self.assertions_count > 0:
63
+ score += 30
64
+ if 2 <= self.assertions_count <= 10:
65
+ score += 20
66
+ elif self.assertions_count == 1:
67
+ score += 10
68
+
69
+ # Performance
70
+ if self.execution_time is not None:
71
+ if self.execution_time < 0.1:
72
+ score += 20 # Very fast
73
+ elif self.execution_time < 1.0:
74
+ score += 15 # Fast enough
75
+ elif self.execution_time < 5.0:
76
+ score += 5 # Acceptable
77
+
78
+ # Issues penalty
79
+ issue_penalty = len(self.issues) * 10
80
+ score = max(0, score + 30 - issue_penalty)
81
+
82
+ return min(100.0, score)
83
+
84
+
85
+ @dataclass
86
+ class TestQualityReport:
87
+ """Complete test quality analysis report"""
88
+
89
+ total_tests: int
90
+ high_quality_tests: int # Score ≥80
91
+ medium_quality_tests: int # Score 50-79
92
+ low_quality_tests: int # Score <50
93
+ flaky_tests: list[str]
94
+ slow_tests: list[str]
95
+ tests_without_assertions: list[str]
96
+ isolated_tests: int
97
+ average_quality_score: float
98
+ issues_by_type: dict[TestQualityIssue, int]
99
+ test_functions: dict[str, TestFunction]
100
+
101
+
102
+ class TestQualityAnalyzer:
103
+ """Analyzes test code quality to identify improvements.
104
+
105
+ Detects:
106
+ - Flaky tests (timing-dependent, random, external state)
107
+ - Missing or weak assertions
108
+ - Slow tests
109
+ - Poor isolation
110
+ - Anti-patterns
111
+ """
112
+
113
+ def __init__(self):
114
+ # Thresholds
115
+ self.slow_threshold = 1.0 # seconds
116
+ self.min_assertions = 1
117
+ self.max_assertions = 20 # Too many might indicate test doing too much
118
+
119
+ # Patterns for detection
120
+ self.assertion_patterns = [
121
+ r"assert\s+",
122
+ r"assertEqual",
123
+ r"assertTrue",
124
+ r"assertFalse",
125
+ r"assertIn",
126
+ r"assertRaises",
127
+ r"assertIsNone",
128
+ r"assertIsNotNone",
129
+ r"expect\(",
130
+ ]
131
+
132
+ self.flakiness_indicators = [
133
+ (r"time\.sleep\(", TestQualityIssue.SLEEP_USAGE),
134
+ (r"random\.", TestQualityIssue.RANDOM_USAGE),
135
+ (r"datetime\.now\(\)", TestQualityIssue.NOT_ISOLATED),
136
+ (r"uuid\.uuid4\(\)", TestQualityIssue.RANDOM_USAGE),
137
+ ]
138
+
139
+ def analyze_test_file(self, file_path: Path) -> list[TestFunction]:
140
+ """Analyze a test file for quality issues
141
+
142
+ Args:
143
+ file_path: Path to test file
144
+
145
+ Returns:
146
+ List of TestFunction objects with issues identified
147
+
148
+ """
149
+ if not file_path.exists():
150
+ raise FileNotFoundError(f"Test file not found: {file_path}")
151
+
152
+ with open(file_path, encoding="utf-8") as f:
153
+ content = f.read()
154
+
155
+ return self._parse_test_functions(content, str(file_path))
156
+
157
+ def _parse_test_functions(self, content: str, file_path: str) -> list[TestFunction]:
158
+ """Parse test functions from file content"""
159
+ test_functions = []
160
+ lines = content.split("\n")
161
+
162
+ # Find test function definitions
163
+ test_func_pattern = re.compile(r"^(\s*)(async\s+)?def\s+(test_\w+)\s*\(")
164
+
165
+ i = 0
166
+ while i < len(lines):
167
+ match = test_func_pattern.match(lines[i])
168
+ if match:
169
+ indent = len(match.group(1))
170
+ is_async = match.group(2) is not None
171
+ func_name = match.group(3)
172
+ line_num = i + 1
173
+
174
+ # Extract function body
175
+ func_body, end_line = self._extract_function_body(lines, i, indent)
176
+
177
+ # Analyze function
178
+ test_func = self._analyze_test_function(
179
+ func_name,
180
+ file_path,
181
+ line_num,
182
+ func_body,
183
+ is_async,
184
+ )
185
+ test_functions.append(test_func)
186
+
187
+ i = end_line
188
+ else:
189
+ i += 1
190
+
191
+ return test_functions
192
+
193
+ def _extract_function_body(
194
+ self,
195
+ lines: list[str],
196
+ start_line: int,
197
+ base_indent: int,
198
+ ) -> tuple[str, int]:
199
+ """Extract the body of a function"""
200
+ body_lines = [lines[start_line]]
201
+ i = start_line + 1
202
+
203
+ while i < len(lines):
204
+ line = lines[i]
205
+
206
+ # Skip empty lines
207
+ if not line.strip():
208
+ body_lines.append(line)
209
+ i += 1
210
+ continue
211
+
212
+ # Check indentation
213
+ current_indent = len(line) - len(line.lstrip())
214
+
215
+ # If less or equal indent and not empty, function ended
216
+ if current_indent <= base_indent and line.strip():
217
+ break
218
+
219
+ body_lines.append(line)
220
+ i += 1
221
+
222
+ return "\n".join(body_lines), i
223
+
224
+ def _analyze_test_function(
225
+ self,
226
+ func_name: str,
227
+ file_path: str,
228
+ line_num: int,
229
+ func_body: str,
230
+ is_async: bool,
231
+ ) -> TestFunction:
232
+ """Analyze a single test function"""
233
+ issues: list[TestQualityIssue] = []
234
+
235
+ # Count assertions
236
+ assertions_count = self._count_assertions(func_body)
237
+
238
+ # Check for no assertions
239
+ if assertions_count == 0:
240
+ issues.append(TestQualityIssue.NO_ASSERTIONS)
241
+
242
+ # Check for weak assertions (only assertTrue/False)
243
+ if self._has_only_weak_assertions(func_body):
244
+ issues.append(TestQualityIssue.WEAK_ASSERTIONS)
245
+
246
+ # Check for flakiness indicators
247
+ for pattern, issue_type in self.flakiness_indicators:
248
+ if re.search(pattern, func_body):
249
+ if issue_type not in issues:
250
+ issues.append(issue_type)
251
+
252
+ # Check for hardcoded values (magic numbers/strings)
253
+ if self._has_hardcoded_values(func_body):
254
+ issues.append(TestQualityIssue.HARDCODED_VALUES)
255
+
256
+ # Extract fixtures used
257
+ fixtures = self._extract_fixtures(func_body)
258
+
259
+ return TestFunction(
260
+ name=func_name,
261
+ file_path=file_path,
262
+ line_number=line_num,
263
+ assertions_count=assertions_count,
264
+ is_async=is_async,
265
+ uses_fixtures=fixtures,
266
+ issues=issues,
267
+ )
268
+
269
+ def _count_assertions(self, func_body: str) -> int:
270
+ """Count number of assertions in function"""
271
+ count = 0
272
+ for pattern in self.assertion_patterns:
273
+ matches = re.findall(pattern, func_body)
274
+ count += len(matches)
275
+ return count
276
+
277
+ def _has_only_weak_assertions(self, func_body: str) -> bool:
278
+ """Check if function only uses weak assertions (assertTrue/False)"""
279
+ weak_assertions = re.findall(r"assert(True|False)\(", func_body)
280
+ all_assertions = sum(
281
+ len(re.findall(pattern, func_body)) for pattern in self.assertion_patterns
282
+ )
283
+
284
+ # If all assertions are weak and there are some
285
+ return len(weak_assertions) == all_assertions and all_assertions > 0
286
+
287
+ def _has_hardcoded_values(self, func_body: str) -> bool:
288
+ """Detect hardcoded magic values"""
289
+ # Look for literal numbers (except common ones like 0, 1, 2)
290
+ magic_numbers = re.findall(r"\b[3-9]\d*\b", func_body)
291
+
292
+ # Look for hardcoded strings that aren't test names or common words
293
+ magic_strings = re.findall(r'["\']([a-zA-Z0-9]{10,})["\']', func_body)
294
+
295
+ # Threshold: more than 3 magic values is suspicious
296
+ return len(magic_numbers) + len(magic_strings) > 3
297
+
298
+ def _extract_fixtures(self, func_body: str) -> list[str]:
299
+ """Extract pytest fixtures used in function signature"""
300
+ # Match function signature parameters
301
+ sig_match = re.search(r"def\s+\w+\s*\(([^)]*)\)", func_body)
302
+ if not sig_match:
303
+ return []
304
+
305
+ params = sig_match.group(1).split(",")
306
+ fixtures = []
307
+
308
+ for param in params:
309
+ param = param.strip()
310
+ # Skip 'self' and empty params
311
+ if param and param != "self":
312
+ # Extract parameter name (before type hint if any)
313
+ fixture_name = param.split(":")[0].strip()
314
+ fixtures.append(fixture_name)
315
+
316
+ return fixtures
317
+
318
+ def analyze_test_execution(self, test_results: list[dict[str, Any]]) -> list[TestFunction]:
319
+ """Analyze test execution results (from pytest JSON report)
320
+
321
+ Args:
322
+ test_results: List of test result dicts with fields:
323
+ - nodeid: test identifier
324
+ - duration: execution time in seconds
325
+ - outcome: passed/failed/skipped
326
+ - call: execution details
327
+
328
+ Returns:
329
+ List of TestFunction objects with execution data
330
+
331
+ """
332
+ test_functions = []
333
+
334
+ for result in test_results:
335
+ # Parse nodeid (e.g., "tests/test_core.py::test_function_name")
336
+ nodeid = str(result.get("nodeid", ""))
337
+ parts = nodeid.split("::")
338
+
339
+ if len(parts) < 2:
340
+ continue
341
+
342
+ file_path = parts[0]
343
+ func_name = parts[1]
344
+
345
+ duration = result.get("duration", 0.0)
346
+
347
+ # Create basic TestFunction (would be enriched with code analysis)
348
+ test_func = TestFunction(
349
+ name=func_name,
350
+ file_path=file_path,
351
+ line_number=0, # Would need code analysis to get this
352
+ assertions_count=0, # Would need code analysis
353
+ execution_time=duration,
354
+ )
355
+
356
+ # Check if slow
357
+ if duration > self.slow_threshold:
358
+ test_func.issues.append(TestQualityIssue.SLOW)
359
+
360
+ # Detect flakiness from multiple runs
361
+ # (This would require historical data)
362
+
363
+ test_functions.append(test_func)
364
+
365
+ return test_functions
366
+
367
+ def detect_flaky_tests(self, historical_results: list[list[dict[str, Any]]]) -> list[str]:
368
+ """Detect flaky tests from historical test runs
369
+
370
+ A test is considered flaky if it has inconsistent results across runs
371
+ with the same code.
372
+
373
+ Args:
374
+ historical_results: List of test result lists from multiple runs
375
+
376
+ Returns:
377
+ List of test names that are flaky
378
+
379
+ """
380
+ if len(historical_results) < 2:
381
+ return []
382
+
383
+ # Track outcomes for each test
384
+ test_outcomes: dict[str, list[str]] = {}
385
+
386
+ for run_results in historical_results:
387
+ for result in run_results:
388
+ nodeid = result.get("nodeid", "")
389
+ outcome = result.get("outcome", "unknown")
390
+
391
+ if nodeid not in test_outcomes:
392
+ test_outcomes[nodeid] = []
393
+
394
+ test_outcomes[nodeid].append(outcome)
395
+
396
+ # Find tests with inconsistent outcomes
397
+ flaky_tests = []
398
+
399
+ for nodeid, outcomes in test_outcomes.items():
400
+ # If outcomes vary, test is flaky
401
+ unique_outcomes = set(outcomes)
402
+ if len(unique_outcomes) > 1:
403
+ # Exclude if only failed once (might be legitimate)
404
+ fail_count = outcomes.count("failed")
405
+ if fail_count > 1 or (fail_count == 1 and len(outcomes) > 2):
406
+ flaky_tests.append(nodeid)
407
+
408
+ return flaky_tests
409
+
410
+ def generate_quality_report(self, test_functions: list[TestFunction]) -> TestQualityReport:
411
+ """Generate comprehensive quality report
412
+
413
+ Args:
414
+ test_functions: List of analyzed test functions
415
+
416
+ Returns:
417
+ TestQualityReport with statistics and categorization
418
+
419
+ """
420
+ high_quality = []
421
+ medium_quality = []
422
+ low_quality = []
423
+ flaky_tests = []
424
+ slow_tests = []
425
+ no_assertions = []
426
+ isolated_count = 0
427
+
428
+ issues_by_type: dict[TestQualityIssue, int] = dict.fromkeys(TestQualityIssue, 0)
429
+
430
+ test_functions_dict = {}
431
+
432
+ for test_func in test_functions:
433
+ quality_score = test_func.quality_score
434
+ test_id = f"{test_func.file_path}::{test_func.name}"
435
+ test_functions_dict[test_id] = test_func
436
+
437
+ # Categorize by quality
438
+ if quality_score >= 80:
439
+ high_quality.append(test_id)
440
+ elif quality_score >= 50:
441
+ medium_quality.append(test_id)
442
+ else:
443
+ low_quality.append(test_id)
444
+
445
+ # Track specific issues
446
+ if TestQualityIssue.NO_ASSERTIONS in test_func.issues:
447
+ no_assertions.append(test_id)
448
+
449
+ if TestQualityIssue.SLOW in test_func.issues:
450
+ slow_tests.append(test_id)
451
+
452
+ if any(
453
+ issue in test_func.issues
454
+ for issue in [
455
+ TestQualityIssue.FLAKY,
456
+ TestQualityIssue.SLEEP_USAGE,
457
+ TestQualityIssue.RANDOM_USAGE,
458
+ TestQualityIssue.NOT_ISOLATED,
459
+ ]
460
+ ):
461
+ flaky_tests.append(test_id)
462
+
463
+ # Check isolation (no external dependencies)
464
+ if TestQualityIssue.NOT_ISOLATED not in test_func.issues:
465
+ isolated_count += 1
466
+
467
+ # Count issues by type
468
+ for issue in test_func.issues:
469
+ issues_by_type[issue] += 1
470
+
471
+ # Calculate average quality score
472
+ if test_functions:
473
+ avg_score = sum(tf.quality_score for tf in test_functions) / len(test_functions)
474
+ else:
475
+ avg_score = 0.0
476
+
477
+ return TestQualityReport(
478
+ total_tests=len(test_functions),
479
+ high_quality_tests=len(high_quality),
480
+ medium_quality_tests=len(medium_quality),
481
+ low_quality_tests=len(low_quality),
482
+ flaky_tests=flaky_tests,
483
+ slow_tests=slow_tests,
484
+ tests_without_assertions=no_assertions,
485
+ isolated_tests=isolated_count,
486
+ average_quality_score=avg_score,
487
+ issues_by_type=issues_by_type,
488
+ test_functions=test_functions_dict,
489
+ )
490
+
491
+ def generate_summary(self, report: TestQualityReport) -> str:
492
+ """Generate human-readable quality summary"""
493
+ summary = []
494
+ summary.append("=" * 60)
495
+ summary.append("TEST QUALITY ANALYSIS SUMMARY")
496
+ summary.append("=" * 60)
497
+ summary.append(f"Total Tests: {report.total_tests}")
498
+ summary.append(f"Average Quality Score: {report.average_quality_score:.1f}/100")
499
+ summary.append("")
500
+ summary.append("Quality Distribution:")
501
+ summary.append(f" ✅ High (≥80): {report.high_quality_tests} tests")
502
+ summary.append(f" ⚠️ Medium (50-79): {report.medium_quality_tests} tests")
503
+ summary.append(f" ❌ Low (<50): {report.low_quality_tests} tests")
504
+ summary.append("")
505
+
506
+ if report.tests_without_assertions:
507
+ summary.append(f"⚠️ Tests Without Assertions: {len(report.tests_without_assertions)}")
508
+ for test_id in report.tests_without_assertions[:3]:
509
+ summary.append(f" - {test_id}")
510
+ if len(report.tests_without_assertions) > 3:
511
+ summary.append(f" ... and {len(report.tests_without_assertions) - 3} more")
512
+ summary.append("")
513
+
514
+ if report.flaky_tests:
515
+ summary.append(f"🔴 Potentially Flaky Tests: {len(report.flaky_tests)}")
516
+ for test_id in report.flaky_tests[:3]:
517
+ summary.append(f" - {test_id}")
518
+ if len(report.flaky_tests) > 3:
519
+ summary.append(f" ... and {len(report.flaky_tests) - 3} more")
520
+ summary.append("")
521
+
522
+ if report.slow_tests:
523
+ summary.append(f"🐌 Slow Tests (>{self.slow_threshold}s): {len(report.slow_tests)}")
524
+ for test_id in report.slow_tests[:3]:
525
+ summary.append(f" - {test_id}")
526
+ if len(report.slow_tests) > 3:
527
+ summary.append(f" ... and {len(report.slow_tests) - 3} more")
528
+
529
+ summary.append("=" * 60)
530
+
531
+ return "\n".join(summary)