empathy-framework 4.6.6__py3-none-any.whl → 4.7.1__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 (273) hide show
  1. empathy_framework-4.7.1.dist-info/METADATA +690 -0
  2. empathy_framework-4.7.1.dist-info/RECORD +379 -0
  3. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/top_level.txt +1 -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 2.py +173 -0
  130. empathy_os/vscode_bridge.py +173 -0
  131. empathy_os/workflows/__init__.py +212 -120
  132. empathy_os/workflows/batch_processing.py +8 -24
  133. empathy_os/workflows/bug_predict.py +1 -1
  134. empathy_os/workflows/code_review.py +20 -5
  135. empathy_os/workflows/code_review_pipeline.py +13 -8
  136. empathy_os/workflows/keyboard_shortcuts/workflow.py +6 -2
  137. empathy_os/workflows/manage_documentation.py +1 -0
  138. empathy_os/workflows/orchestrated_health_check.py +6 -11
  139. empathy_os/workflows/orchestrated_release_prep.py +3 -3
  140. empathy_os/workflows/pr_review.py +18 -10
  141. empathy_os/workflows/progressive/README 2.md +454 -0
  142. empathy_os/workflows/progressive/__init__ 2.py +92 -0
  143. empathy_os/workflows/progressive/__init__.py +2 -12
  144. empathy_os/workflows/progressive/cli 2.py +242 -0
  145. empathy_os/workflows/progressive/cli.py +14 -37
  146. empathy_os/workflows/progressive/core 2.py +488 -0
  147. empathy_os/workflows/progressive/core.py +12 -12
  148. empathy_os/workflows/progressive/orchestrator 2.py +701 -0
  149. empathy_os/workflows/progressive/orchestrator.py +166 -144
  150. empathy_os/workflows/progressive/reports 2.py +528 -0
  151. empathy_os/workflows/progressive/reports.py +22 -31
  152. empathy_os/workflows/progressive/telemetry 2.py +280 -0
  153. empathy_os/workflows/progressive/telemetry.py +8 -14
  154. empathy_os/workflows/progressive/test_gen 2.py +514 -0
  155. empathy_os/workflows/progressive/test_gen.py +29 -48
  156. empathy_os/workflows/progressive/workflow 2.py +628 -0
  157. empathy_os/workflows/progressive/workflow.py +31 -70
  158. empathy_os/workflows/release_prep.py +21 -6
  159. empathy_os/workflows/release_prep_crew.py +1 -0
  160. empathy_os/workflows/secure_release.py +13 -6
  161. empathy_os/workflows/security_audit.py +8 -3
  162. empathy_os/workflows/test_coverage_boost_crew.py +3 -2
  163. empathy_os/workflows/test_maintenance_crew.py +1 -0
  164. empathy_os/workflows/test_runner.py +16 -12
  165. empathy_software_plugin/SOFTWARE_PLUGIN_README.md +25 -703
  166. empathy_software_plugin/cli.py +0 -122
  167. patterns/README.md +119 -0
  168. patterns/__init__.py +95 -0
  169. patterns/behavior.py +298 -0
  170. patterns/code_review_memory.json +441 -0
  171. patterns/core.py +97 -0
  172. patterns/debugging.json +3763 -0
  173. patterns/empathy.py +268 -0
  174. patterns/health_check_memory.json +505 -0
  175. patterns/input.py +161 -0
  176. patterns/memory_graph.json +8 -0
  177. patterns/refactoring_memory.json +1113 -0
  178. patterns/registry.py +663 -0
  179. patterns/security_memory.json +8 -0
  180. patterns/structural.py +415 -0
  181. patterns/validation.py +194 -0
  182. coach_wizards/__init__.py +0 -45
  183. coach_wizards/accessibility_wizard.py +0 -91
  184. coach_wizards/api_wizard.py +0 -91
  185. coach_wizards/base_wizard.py +0 -209
  186. coach_wizards/cicd_wizard.py +0 -91
  187. coach_wizards/code_reviewer_README.md +0 -60
  188. coach_wizards/code_reviewer_wizard.py +0 -180
  189. coach_wizards/compliance_wizard.py +0 -91
  190. coach_wizards/database_wizard.py +0 -91
  191. coach_wizards/debugging_wizard.py +0 -91
  192. coach_wizards/documentation_wizard.py +0 -91
  193. coach_wizards/generate_wizards.py +0 -347
  194. coach_wizards/localization_wizard.py +0 -173
  195. coach_wizards/migration_wizard.py +0 -91
  196. coach_wizards/monitoring_wizard.py +0 -91
  197. coach_wizards/observability_wizard.py +0 -91
  198. coach_wizards/performance_wizard.py +0 -91
  199. coach_wizards/prompt_engineering_wizard.py +0 -661
  200. coach_wizards/refactoring_wizard.py +0 -91
  201. coach_wizards/scaling_wizard.py +0 -90
  202. coach_wizards/security_wizard.py +0 -92
  203. coach_wizards/testing_wizard.py +0 -91
  204. empathy_framework-4.6.6.dist-info/METADATA +0 -1597
  205. empathy_framework-4.6.6.dist-info/RECORD +0 -410
  206. empathy_llm_toolkit/wizards/__init__.py +0 -43
  207. empathy_llm_toolkit/wizards/base_wizard.py +0 -364
  208. empathy_llm_toolkit/wizards/customer_support_wizard.py +0 -190
  209. empathy_llm_toolkit/wizards/healthcare_wizard.py +0 -378
  210. empathy_llm_toolkit/wizards/patient_assessment_README.md +0 -64
  211. empathy_llm_toolkit/wizards/patient_assessment_wizard.py +0 -193
  212. empathy_llm_toolkit/wizards/technology_wizard.py +0 -209
  213. empathy_os/wizard_factory_cli.py +0 -170
  214. empathy_software_plugin/wizards/__init__.py +0 -42
  215. empathy_software_plugin/wizards/advanced_debugging_wizard.py +0 -395
  216. empathy_software_plugin/wizards/agent_orchestration_wizard.py +0 -511
  217. empathy_software_plugin/wizards/ai_collaboration_wizard.py +0 -503
  218. empathy_software_plugin/wizards/ai_context_wizard.py +0 -441
  219. empathy_software_plugin/wizards/ai_documentation_wizard.py +0 -503
  220. empathy_software_plugin/wizards/base_wizard.py +0 -288
  221. empathy_software_plugin/wizards/book_chapter_wizard.py +0 -519
  222. empathy_software_plugin/wizards/code_review_wizard.py +0 -604
  223. empathy_software_plugin/wizards/debugging/__init__.py +0 -50
  224. empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +0 -414
  225. empathy_software_plugin/wizards/debugging/config_loaders.py +0 -446
  226. empathy_software_plugin/wizards/debugging/fix_applier.py +0 -469
  227. empathy_software_plugin/wizards/debugging/language_patterns.py +0 -385
  228. empathy_software_plugin/wizards/debugging/linter_parsers.py +0 -470
  229. empathy_software_plugin/wizards/debugging/verification.py +0 -369
  230. empathy_software_plugin/wizards/enhanced_testing_wizard.py +0 -537
  231. empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +0 -816
  232. empathy_software_plugin/wizards/multi_model_wizard.py +0 -501
  233. empathy_software_plugin/wizards/pattern_extraction_wizard.py +0 -422
  234. empathy_software_plugin/wizards/pattern_retriever_wizard.py +0 -400
  235. empathy_software_plugin/wizards/performance/__init__.py +0 -9
  236. empathy_software_plugin/wizards/performance/bottleneck_detector.py +0 -221
  237. empathy_software_plugin/wizards/performance/profiler_parsers.py +0 -278
  238. empathy_software_plugin/wizards/performance/trajectory_analyzer.py +0 -429
  239. empathy_software_plugin/wizards/performance_profiling_wizard.py +0 -305
  240. empathy_software_plugin/wizards/prompt_engineering_wizard.py +0 -425
  241. empathy_software_plugin/wizards/rag_pattern_wizard.py +0 -461
  242. empathy_software_plugin/wizards/security/__init__.py +0 -32
  243. empathy_software_plugin/wizards/security/exploit_analyzer.py +0 -290
  244. empathy_software_plugin/wizards/security/owasp_patterns.py +0 -241
  245. empathy_software_plugin/wizards/security/vulnerability_scanner.py +0 -604
  246. empathy_software_plugin/wizards/security_analysis_wizard.py +0 -322
  247. empathy_software_plugin/wizards/security_learning_wizard.py +0 -740
  248. empathy_software_plugin/wizards/tech_debt_wizard.py +0 -726
  249. empathy_software_plugin/wizards/testing/__init__.py +0 -27
  250. empathy_software_plugin/wizards/testing/coverage_analyzer.py +0 -459
  251. empathy_software_plugin/wizards/testing/quality_analyzer.py +0 -525
  252. empathy_software_plugin/wizards/testing/test_suggester.py +0 -533
  253. empathy_software_plugin/wizards/testing_wizard.py +0 -274
  254. wizards/__init__.py +0 -82
  255. wizards/admission_assessment_wizard.py +0 -644
  256. wizards/care_plan.py +0 -321
  257. wizards/clinical_assessment.py +0 -769
  258. wizards/discharge_planning.py +0 -77
  259. wizards/discharge_summary_wizard.py +0 -468
  260. wizards/dosage_calculation.py +0 -497
  261. wizards/incident_report_wizard.py +0 -454
  262. wizards/medication_reconciliation.py +0 -85
  263. wizards/nursing_assessment.py +0 -171
  264. wizards/patient_education.py +0 -654
  265. wizards/quality_improvement.py +0 -705
  266. wizards/sbar_report.py +0 -324
  267. wizards/sbar_wizard.py +0 -608
  268. wizards/shift_handoff_wizard.py +0 -535
  269. wizards/soap_note_wizard.py +0 -679
  270. wizards/treatment_plan.py +0 -15
  271. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/WHEEL +0 -0
  272. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/entry_points.txt +0 -0
  273. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.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)