empathy-framework 2.4.0__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 (329) hide show
  1. coach_wizards/__init__.py +13 -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 +661 -0
  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.8.2.dist-info/METADATA +1176 -0
  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-2.4.0.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 +186 -28
  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 +168 -53
  64. empathy_llm_toolkit/git_pattern_extractor.py +17 -13
  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 +16 -14
  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 +20 -22
  86. empathy_llm_toolkit/state.py +28 -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 +125 -84
  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} +28 -28
  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 +1516 -70
  105. empathy_os/cli_unified.py +597 -0
  106. empathy_os/config/__init__.py +63 -0
  107. empathy_os/config/xml_config.py +239 -0
  108. empathy_os/config.py +95 -37
  109. empathy_os/coordination.py +72 -68
  110. empathy_os/core.py +94 -107
  111. empathy_os/cost_tracker.py +74 -55
  112. empathy_os/dashboard/__init__.py +15 -0
  113. empathy_os/dashboard/server.py +743 -0
  114. empathy_os/discovery.py +17 -14
  115. empathy_os/emergence.py +21 -22
  116. empathy_os/exceptions.py +18 -30
  117. empathy_os/feedback_loops.py +30 -33
  118. empathy_os/levels.py +32 -35
  119. empathy_os/leverage_points.py +31 -32
  120. empathy_os/logging_config.py +19 -16
  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 +30 -29
  160. empathy_os/persistence.py +35 -37
  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 +79 -77
  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 +19 -14
  192. empathy_os/trust/__init__.py +28 -0
  193. empathy_os/trust/circuit_breaker.py +579 -0
  194. empathy_os/trust_building.py +67 -58
  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} +131 -37
  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 +49 -27
  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-2.4.0.dist-info/METADATA +0 -485
  324. empathy_framework-2.4.0.dist-info/RECORD +0 -102
  325. empathy_framework-2.4.0.dist-info/entry_points.txt +0 -6
  326. empathy_llm_toolkit/htmlcov/status.json +0 -1
  327. empathy_llm_toolkit/security/htmlcov/status.json +0 -1
  328. {empathy_framework-2.4.0.dist-info → empathy_framework-3.8.2.dist-info}/WHEEL +0 -0
  329. {empathy_framework-2.4.0.dist-info → empathy_framework-3.8.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,626 @@
1
+ """Test Maintenance Workflow - Automatic Test Lifecycle Management
2
+
3
+ Integrates with Project Index to:
4
+ - Track files requiring tests
5
+ - Detect when tests become stale
6
+ - Generate test plans based on file events
7
+ - Execute automatic test generation
8
+ - Report on test health
9
+
10
+ Key events handled:
11
+ - File created: Check if needs tests, queue for generation
12
+ - File modified: Check if tests need updating
13
+ - File deleted: Mark associated tests as orphaned
14
+
15
+ Copyright 2025 Smart AI Memory, LLC
16
+ Licensed under Fair Source 0.9
17
+ """
18
+
19
+ import logging
20
+ from dataclasses import dataclass, field
21
+ from datetime import datetime
22
+ from enum import Enum
23
+ from pathlib import Path
24
+ from typing import Any
25
+
26
+ from ..project_index import FileRecord, ProjectIndex
27
+ from ..project_index.reports import ReportGenerator
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class TestAction(str, Enum):
33
+ """Actions that can be taken for test management."""
34
+
35
+ CREATE = "create" # Create new tests
36
+ UPDATE = "update" # Update existing tests
37
+ REVIEW = "review" # Review and possibly regenerate
38
+ DELETE = "delete" # Delete orphaned tests
39
+ SKIP = "skip" # No action needed
40
+ MANUAL = "manual" # Requires manual intervention
41
+
42
+
43
+ class TestPriority(str, Enum):
44
+ """Priority levels for test actions."""
45
+
46
+ CRITICAL = "critical" # High-impact files, blocking
47
+ HIGH = "high" # Important files
48
+ MEDIUM = "medium" # Standard priority
49
+ LOW = "low" # Nice to have
50
+ DEFERRED = "deferred" # Can wait
51
+
52
+
53
+ @dataclass
54
+ class TestPlanItem:
55
+ """A single item in a test maintenance plan."""
56
+
57
+ file_path: str
58
+ action: TestAction
59
+ priority: TestPriority
60
+ reason: str
61
+ test_file_path: str | None = None
62
+ estimated_effort: str = "unknown"
63
+ auto_executable: bool = True
64
+ metadata: dict[str, Any] = field(default_factory=dict)
65
+
66
+ def to_dict(self) -> dict[str, Any]:
67
+ return {
68
+ "file_path": self.file_path,
69
+ "action": self.action.value,
70
+ "priority": self.priority.value,
71
+ "reason": self.reason,
72
+ "test_file_path": self.test_file_path,
73
+ "estimated_effort": self.estimated_effort,
74
+ "auto_executable": self.auto_executable,
75
+ "metadata": self.metadata,
76
+ }
77
+
78
+
79
+ @dataclass
80
+ class TestMaintenancePlan:
81
+ """Complete test maintenance plan for a project."""
82
+
83
+ generated_at: datetime = field(default_factory=datetime.now)
84
+ items: list[TestPlanItem] = field(default_factory=list)
85
+ summary: dict[str, Any] = field(default_factory=dict)
86
+ options: list[dict[str, Any]] = field(default_factory=list)
87
+
88
+ def to_dict(self) -> dict[str, Any]:
89
+ return {
90
+ "generated_at": self.generated_at.isoformat(),
91
+ "items": [item.to_dict() for item in self.items],
92
+ "summary": self.summary,
93
+ "options": self.options,
94
+ }
95
+
96
+ def get_items_by_action(self, action: TestAction) -> list[TestPlanItem]:
97
+ return [item for item in self.items if item.action == action]
98
+
99
+ def get_items_by_priority(self, priority: TestPriority) -> list[TestPlanItem]:
100
+ return [item for item in self.items if item.priority == priority]
101
+
102
+ def get_auto_executable_items(self) -> list[TestPlanItem]:
103
+ return [item for item in self.items if item.auto_executable]
104
+
105
+
106
+ class TestMaintenanceWorkflow:
107
+ """Workflow for automatic test lifecycle management.
108
+
109
+ Integrates with Project Index to track and manage tests.
110
+ Can run automatically on file events or manually on demand.
111
+
112
+ Modes:
113
+ - analyze: Generate plan without executing
114
+ - execute: Execute plan items (with confirmation)
115
+ - auto: Automatically execute auto_executable items
116
+ - report: Generate detailed test health report
117
+ """
118
+
119
+ def __init__(self, project_root: str, index: ProjectIndex | None = None):
120
+ self.name = "test_maintenance"
121
+ self.description = "Automatic test lifecycle management"
122
+ self.project_root = Path(project_root)
123
+ self.index = index or ProjectIndex(str(project_root))
124
+ self._ensure_index_loaded()
125
+
126
+ def _ensure_index_loaded(self) -> None:
127
+ """Ensure index is loaded, refresh if needed."""
128
+ if not self.index.load():
129
+ logger.info("Index not found, refreshing...")
130
+ self.index.refresh()
131
+
132
+ async def run(self, context: dict[str, Any]) -> dict[str, Any]:
133
+ """Run the test maintenance workflow.
134
+
135
+ Context options:
136
+ mode: "analyze" | "execute" | "auto" | "report"
137
+ changed_files: List of files that changed (for event-driven)
138
+ max_items: Maximum items to process (default: 20)
139
+ priority_filter: Only process items of this priority or higher
140
+ dry_run: If True, don't actually execute (default: False)
141
+ """
142
+ mode = context.get("mode", "analyze")
143
+ changed_files = context.get("changed_files", [])
144
+ max_items = context.get("max_items", 20)
145
+ dry_run = context.get("dry_run", False)
146
+
147
+ # Refresh index if files changed
148
+ if changed_files:
149
+ self.index.refresh()
150
+
151
+ # Generate the plan
152
+ plan = self._generate_plan(changed_files, max_items)
153
+
154
+ result = {
155
+ "workflow": self.name,
156
+ "mode": mode,
157
+ "generated_at": datetime.now().isoformat(),
158
+ "plan": plan.to_dict(),
159
+ }
160
+
161
+ if mode == "analyze":
162
+ # Just return the plan
163
+ result["status"] = "plan_generated"
164
+ result["message"] = f"Generated plan with {len(plan.items)} items"
165
+
166
+ elif mode == "execute":
167
+ if dry_run:
168
+ result["status"] = "dry_run"
169
+ result["message"] = f"Would execute {len(plan.items)} items"
170
+ else:
171
+ execution_result = await self._execute_plan(plan, auto_only=False)
172
+ result["execution"] = execution_result
173
+ result["status"] = "executed"
174
+
175
+ elif mode == "auto":
176
+ auto_items = plan.get_auto_executable_items()
177
+ if dry_run:
178
+ result["status"] = "dry_run"
179
+ result["message"] = f"Would auto-execute {len(auto_items)} items"
180
+ else:
181
+ execution_result = await self._execute_plan(plan, auto_only=True)
182
+ result["execution"] = execution_result
183
+ result["status"] = "auto_executed"
184
+
185
+ elif mode == "report":
186
+ report = self._generate_report()
187
+ result["report"] = report
188
+ result["status"] = "report_generated"
189
+
190
+ return result
191
+
192
+ def _generate_plan(
193
+ self,
194
+ changed_files: list[str],
195
+ max_items: int,
196
+ ) -> TestMaintenancePlan:
197
+ """Generate a test maintenance plan."""
198
+ plan = TestMaintenancePlan()
199
+ items: list[TestPlanItem] = []
200
+
201
+ # If specific files changed, prioritize them
202
+ if changed_files:
203
+ for file_path in changed_files:
204
+ record = self.index.get_file(file_path)
205
+ if record:
206
+ item = self._create_plan_item_for_file(record, event="modified")
207
+ if item and item.action != TestAction.SKIP:
208
+ items.append(item)
209
+
210
+ # Add files needing tests (not in changed_files)
211
+ changed_set = set(changed_files)
212
+ for record in self.index.get_files_needing_tests():
213
+ if record.path not in changed_set:
214
+ item = self._create_plan_item_for_file(record, event="missing_tests")
215
+ if item and item.action != TestAction.SKIP:
216
+ items.append(item)
217
+
218
+ # Add stale test files
219
+ for record in self.index.get_stale_files():
220
+ if record.path not in changed_set:
221
+ item = self._create_plan_item_for_file(record, event="stale")
222
+ if item and item.action != TestAction.SKIP:
223
+ items.append(item)
224
+
225
+ # Sort by priority
226
+ priority_order = {
227
+ TestPriority.CRITICAL: 0,
228
+ TestPriority.HIGH: 1,
229
+ TestPriority.MEDIUM: 2,
230
+ TestPriority.LOW: 3,
231
+ TestPriority.DEFERRED: 4,
232
+ }
233
+
234
+ def get_sort_key(item: TestPlanItem) -> tuple[int, float]:
235
+ file_rec = self.index.get_file(item.file_path)
236
+ impact = float(-file_rec.impact_score) if file_rec else 0.0
237
+ return (priority_order[item.priority], impact)
238
+
239
+ items.sort(key=get_sort_key)
240
+
241
+ # Limit items
242
+ plan.items = items[:max_items]
243
+
244
+ # Generate summary
245
+ plan.summary = {
246
+ "total_items": len(items),
247
+ "shown_items": len(plan.items),
248
+ "by_action": {
249
+ action.value: len([i for i in items if i.action == action]) for action in TestAction
250
+ },
251
+ "by_priority": {
252
+ priority.value: len([i for i in items if i.priority == priority])
253
+ for priority in TestPriority
254
+ },
255
+ "auto_executable": len([i for i in items if i.auto_executable]),
256
+ "manual_required": len([i for i in items if not i.auto_executable]),
257
+ }
258
+
259
+ # Generate options for the user
260
+ plan.options = self._generate_options(plan)
261
+
262
+ return plan
263
+
264
+ def _create_plan_item_for_file(
265
+ self,
266
+ record: FileRecord,
267
+ event: str,
268
+ ) -> TestPlanItem | None:
269
+ """Create a plan item for a specific file."""
270
+ # Determine action based on event and file state
271
+ if event == "missing_tests":
272
+ action = TestAction.CREATE
273
+ reason = "File requires tests but none exist"
274
+ elif event == "stale":
275
+ action = TestAction.UPDATE
276
+ reason = f"Tests are {record.staleness_days} days stale"
277
+ elif event == "modified":
278
+ if record.tests_exist:
279
+ action = TestAction.REVIEW
280
+ reason = "Source file modified, tests may need update"
281
+ else:
282
+ action = TestAction.CREATE
283
+ reason = "Modified file needs tests"
284
+ elif event == "deleted":
285
+ action = TestAction.DELETE
286
+ reason = "Source file deleted, tests may be orphaned"
287
+ else:
288
+ action = TestAction.SKIP
289
+ reason = "No action needed"
290
+
291
+ # Determine priority based on impact score
292
+ if record.impact_score >= 10.0:
293
+ priority = TestPriority.CRITICAL
294
+ elif record.impact_score >= 5.0:
295
+ priority = TestPriority.HIGH
296
+ elif record.impact_score >= 2.0:
297
+ priority = TestPriority.MEDIUM
298
+ else:
299
+ priority = TestPriority.LOW
300
+
301
+ # Estimate effort
302
+ if record.lines_of_code < 50:
303
+ effort = "small (< 1 hour)"
304
+ elif record.lines_of_code < 200:
305
+ effort = "medium (1-2 hours)"
306
+ else:
307
+ effort = "large (2+ hours)"
308
+
309
+ # Determine if auto-executable
310
+ auto_executable = (
311
+ action in [TestAction.CREATE, TestAction.UPDATE]
312
+ and record.language == "python"
313
+ and record.lines_of_code < 500
314
+ )
315
+
316
+ return TestPlanItem(
317
+ file_path=record.path,
318
+ action=action,
319
+ priority=priority,
320
+ reason=reason,
321
+ test_file_path=record.test_file_path,
322
+ estimated_effort=effort,
323
+ auto_executable=auto_executable,
324
+ metadata={
325
+ "lines_of_code": record.lines_of_code,
326
+ "impact_score": record.impact_score,
327
+ "language": record.language,
328
+ "complexity": record.complexity_score,
329
+ },
330
+ )
331
+
332
+ def _generate_options(self, plan: TestMaintenancePlan) -> list[dict[str, Any]]:
333
+ """Generate execution options for the user."""
334
+ options = []
335
+
336
+ # Option 1: Execute all auto-executable
337
+ auto_count = len(plan.get_auto_executable_items())
338
+ if auto_count > 0:
339
+ options.append(
340
+ {
341
+ "id": "auto_all",
342
+ "name": "Auto-execute all",
343
+ "description": f"Automatically generate/update tests for {auto_count} files",
344
+ "item_count": auto_count,
345
+ "estimated_time": f"{auto_count * 5}-{auto_count * 15} minutes",
346
+ "command": "python -m empathy_os.workflows.test_maintenance auto",
347
+ },
348
+ )
349
+
350
+ # Option 2: Critical only
351
+ critical_count = len(plan.get_items_by_priority(TestPriority.CRITICAL))
352
+ if critical_count > 0:
353
+ options.append(
354
+ {
355
+ "id": "critical_only",
356
+ "name": "Critical files only",
357
+ "description": f"Focus on {critical_count} critical high-impact files",
358
+ "item_count": critical_count,
359
+ "estimated_time": f"{critical_count * 10}-{critical_count * 20} minutes",
360
+ "command": "python -m empathy_os.workflows.test_maintenance execute --priority critical",
361
+ },
362
+ )
363
+
364
+ # Option 3: Create new tests only
365
+ create_count = len(plan.get_items_by_action(TestAction.CREATE))
366
+ if create_count > 0:
367
+ options.append(
368
+ {
369
+ "id": "create_only",
370
+ "name": "Create new tests only",
371
+ "description": f"Generate tests for {create_count} files without tests",
372
+ "item_count": create_count,
373
+ "estimated_time": f"{create_count * 10}-{create_count * 20} minutes",
374
+ "command": "python -m empathy_os.workflows.test_maintenance execute --action create",
375
+ },
376
+ )
377
+
378
+ # Option 4: Update stale tests only
379
+ update_count = len(plan.get_items_by_action(TestAction.UPDATE))
380
+ if update_count > 0:
381
+ options.append(
382
+ {
383
+ "id": "update_stale",
384
+ "name": "Update stale tests",
385
+ "description": f"Update {update_count} stale test files",
386
+ "item_count": update_count,
387
+ "estimated_time": f"{update_count * 5}-{update_count * 10} minutes",
388
+ "command": "python -m empathy_os.workflows.test_maintenance execute --action update",
389
+ },
390
+ )
391
+
392
+ # Option 5: Manual review
393
+ options.append(
394
+ {
395
+ "id": "manual_review",
396
+ "name": "Manual review",
397
+ "description": "Review the plan and select specific items",
398
+ "item_count": len(plan.items),
399
+ "command": "python -m empathy_os.workflows.test_maintenance analyze --json",
400
+ },
401
+ )
402
+
403
+ return options
404
+
405
+ async def _execute_plan(
406
+ self,
407
+ plan: TestMaintenancePlan,
408
+ auto_only: bool = False,
409
+ ) -> dict[str, Any]:
410
+ """Execute items in the plan."""
411
+ items_to_execute = plan.get_auto_executable_items() if auto_only else plan.items
412
+
413
+ # Use typed variables for proper type inference
414
+ succeeded = 0
415
+ failed = 0
416
+ skipped = 0
417
+ details: list[dict[str, Any]] = []
418
+
419
+ for item in items_to_execute:
420
+ try:
421
+ if item.action == TestAction.CREATE:
422
+ success = await self._create_tests_for_file(item)
423
+ elif item.action == TestAction.UPDATE:
424
+ success = await self._update_tests_for_file(item)
425
+ elif item.action == TestAction.REVIEW:
426
+ success = await self._review_tests_for_file(item)
427
+ elif item.action == TestAction.DELETE:
428
+ success = await self._delete_orphaned_tests(item)
429
+ else:
430
+ success = False
431
+ skipped += 1
432
+ continue
433
+
434
+ if success:
435
+ succeeded += 1
436
+ # Update index
437
+ self.index.update_file(
438
+ item.file_path,
439
+ tests_exist=True,
440
+ tests_last_modified=datetime.now(),
441
+ is_stale=False,
442
+ staleness_days=0,
443
+ )
444
+ else:
445
+ failed += 1
446
+
447
+ details.append(
448
+ {
449
+ "file": item.file_path,
450
+ "action": item.action.value,
451
+ "success": success,
452
+ },
453
+ )
454
+
455
+ except Exception as e:
456
+ logger.error(f"Error processing {item.file_path}: {e}")
457
+ failed += 1
458
+ details.append(
459
+ {
460
+ "file": item.file_path,
461
+ "action": item.action.value,
462
+ "success": False,
463
+ "error": str(e),
464
+ },
465
+ )
466
+
467
+ return {
468
+ "total": len(items_to_execute),
469
+ "succeeded": succeeded,
470
+ "failed": failed,
471
+ "skipped": skipped,
472
+ "details": details,
473
+ }
474
+
475
+ async def _create_tests_for_file(self, item: TestPlanItem) -> bool:
476
+ """Create tests for a file using test-gen workflow."""
477
+ # This would integrate with the test-gen workflow
478
+ # For now, return True as placeholder
479
+ logger.info(f"Would create tests for: {item.file_path}")
480
+ return True
481
+
482
+ async def _update_tests_for_file(self, item: TestPlanItem) -> bool:
483
+ """Update existing tests for a file."""
484
+ logger.info(f"Would update tests for: {item.file_path}")
485
+ return True
486
+
487
+ async def _review_tests_for_file(self, item: TestPlanItem) -> bool:
488
+ """Review and possibly regenerate tests."""
489
+ logger.info(f"Would review tests for: {item.file_path}")
490
+ return True
491
+
492
+ async def _delete_orphaned_tests(self, item: TestPlanItem) -> bool:
493
+ """Delete orphaned test files."""
494
+ logger.info(f"Would delete orphaned tests for: {item.file_path}")
495
+ return True
496
+
497
+ def _generate_report(self) -> dict[str, Any]:
498
+ """Generate detailed test health report."""
499
+ generator = ReportGenerator(
500
+ self.index.get_summary(),
501
+ self.index.get_all_files(),
502
+ )
503
+
504
+ return {
505
+ "health": generator.health_report(),
506
+ "test_gap": generator.test_gap_report(),
507
+ "staleness": generator.staleness_report(),
508
+ "coverage": generator.coverage_report(),
509
+ }
510
+
511
+ # ===== Event Handlers =====
512
+
513
+ async def on_file_created(self, file_path: str) -> dict[str, Any]:
514
+ """Handle file creation event."""
515
+ self.index.refresh()
516
+ record = self.index.get_file(file_path)
517
+
518
+ if not record:
519
+ return {"status": "not_indexed", "file": file_path}
520
+
521
+ if record.test_requirement.value == "required":
522
+ item = self._create_plan_item_for_file(record, event="missing_tests")
523
+ return {
524
+ "status": "needs_tests",
525
+ "file": file_path,
526
+ "plan_item": item.to_dict() if item else None,
527
+ "message": f"New file {file_path} requires tests",
528
+ }
529
+
530
+ return {"status": "no_tests_required", "file": file_path}
531
+
532
+ async def on_file_modified(self, file_path: str) -> dict[str, Any]:
533
+ """Handle file modification event."""
534
+ record = self.index.get_file(file_path)
535
+
536
+ if not record:
537
+ self.index.refresh()
538
+ record = self.index.get_file(file_path)
539
+
540
+ if not record:
541
+ return {"status": "not_indexed", "file": file_path}
542
+
543
+ # Mark as potentially stale
544
+ if record.tests_exist and record.test_file_path:
545
+ self.index.update_file(
546
+ file_path,
547
+ last_modified=datetime.now(),
548
+ is_stale=True,
549
+ )
550
+
551
+ item = self._create_plan_item_for_file(record, event="modified")
552
+ return {
553
+ "status": "tests_may_need_update",
554
+ "file": file_path,
555
+ "test_file": record.test_file_path,
556
+ "plan_item": item.to_dict() if item else None,
557
+ }
558
+
559
+ if record.test_requirement.value == "required":
560
+ item = self._create_plan_item_for_file(record, event="modified")
561
+ return {
562
+ "status": "needs_tests",
563
+ "file": file_path,
564
+ "plan_item": item.to_dict() if item else None,
565
+ }
566
+
567
+ return {"status": "no_action_needed", "file": file_path}
568
+
569
+ async def on_file_deleted(self, file_path: str) -> dict[str, Any]:
570
+ """Handle file deletion event."""
571
+ record = self.index.get_file(file_path)
572
+
573
+ if record and record.test_file_path:
574
+ test_path = self.project_root / record.test_file_path
575
+ if test_path.exists():
576
+ return {
577
+ "status": "orphaned_tests",
578
+ "file": file_path,
579
+ "test_file": record.test_file_path,
580
+ "message": f"Tests at {record.test_file_path} may be orphaned",
581
+ "action": "review_for_deletion",
582
+ }
583
+
584
+ # Refresh index to remove deleted file
585
+ self.index.refresh()
586
+
587
+ return {"status": "file_removed", "file": file_path}
588
+
589
+ # ===== Convenience Methods =====
590
+
591
+ def get_files_needing_tests(self, limit: int = 20) -> list[dict[str, Any]]:
592
+ """Get files that need tests, prioritized by impact."""
593
+ files = self.index.get_files_needing_tests()
594
+ return [
595
+ {
596
+ "path": f.path,
597
+ "impact_score": f.impact_score,
598
+ "lines_of_code": f.lines_of_code,
599
+ "language": f.language,
600
+ }
601
+ for f in sorted(files, key=lambda x: -x.impact_score)[:limit]
602
+ ]
603
+
604
+ def get_stale_tests(self, limit: int = 20) -> list[dict[str, Any]]:
605
+ """Get files with stale tests."""
606
+ files = self.index.get_stale_files()
607
+ return [
608
+ {
609
+ "path": f.path,
610
+ "test_file": f.test_file_path,
611
+ "staleness_days": f.staleness_days,
612
+ }
613
+ for f in sorted(files, key=lambda x: -x.staleness_days)[:limit]
614
+ ]
615
+
616
+ def get_test_health_summary(self) -> dict[str, Any]:
617
+ """Get quick test health summary."""
618
+ summary = self.index.get_summary()
619
+ return {
620
+ "files_requiring_tests": summary.files_requiring_tests,
621
+ "files_with_tests": summary.files_with_tests,
622
+ "files_without_tests": summary.files_without_tests,
623
+ "coverage_avg": summary.test_coverage_avg,
624
+ "stale_count": summary.stale_file_count,
625
+ "test_to_code_ratio": summary.test_to_code_ratio,
626
+ }