empathy-framework 4.6.6__py3-none-any.whl → 4.7.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 (247) hide show
  1. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.0.dist-info}/METADATA +7 -6
  2. empathy_framework-4.7.0.dist-info/RECORD +354 -0
  3. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.0.dist-info}/top_level.txt +0 -2
  4. empathy_healthcare_plugin/monitors/monitoring/__init__.py +9 -9
  5. empathy_llm_toolkit/agent_factory/__init__.py +6 -6
  6. empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +7 -10
  7. empathy_llm_toolkit/agents_md/__init__.py +22 -0
  8. empathy_llm_toolkit/agents_md/loader.py +218 -0
  9. empathy_llm_toolkit/agents_md/parser.py +271 -0
  10. empathy_llm_toolkit/agents_md/registry.py +307 -0
  11. empathy_llm_toolkit/commands/__init__.py +51 -0
  12. empathy_llm_toolkit/commands/context.py +375 -0
  13. empathy_llm_toolkit/commands/loader.py +301 -0
  14. empathy_llm_toolkit/commands/models.py +231 -0
  15. empathy_llm_toolkit/commands/parser.py +371 -0
  16. empathy_llm_toolkit/commands/registry.py +429 -0
  17. empathy_llm_toolkit/config/__init__.py +8 -8
  18. empathy_llm_toolkit/config/unified.py +3 -7
  19. empathy_llm_toolkit/context/__init__.py +22 -0
  20. empathy_llm_toolkit/context/compaction.py +455 -0
  21. empathy_llm_toolkit/context/manager.py +434 -0
  22. empathy_llm_toolkit/hooks/__init__.py +24 -0
  23. empathy_llm_toolkit/hooks/config.py +306 -0
  24. empathy_llm_toolkit/hooks/executor.py +289 -0
  25. empathy_llm_toolkit/hooks/registry.py +302 -0
  26. empathy_llm_toolkit/hooks/scripts/__init__.py +39 -0
  27. empathy_llm_toolkit/hooks/scripts/evaluate_session.py +201 -0
  28. empathy_llm_toolkit/hooks/scripts/first_time_init.py +285 -0
  29. empathy_llm_toolkit/hooks/scripts/pre_compact.py +207 -0
  30. empathy_llm_toolkit/hooks/scripts/session_end.py +183 -0
  31. empathy_llm_toolkit/hooks/scripts/session_start.py +163 -0
  32. empathy_llm_toolkit/hooks/scripts/suggest_compact.py +225 -0
  33. empathy_llm_toolkit/learning/__init__.py +30 -0
  34. empathy_llm_toolkit/learning/evaluator.py +438 -0
  35. empathy_llm_toolkit/learning/extractor.py +514 -0
  36. empathy_llm_toolkit/learning/storage.py +560 -0
  37. empathy_llm_toolkit/providers.py +4 -11
  38. empathy_llm_toolkit/security/__init__.py +17 -17
  39. empathy_llm_toolkit/utils/tokens.py +2 -5
  40. empathy_os/__init__.py +202 -70
  41. empathy_os/cache_monitor.py +5 -3
  42. empathy_os/cli/__init__.py +11 -55
  43. empathy_os/cli/__main__.py +29 -15
  44. empathy_os/cli/commands/inspection.py +21 -12
  45. empathy_os/cli/commands/memory.py +4 -12
  46. empathy_os/cli/commands/profiling.py +198 -0
  47. empathy_os/cli/commands/utilities.py +27 -7
  48. empathy_os/cli.py +28 -57
  49. empathy_os/cli_unified.py +525 -1164
  50. empathy_os/cost_tracker.py +9 -3
  51. empathy_os/dashboard/server.py +200 -2
  52. empathy_os/hot_reload/__init__.py +7 -7
  53. empathy_os/hot_reload/config.py +6 -7
  54. empathy_os/hot_reload/integration.py +35 -35
  55. empathy_os/hot_reload/reloader.py +57 -57
  56. empathy_os/hot_reload/watcher.py +28 -28
  57. empathy_os/hot_reload/websocket.py +2 -2
  58. empathy_os/memory/__init__.py +11 -4
  59. empathy_os/memory/claude_memory.py +1 -1
  60. empathy_os/memory/cross_session.py +8 -12
  61. empathy_os/memory/edges.py +6 -6
  62. empathy_os/memory/file_session.py +770 -0
  63. empathy_os/memory/graph.py +30 -30
  64. empathy_os/memory/nodes.py +6 -6
  65. empathy_os/memory/short_term.py +15 -9
  66. empathy_os/memory/unified.py +606 -140
  67. empathy_os/meta_workflows/agent_creator.py +3 -9
  68. empathy_os/meta_workflows/cli_meta_workflows.py +113 -53
  69. empathy_os/meta_workflows/form_engine.py +6 -18
  70. empathy_os/meta_workflows/intent_detector.py +64 -24
  71. empathy_os/meta_workflows/models.py +3 -1
  72. empathy_os/meta_workflows/pattern_learner.py +13 -31
  73. empathy_os/meta_workflows/plan_generator.py +55 -47
  74. empathy_os/meta_workflows/session_context.py +2 -3
  75. empathy_os/meta_workflows/workflow.py +20 -51
  76. empathy_os/models/cli.py +2 -2
  77. empathy_os/models/tasks.py +1 -2
  78. empathy_os/models/telemetry.py +4 -1
  79. empathy_os/models/token_estimator.py +3 -1
  80. empathy_os/monitoring/alerts.py +938 -9
  81. empathy_os/monitoring/alerts_cli.py +346 -183
  82. empathy_os/orchestration/execution_strategies.py +12 -29
  83. empathy_os/orchestration/pattern_learner.py +20 -26
  84. empathy_os/orchestration/real_tools.py +6 -15
  85. empathy_os/platform_utils.py +2 -1
  86. empathy_os/plugins/__init__.py +2 -2
  87. empathy_os/plugins/base.py +64 -64
  88. empathy_os/plugins/registry.py +32 -32
  89. empathy_os/project_index/index.py +49 -15
  90. empathy_os/project_index/models.py +1 -2
  91. empathy_os/project_index/reports.py +1 -1
  92. empathy_os/project_index/scanner.py +1 -0
  93. empathy_os/redis_memory.py +10 -7
  94. empathy_os/resilience/__init__.py +1 -1
  95. empathy_os/resilience/health.py +10 -10
  96. empathy_os/routing/__init__.py +7 -7
  97. empathy_os/routing/chain_executor.py +37 -37
  98. empathy_os/routing/classifier.py +36 -36
  99. empathy_os/routing/smart_router.py +40 -40
  100. empathy_os/routing/{wizard_registry.py → workflow_registry.py} +47 -47
  101. empathy_os/scaffolding/__init__.py +8 -8
  102. empathy_os/scaffolding/__main__.py +1 -1
  103. empathy_os/scaffolding/cli.py +28 -28
  104. empathy_os/socratic/__init__.py +3 -19
  105. empathy_os/socratic/ab_testing.py +25 -36
  106. empathy_os/socratic/blueprint.py +38 -38
  107. empathy_os/socratic/cli.py +34 -20
  108. empathy_os/socratic/collaboration.py +30 -28
  109. empathy_os/socratic/domain_templates.py +9 -1
  110. empathy_os/socratic/embeddings.py +17 -13
  111. empathy_os/socratic/engine.py +135 -70
  112. empathy_os/socratic/explainer.py +70 -60
  113. empathy_os/socratic/feedback.py +24 -19
  114. empathy_os/socratic/forms.py +15 -10
  115. empathy_os/socratic/generator.py +51 -35
  116. empathy_os/socratic/llm_analyzer.py +25 -23
  117. empathy_os/socratic/mcp_server.py +99 -159
  118. empathy_os/socratic/session.py +19 -13
  119. empathy_os/socratic/storage.py +98 -67
  120. empathy_os/socratic/success.py +38 -27
  121. empathy_os/socratic/visual_editor.py +51 -39
  122. empathy_os/socratic/web_ui.py +99 -66
  123. empathy_os/telemetry/cli.py +3 -1
  124. empathy_os/telemetry/usage_tracker.py +1 -3
  125. empathy_os/test_generator/__init__.py +3 -3
  126. empathy_os/test_generator/cli.py +28 -28
  127. empathy_os/test_generator/generator.py +64 -66
  128. empathy_os/test_generator/risk_analyzer.py +11 -11
  129. empathy_os/vscode_bridge.py +173 -0
  130. empathy_os/workflows/__init__.py +212 -120
  131. empathy_os/workflows/batch_processing.py +8 -24
  132. empathy_os/workflows/bug_predict.py +1 -1
  133. empathy_os/workflows/code_review.py +20 -5
  134. empathy_os/workflows/code_review_pipeline.py +13 -8
  135. empathy_os/workflows/keyboard_shortcuts/workflow.py +6 -2
  136. empathy_os/workflows/manage_documentation.py +1 -0
  137. empathy_os/workflows/orchestrated_health_check.py +6 -11
  138. empathy_os/workflows/orchestrated_release_prep.py +3 -3
  139. empathy_os/workflows/pr_review.py +18 -10
  140. empathy_os/workflows/progressive/__init__.py +2 -12
  141. empathy_os/workflows/progressive/cli.py +14 -37
  142. empathy_os/workflows/progressive/core.py +12 -12
  143. empathy_os/workflows/progressive/orchestrator.py +166 -144
  144. empathy_os/workflows/progressive/reports.py +22 -31
  145. empathy_os/workflows/progressive/telemetry.py +8 -14
  146. empathy_os/workflows/progressive/test_gen.py +29 -48
  147. empathy_os/workflows/progressive/workflow.py +31 -70
  148. empathy_os/workflows/release_prep.py +21 -6
  149. empathy_os/workflows/release_prep_crew.py +1 -0
  150. empathy_os/workflows/secure_release.py +13 -6
  151. empathy_os/workflows/security_audit.py +8 -3
  152. empathy_os/workflows/test_coverage_boost_crew.py +3 -2
  153. empathy_os/workflows/test_maintenance_crew.py +1 -0
  154. empathy_os/workflows/test_runner.py +16 -12
  155. empathy_software_plugin/SOFTWARE_PLUGIN_README.md +25 -703
  156. empathy_software_plugin/cli.py +0 -122
  157. coach_wizards/__init__.py +0 -45
  158. coach_wizards/accessibility_wizard.py +0 -91
  159. coach_wizards/api_wizard.py +0 -91
  160. coach_wizards/base_wizard.py +0 -209
  161. coach_wizards/cicd_wizard.py +0 -91
  162. coach_wizards/code_reviewer_README.md +0 -60
  163. coach_wizards/code_reviewer_wizard.py +0 -180
  164. coach_wizards/compliance_wizard.py +0 -91
  165. coach_wizards/database_wizard.py +0 -91
  166. coach_wizards/debugging_wizard.py +0 -91
  167. coach_wizards/documentation_wizard.py +0 -91
  168. coach_wizards/generate_wizards.py +0 -347
  169. coach_wizards/localization_wizard.py +0 -173
  170. coach_wizards/migration_wizard.py +0 -91
  171. coach_wizards/monitoring_wizard.py +0 -91
  172. coach_wizards/observability_wizard.py +0 -91
  173. coach_wizards/performance_wizard.py +0 -91
  174. coach_wizards/prompt_engineering_wizard.py +0 -661
  175. coach_wizards/refactoring_wizard.py +0 -91
  176. coach_wizards/scaling_wizard.py +0 -90
  177. coach_wizards/security_wizard.py +0 -92
  178. coach_wizards/testing_wizard.py +0 -91
  179. empathy_framework-4.6.6.dist-info/RECORD +0 -410
  180. empathy_llm_toolkit/wizards/__init__.py +0 -43
  181. empathy_llm_toolkit/wizards/base_wizard.py +0 -364
  182. empathy_llm_toolkit/wizards/customer_support_wizard.py +0 -190
  183. empathy_llm_toolkit/wizards/healthcare_wizard.py +0 -378
  184. empathy_llm_toolkit/wizards/patient_assessment_README.md +0 -64
  185. empathy_llm_toolkit/wizards/patient_assessment_wizard.py +0 -193
  186. empathy_llm_toolkit/wizards/technology_wizard.py +0 -209
  187. empathy_os/wizard_factory_cli.py +0 -170
  188. empathy_software_plugin/wizards/__init__.py +0 -42
  189. empathy_software_plugin/wizards/advanced_debugging_wizard.py +0 -395
  190. empathy_software_plugin/wizards/agent_orchestration_wizard.py +0 -511
  191. empathy_software_plugin/wizards/ai_collaboration_wizard.py +0 -503
  192. empathy_software_plugin/wizards/ai_context_wizard.py +0 -441
  193. empathy_software_plugin/wizards/ai_documentation_wizard.py +0 -503
  194. empathy_software_plugin/wizards/base_wizard.py +0 -288
  195. empathy_software_plugin/wizards/book_chapter_wizard.py +0 -519
  196. empathy_software_plugin/wizards/code_review_wizard.py +0 -604
  197. empathy_software_plugin/wizards/debugging/__init__.py +0 -50
  198. empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +0 -414
  199. empathy_software_plugin/wizards/debugging/config_loaders.py +0 -446
  200. empathy_software_plugin/wizards/debugging/fix_applier.py +0 -469
  201. empathy_software_plugin/wizards/debugging/language_patterns.py +0 -385
  202. empathy_software_plugin/wizards/debugging/linter_parsers.py +0 -470
  203. empathy_software_plugin/wizards/debugging/verification.py +0 -369
  204. empathy_software_plugin/wizards/enhanced_testing_wizard.py +0 -537
  205. empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +0 -816
  206. empathy_software_plugin/wizards/multi_model_wizard.py +0 -501
  207. empathy_software_plugin/wizards/pattern_extraction_wizard.py +0 -422
  208. empathy_software_plugin/wizards/pattern_retriever_wizard.py +0 -400
  209. empathy_software_plugin/wizards/performance/__init__.py +0 -9
  210. empathy_software_plugin/wizards/performance/bottleneck_detector.py +0 -221
  211. empathy_software_plugin/wizards/performance/profiler_parsers.py +0 -278
  212. empathy_software_plugin/wizards/performance/trajectory_analyzer.py +0 -429
  213. empathy_software_plugin/wizards/performance_profiling_wizard.py +0 -305
  214. empathy_software_plugin/wizards/prompt_engineering_wizard.py +0 -425
  215. empathy_software_plugin/wizards/rag_pattern_wizard.py +0 -461
  216. empathy_software_plugin/wizards/security/__init__.py +0 -32
  217. empathy_software_plugin/wizards/security/exploit_analyzer.py +0 -290
  218. empathy_software_plugin/wizards/security/owasp_patterns.py +0 -241
  219. empathy_software_plugin/wizards/security/vulnerability_scanner.py +0 -604
  220. empathy_software_plugin/wizards/security_analysis_wizard.py +0 -322
  221. empathy_software_plugin/wizards/security_learning_wizard.py +0 -740
  222. empathy_software_plugin/wizards/tech_debt_wizard.py +0 -726
  223. empathy_software_plugin/wizards/testing/__init__.py +0 -27
  224. empathy_software_plugin/wizards/testing/coverage_analyzer.py +0 -459
  225. empathy_software_plugin/wizards/testing/quality_analyzer.py +0 -525
  226. empathy_software_plugin/wizards/testing/test_suggester.py +0 -533
  227. empathy_software_plugin/wizards/testing_wizard.py +0 -274
  228. wizards/__init__.py +0 -82
  229. wizards/admission_assessment_wizard.py +0 -644
  230. wizards/care_plan.py +0 -321
  231. wizards/clinical_assessment.py +0 -769
  232. wizards/discharge_planning.py +0 -77
  233. wizards/discharge_summary_wizard.py +0 -468
  234. wizards/dosage_calculation.py +0 -497
  235. wizards/incident_report_wizard.py +0 -454
  236. wizards/medication_reconciliation.py +0 -85
  237. wizards/nursing_assessment.py +0 -171
  238. wizards/patient_education.py +0 -654
  239. wizards/quality_improvement.py +0 -705
  240. wizards/sbar_report.py +0 -324
  241. wizards/sbar_wizard.py +0 -608
  242. wizards/shift_handoff_wizard.py +0 -535
  243. wizards/soap_note_wizard.py +0 -679
  244. wizards/treatment_plan.py +0 -15
  245. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.0.dist-info}/WHEEL +0 -0
  246. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.0.dist-info}/entry_points.txt +0 -0
  247. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,525 +0,0 @@
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, field
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] = field(default_factory=list)
41
- issues: list[TestQualityIssue] = field(default_factory=list)
42
-
43
- @property
44
- def quality_score(self) -> float:
45
- """Calculate quality score (0-100)
46
-
47
- Factors:
48
- - Has assertions: +30
49
- - Good assertion count (2-10): +20
50
- - Fast (<1s): +20
51
- - No issues: +30
52
- """
53
- score = 0.0
54
-
55
- # Assertions
56
- if self.assertions_count > 0:
57
- score += 30
58
- if 2 <= self.assertions_count <= 10:
59
- score += 20
60
- elif self.assertions_count == 1:
61
- score += 10
62
-
63
- # Performance
64
- if self.execution_time is not None:
65
- if self.execution_time < 0.1:
66
- score += 20 # Very fast
67
- elif self.execution_time < 1.0:
68
- score += 15 # Fast enough
69
- elif self.execution_time < 5.0:
70
- score += 5 # Acceptable
71
-
72
- # Issues penalty
73
- issue_penalty = len(self.issues) * 10
74
- score = max(0, score + 30 - issue_penalty)
75
-
76
- return min(100.0, score)
77
-
78
-
79
- @dataclass
80
- class TestQualityReport:
81
- """Complete test quality analysis report"""
82
-
83
- total_tests: int
84
- high_quality_tests: int # Score ≥80
85
- medium_quality_tests: int # Score 50-79
86
- low_quality_tests: int # Score <50
87
- flaky_tests: list[str]
88
- slow_tests: list[str]
89
- tests_without_assertions: list[str]
90
- isolated_tests: int
91
- average_quality_score: float
92
- issues_by_type: dict[TestQualityIssue, int]
93
- test_functions: dict[str, TestFunction]
94
-
95
-
96
- class TestQualityAnalyzer:
97
- """Analyzes test code quality to identify improvements.
98
-
99
- Detects:
100
- - Flaky tests (timing-dependent, random, external state)
101
- - Missing or weak assertions
102
- - Slow tests
103
- - Poor isolation
104
- - Anti-patterns
105
- """
106
-
107
- def __init__(self):
108
- # Thresholds
109
- self.slow_threshold = 1.0 # seconds
110
- self.min_assertions = 1
111
- self.max_assertions = 20 # Too many might indicate test doing too much
112
-
113
- # Patterns for detection
114
- self.assertion_patterns = [
115
- r"assert\s+",
116
- r"assertEqual",
117
- r"assertTrue",
118
- r"assertFalse",
119
- r"assertIn",
120
- r"assertRaises",
121
- r"assertIsNone",
122
- r"assertIsNotNone",
123
- r"expect\(",
124
- ]
125
-
126
- self.flakiness_indicators = [
127
- (r"time\.sleep\(", TestQualityIssue.SLEEP_USAGE),
128
- (r"random\.", TestQualityIssue.RANDOM_USAGE),
129
- (r"datetime\.now\(\)", TestQualityIssue.NOT_ISOLATED),
130
- (r"uuid\.uuid4\(\)", TestQualityIssue.RANDOM_USAGE),
131
- ]
132
-
133
- def analyze_test_file(self, file_path: Path) -> list[TestFunction]:
134
- """Analyze a test file for quality issues
135
-
136
- Args:
137
- file_path: Path to test file
138
-
139
- Returns:
140
- List of TestFunction objects with issues identified
141
-
142
- """
143
- if not file_path.exists():
144
- raise FileNotFoundError(f"Test file not found: {file_path}")
145
-
146
- with open(file_path, encoding="utf-8") as f:
147
- content = f.read()
148
-
149
- return self._parse_test_functions(content, str(file_path))
150
-
151
- def _parse_test_functions(self, content: str, file_path: str) -> list[TestFunction]:
152
- """Parse test functions from file content"""
153
- test_functions = []
154
- lines = content.split("\n")
155
-
156
- # Find test function definitions
157
- test_func_pattern = re.compile(r"^(\s*)(async\s+)?def\s+(test_\w+)\s*\(")
158
-
159
- i = 0
160
- while i < len(lines):
161
- match = test_func_pattern.match(lines[i])
162
- if match:
163
- indent = len(match.group(1))
164
- is_async = match.group(2) is not None
165
- func_name = match.group(3)
166
- line_num = i + 1
167
-
168
- # Extract function body
169
- func_body, end_line = self._extract_function_body(lines, i, indent)
170
-
171
- # Analyze function
172
- test_func = self._analyze_test_function(
173
- func_name,
174
- file_path,
175
- line_num,
176
- func_body,
177
- is_async,
178
- )
179
- test_functions.append(test_func)
180
-
181
- i = end_line
182
- else:
183
- i += 1
184
-
185
- return test_functions
186
-
187
- def _extract_function_body(
188
- self,
189
- lines: list[str],
190
- start_line: int,
191
- base_indent: int,
192
- ) -> tuple[str, int]:
193
- """Extract the body of a function"""
194
- body_lines = [lines[start_line]]
195
- i = start_line + 1
196
-
197
- while i < len(lines):
198
- line = lines[i]
199
-
200
- # Skip empty lines
201
- if not line.strip():
202
- body_lines.append(line)
203
- i += 1
204
- continue
205
-
206
- # Check indentation
207
- current_indent = len(line) - len(line.lstrip())
208
-
209
- # If less or equal indent and not empty, function ended
210
- if current_indent <= base_indent and line.strip():
211
- break
212
-
213
- body_lines.append(line)
214
- i += 1
215
-
216
- return "\n".join(body_lines), i
217
-
218
- def _analyze_test_function(
219
- self,
220
- func_name: str,
221
- file_path: str,
222
- line_num: int,
223
- func_body: str,
224
- is_async: bool,
225
- ) -> TestFunction:
226
- """Analyze a single test function"""
227
- issues: list[TestQualityIssue] = []
228
-
229
- # Count assertions
230
- assertions_count = self._count_assertions(func_body)
231
-
232
- # Check for no assertions
233
- if assertions_count == 0:
234
- issues.append(TestQualityIssue.NO_ASSERTIONS)
235
-
236
- # Check for weak assertions (only assertTrue/False)
237
- if self._has_only_weak_assertions(func_body):
238
- issues.append(TestQualityIssue.WEAK_ASSERTIONS)
239
-
240
- # Check for flakiness indicators
241
- for pattern, issue_type in self.flakiness_indicators:
242
- if re.search(pattern, func_body):
243
- if issue_type not in issues:
244
- issues.append(issue_type)
245
-
246
- # Check for hardcoded values (magic numbers/strings)
247
- if self._has_hardcoded_values(func_body):
248
- issues.append(TestQualityIssue.HARDCODED_VALUES)
249
-
250
- # Extract fixtures used
251
- fixtures = self._extract_fixtures(func_body)
252
-
253
- return TestFunction(
254
- name=func_name,
255
- file_path=file_path,
256
- line_number=line_num,
257
- assertions_count=assertions_count,
258
- is_async=is_async,
259
- uses_fixtures=fixtures,
260
- issues=issues,
261
- )
262
-
263
- def _count_assertions(self, func_body: str) -> int:
264
- """Count number of assertions in function"""
265
- count = 0
266
- for pattern in self.assertion_patterns:
267
- matches = re.findall(pattern, func_body)
268
- count += len(matches)
269
- return count
270
-
271
- def _has_only_weak_assertions(self, func_body: str) -> bool:
272
- """Check if function only uses weak assertions (assertTrue/False)"""
273
- weak_assertions = re.findall(r"assert(True|False)\(", func_body)
274
- all_assertions = sum(
275
- len(re.findall(pattern, func_body)) for pattern in self.assertion_patterns
276
- )
277
-
278
- # If all assertions are weak and there are some
279
- return len(weak_assertions) == all_assertions and all_assertions > 0
280
-
281
- def _has_hardcoded_values(self, func_body: str) -> bool:
282
- """Detect hardcoded magic values"""
283
- # Look for literal numbers (except common ones like 0, 1, 2)
284
- magic_numbers = re.findall(r"\b[3-9]\d*\b", func_body)
285
-
286
- # Look for hardcoded strings that aren't test names or common words
287
- magic_strings = re.findall(r'["\']([a-zA-Z0-9]{10,})["\']', func_body)
288
-
289
- # Threshold: more than 3 magic values is suspicious
290
- return len(magic_numbers) + len(magic_strings) > 3
291
-
292
- def _extract_fixtures(self, func_body: str) -> list[str]:
293
- """Extract pytest fixtures used in function signature"""
294
- # Match function signature parameters
295
- sig_match = re.search(r"def\s+\w+\s*\(([^)]*)\)", func_body)
296
- if not sig_match:
297
- return []
298
-
299
- params = sig_match.group(1).split(",")
300
- fixtures = []
301
-
302
- for param in params:
303
- param = param.strip()
304
- # Skip 'self' and empty params
305
- if param and param != "self":
306
- # Extract parameter name (before type hint if any)
307
- fixture_name = param.split(":")[0].strip()
308
- fixtures.append(fixture_name)
309
-
310
- return fixtures
311
-
312
- def analyze_test_execution(self, test_results: list[dict[str, Any]]) -> list[TestFunction]:
313
- """Analyze test execution results (from pytest JSON report)
314
-
315
- Args:
316
- test_results: List of test result dicts with fields:
317
- - nodeid: test identifier
318
- - duration: execution time in seconds
319
- - outcome: passed/failed/skipped
320
- - call: execution details
321
-
322
- Returns:
323
- List of TestFunction objects with execution data
324
-
325
- """
326
- test_functions = []
327
-
328
- for result in test_results:
329
- # Parse nodeid (e.g., "tests/test_core.py::test_function_name")
330
- nodeid = str(result.get("nodeid", ""))
331
- parts = nodeid.split("::")
332
-
333
- if len(parts) < 2:
334
- continue
335
-
336
- file_path = parts[0]
337
- func_name = parts[1]
338
-
339
- duration = result.get("duration", 0.0)
340
-
341
- # Create basic TestFunction (would be enriched with code analysis)
342
- test_func = TestFunction(
343
- name=func_name,
344
- file_path=file_path,
345
- line_number=0, # Would need code analysis to get this
346
- assertions_count=0, # Would need code analysis
347
- execution_time=duration,
348
- )
349
-
350
- # Check if slow
351
- if duration > self.slow_threshold:
352
- test_func.issues.append(TestQualityIssue.SLOW)
353
-
354
- # Detect flakiness from multiple runs
355
- # (This would require historical data)
356
-
357
- test_functions.append(test_func)
358
-
359
- return test_functions
360
-
361
- def detect_flaky_tests(self, historical_results: list[list[dict[str, Any]]]) -> list[str]:
362
- """Detect flaky tests from historical test runs
363
-
364
- A test is considered flaky if it has inconsistent results across runs
365
- with the same code.
366
-
367
- Args:
368
- historical_results: List of test result lists from multiple runs
369
-
370
- Returns:
371
- List of test names that are flaky
372
-
373
- """
374
- if len(historical_results) < 2:
375
- return []
376
-
377
- # Track outcomes for each test
378
- test_outcomes: dict[str, list[str]] = {}
379
-
380
- for run_results in historical_results:
381
- for result in run_results:
382
- nodeid = result.get("nodeid", "")
383
- outcome = result.get("outcome", "unknown")
384
-
385
- if nodeid not in test_outcomes:
386
- test_outcomes[nodeid] = []
387
-
388
- test_outcomes[nodeid].append(outcome)
389
-
390
- # Find tests with inconsistent outcomes
391
- flaky_tests = []
392
-
393
- for nodeid, outcomes in test_outcomes.items():
394
- # If outcomes vary, test is flaky
395
- unique_outcomes = set(outcomes)
396
- if len(unique_outcomes) > 1:
397
- # Exclude if only failed once (might be legitimate)
398
- fail_count = outcomes.count("failed")
399
- if fail_count > 1 or (fail_count == 1 and len(outcomes) > 2):
400
- flaky_tests.append(nodeid)
401
-
402
- return flaky_tests
403
-
404
- def generate_quality_report(self, test_functions: list[TestFunction]) -> TestQualityReport:
405
- """Generate comprehensive quality report
406
-
407
- Args:
408
- test_functions: List of analyzed test functions
409
-
410
- Returns:
411
- TestQualityReport with statistics and categorization
412
-
413
- """
414
- high_quality = []
415
- medium_quality = []
416
- low_quality = []
417
- flaky_tests = []
418
- slow_tests = []
419
- no_assertions = []
420
- isolated_count = 0
421
-
422
- issues_by_type: dict[TestQualityIssue, int] = dict.fromkeys(TestQualityIssue, 0)
423
-
424
- test_functions_dict = {}
425
-
426
- for test_func in test_functions:
427
- quality_score = test_func.quality_score
428
- test_id = f"{test_func.file_path}::{test_func.name}"
429
- test_functions_dict[test_id] = test_func
430
-
431
- # Categorize by quality
432
- if quality_score >= 80:
433
- high_quality.append(test_id)
434
- elif quality_score >= 50:
435
- medium_quality.append(test_id)
436
- else:
437
- low_quality.append(test_id)
438
-
439
- # Track specific issues
440
- if TestQualityIssue.NO_ASSERTIONS in test_func.issues:
441
- no_assertions.append(test_id)
442
-
443
- if TestQualityIssue.SLOW in test_func.issues:
444
- slow_tests.append(test_id)
445
-
446
- if any(
447
- issue in test_func.issues
448
- for issue in [
449
- TestQualityIssue.FLAKY,
450
- TestQualityIssue.SLEEP_USAGE,
451
- TestQualityIssue.RANDOM_USAGE,
452
- TestQualityIssue.NOT_ISOLATED,
453
- ]
454
- ):
455
- flaky_tests.append(test_id)
456
-
457
- # Check isolation (no external dependencies)
458
- if TestQualityIssue.NOT_ISOLATED not in test_func.issues:
459
- isolated_count += 1
460
-
461
- # Count issues by type
462
- for issue in test_func.issues:
463
- issues_by_type[issue] += 1
464
-
465
- # Calculate average quality score
466
- if test_functions:
467
- avg_score = sum(tf.quality_score for tf in test_functions) / len(test_functions)
468
- else:
469
- avg_score = 0.0
470
-
471
- return TestQualityReport(
472
- total_tests=len(test_functions),
473
- high_quality_tests=len(high_quality),
474
- medium_quality_tests=len(medium_quality),
475
- low_quality_tests=len(low_quality),
476
- flaky_tests=flaky_tests,
477
- slow_tests=slow_tests,
478
- tests_without_assertions=no_assertions,
479
- isolated_tests=isolated_count,
480
- average_quality_score=avg_score,
481
- issues_by_type=issues_by_type,
482
- test_functions=test_functions_dict,
483
- )
484
-
485
- def generate_summary(self, report: TestQualityReport) -> str:
486
- """Generate human-readable quality summary"""
487
- summary = []
488
- summary.append("=" * 60)
489
- summary.append("TEST QUALITY ANALYSIS SUMMARY")
490
- summary.append("=" * 60)
491
- summary.append(f"Total Tests: {report.total_tests}")
492
- summary.append(f"Average Quality Score: {report.average_quality_score:.1f}/100")
493
- summary.append("")
494
- summary.append("Quality Distribution:")
495
- summary.append(f" ✅ High (≥80): {report.high_quality_tests} tests")
496
- summary.append(f" ⚠️ Medium (50-79): {report.medium_quality_tests} tests")
497
- summary.append(f" ❌ Low (<50): {report.low_quality_tests} tests")
498
- summary.append("")
499
-
500
- if report.tests_without_assertions:
501
- summary.append(f"⚠️ Tests Without Assertions: {len(report.tests_without_assertions)}")
502
- for test_id in report.tests_without_assertions[:3]:
503
- summary.append(f" - {test_id}")
504
- if len(report.tests_without_assertions) > 3:
505
- summary.append(f" ... and {len(report.tests_without_assertions) - 3} more")
506
- summary.append("")
507
-
508
- if report.flaky_tests:
509
- summary.append(f"🔴 Potentially Flaky Tests: {len(report.flaky_tests)}")
510
- for test_id in report.flaky_tests[:3]:
511
- summary.append(f" - {test_id}")
512
- if len(report.flaky_tests) > 3:
513
- summary.append(f" ... and {len(report.flaky_tests) - 3} more")
514
- summary.append("")
515
-
516
- if report.slow_tests:
517
- summary.append(f"🐌 Slow Tests (>{self.slow_threshold}s): {len(report.slow_tests)}")
518
- for test_id in report.slow_tests[:3]:
519
- summary.append(f" - {test_id}")
520
- if len(report.slow_tests) > 3:
521
- summary.append(f" ... and {len(report.slow_tests) - 3} more")
522
-
523
- summary.append("=" * 60)
524
-
525
- return "\n".join(summary)