empathy-framework 3.2.3__py3-none-any.whl → 3.8.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. coach_wizards/__init__.py +11 -12
  2. coach_wizards/accessibility_wizard.py +12 -12
  3. coach_wizards/api_wizard.py +12 -12
  4. coach_wizards/base_wizard.py +26 -20
  5. coach_wizards/cicd_wizard.py +15 -13
  6. coach_wizards/code_reviewer_README.md +60 -0
  7. coach_wizards/code_reviewer_wizard.py +180 -0
  8. coach_wizards/compliance_wizard.py +12 -12
  9. coach_wizards/database_wizard.py +12 -12
  10. coach_wizards/debugging_wizard.py +12 -12
  11. coach_wizards/documentation_wizard.py +12 -12
  12. coach_wizards/generate_wizards.py +1 -2
  13. coach_wizards/localization_wizard.py +101 -19
  14. coach_wizards/migration_wizard.py +12 -12
  15. coach_wizards/monitoring_wizard.py +12 -12
  16. coach_wizards/observability_wizard.py +12 -12
  17. coach_wizards/performance_wizard.py +12 -12
  18. coach_wizards/prompt_engineering_wizard.py +22 -25
  19. coach_wizards/refactoring_wizard.py +12 -12
  20. coach_wizards/scaling_wizard.py +12 -12
  21. coach_wizards/security_wizard.py +12 -12
  22. coach_wizards/testing_wizard.py +12 -12
  23. {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/METADATA +513 -58
  24. empathy_framework-3.8.2.dist-info/RECORD +333 -0
  25. empathy_framework-3.8.2.dist-info/entry_points.txt +22 -0
  26. {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/top_level.txt +5 -1
  27. empathy_healthcare_plugin/__init__.py +1 -2
  28. empathy_healthcare_plugin/monitors/__init__.py +9 -0
  29. empathy_healthcare_plugin/monitors/clinical_protocol_monitor.py +315 -0
  30. empathy_healthcare_plugin/monitors/monitoring/__init__.py +44 -0
  31. empathy_healthcare_plugin/monitors/monitoring/protocol_checker.py +300 -0
  32. empathy_healthcare_plugin/monitors/monitoring/protocol_loader.py +214 -0
  33. empathy_healthcare_plugin/monitors/monitoring/sensor_parsers.py +306 -0
  34. empathy_healthcare_plugin/monitors/monitoring/trajectory_analyzer.py +389 -0
  35. empathy_llm_toolkit/__init__.py +7 -7
  36. empathy_llm_toolkit/agent_factory/__init__.py +53 -0
  37. empathy_llm_toolkit/agent_factory/adapters/__init__.py +85 -0
  38. empathy_llm_toolkit/agent_factory/adapters/autogen_adapter.py +312 -0
  39. empathy_llm_toolkit/agent_factory/adapters/crewai_adapter.py +454 -0
  40. empathy_llm_toolkit/agent_factory/adapters/haystack_adapter.py +298 -0
  41. empathy_llm_toolkit/agent_factory/adapters/langchain_adapter.py +362 -0
  42. empathy_llm_toolkit/agent_factory/adapters/langgraph_adapter.py +333 -0
  43. empathy_llm_toolkit/agent_factory/adapters/native.py +228 -0
  44. empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +426 -0
  45. empathy_llm_toolkit/agent_factory/base.py +305 -0
  46. empathy_llm_toolkit/agent_factory/crews/__init__.py +67 -0
  47. empathy_llm_toolkit/agent_factory/crews/code_review.py +1113 -0
  48. empathy_llm_toolkit/agent_factory/crews/health_check.py +1246 -0
  49. empathy_llm_toolkit/agent_factory/crews/refactoring.py +1128 -0
  50. empathy_llm_toolkit/agent_factory/crews/security_audit.py +1018 -0
  51. empathy_llm_toolkit/agent_factory/decorators.py +286 -0
  52. empathy_llm_toolkit/agent_factory/factory.py +558 -0
  53. empathy_llm_toolkit/agent_factory/framework.py +192 -0
  54. empathy_llm_toolkit/agent_factory/memory_integration.py +324 -0
  55. empathy_llm_toolkit/agent_factory/resilient.py +320 -0
  56. empathy_llm_toolkit/claude_memory.py +14 -15
  57. empathy_llm_toolkit/cli/__init__.py +8 -0
  58. empathy_llm_toolkit/cli/sync_claude.py +487 -0
  59. empathy_llm_toolkit/code_health.py +177 -22
  60. empathy_llm_toolkit/config/__init__.py +29 -0
  61. empathy_llm_toolkit/config/unified.py +295 -0
  62. empathy_llm_toolkit/contextual_patterns.py +11 -12
  63. empathy_llm_toolkit/core.py +51 -49
  64. empathy_llm_toolkit/git_pattern_extractor.py +16 -12
  65. empathy_llm_toolkit/levels.py +6 -13
  66. empathy_llm_toolkit/pattern_confidence.py +14 -18
  67. empathy_llm_toolkit/pattern_resolver.py +10 -12
  68. empathy_llm_toolkit/pattern_summary.py +13 -11
  69. empathy_llm_toolkit/providers.py +194 -28
  70. empathy_llm_toolkit/routing/__init__.py +32 -0
  71. empathy_llm_toolkit/routing/model_router.py +362 -0
  72. empathy_llm_toolkit/security/IMPLEMENTATION_SUMMARY.md +413 -0
  73. empathy_llm_toolkit/security/PHASE2_COMPLETE.md +384 -0
  74. empathy_llm_toolkit/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
  75. empathy_llm_toolkit/security/QUICK_REFERENCE.md +316 -0
  76. empathy_llm_toolkit/security/README.md +262 -0
  77. empathy_llm_toolkit/security/__init__.py +62 -0
  78. empathy_llm_toolkit/security/audit_logger.py +929 -0
  79. empathy_llm_toolkit/security/audit_logger_example.py +152 -0
  80. empathy_llm_toolkit/security/pii_scrubber.py +640 -0
  81. empathy_llm_toolkit/security/secrets_detector.py +678 -0
  82. empathy_llm_toolkit/security/secrets_detector_example.py +304 -0
  83. empathy_llm_toolkit/security/secure_memdocs.py +1192 -0
  84. empathy_llm_toolkit/security/secure_memdocs_example.py +278 -0
  85. empathy_llm_toolkit/session_status.py +18 -20
  86. empathy_llm_toolkit/state.py +20 -21
  87. empathy_llm_toolkit/wizards/__init__.py +38 -0
  88. empathy_llm_toolkit/wizards/base_wizard.py +364 -0
  89. empathy_llm_toolkit/wizards/customer_support_wizard.py +190 -0
  90. empathy_llm_toolkit/wizards/healthcare_wizard.py +362 -0
  91. empathy_llm_toolkit/wizards/patient_assessment_README.md +64 -0
  92. empathy_llm_toolkit/wizards/patient_assessment_wizard.py +193 -0
  93. empathy_llm_toolkit/wizards/technology_wizard.py +194 -0
  94. empathy_os/__init__.py +76 -77
  95. empathy_os/adaptive/__init__.py +13 -0
  96. empathy_os/adaptive/task_complexity.py +127 -0
  97. empathy_os/{monitoring.py → agent_monitoring.py} +27 -27
  98. empathy_os/cache/__init__.py +117 -0
  99. empathy_os/cache/base.py +166 -0
  100. empathy_os/cache/dependency_manager.py +253 -0
  101. empathy_os/cache/hash_only.py +248 -0
  102. empathy_os/cache/hybrid.py +390 -0
  103. empathy_os/cache/storage.py +282 -0
  104. empathy_os/cli.py +515 -109
  105. empathy_os/cli_unified.py +189 -42
  106. empathy_os/config/__init__.py +63 -0
  107. empathy_os/config/xml_config.py +239 -0
  108. empathy_os/config.py +87 -36
  109. empathy_os/coordination.py +48 -54
  110. empathy_os/core.py +90 -99
  111. empathy_os/cost_tracker.py +20 -23
  112. empathy_os/dashboard/__init__.py +15 -0
  113. empathy_os/dashboard/server.py +743 -0
  114. empathy_os/discovery.py +9 -11
  115. empathy_os/emergence.py +20 -21
  116. empathy_os/exceptions.py +18 -30
  117. empathy_os/feedback_loops.py +27 -30
  118. empathy_os/levels.py +31 -34
  119. empathy_os/leverage_points.py +27 -28
  120. empathy_os/logging_config.py +11 -12
  121. empathy_os/memory/__init__.py +195 -0
  122. empathy_os/memory/claude_memory.py +466 -0
  123. empathy_os/memory/config.py +224 -0
  124. empathy_os/memory/control_panel.py +1298 -0
  125. empathy_os/memory/edges.py +179 -0
  126. empathy_os/memory/graph.py +567 -0
  127. empathy_os/memory/long_term.py +1194 -0
  128. empathy_os/memory/nodes.py +179 -0
  129. empathy_os/memory/redis_bootstrap.py +540 -0
  130. empathy_os/memory/security/__init__.py +31 -0
  131. empathy_os/memory/security/audit_logger.py +930 -0
  132. empathy_os/memory/security/pii_scrubber.py +640 -0
  133. empathy_os/memory/security/secrets_detector.py +678 -0
  134. empathy_os/memory/short_term.py +2119 -0
  135. empathy_os/memory/storage/__init__.py +15 -0
  136. empathy_os/memory/summary_index.py +583 -0
  137. empathy_os/memory/unified.py +619 -0
  138. empathy_os/metrics/__init__.py +12 -0
  139. empathy_os/metrics/prompt_metrics.py +190 -0
  140. empathy_os/models/__init__.py +136 -0
  141. empathy_os/models/__main__.py +13 -0
  142. empathy_os/models/cli.py +655 -0
  143. empathy_os/models/empathy_executor.py +354 -0
  144. empathy_os/models/executor.py +252 -0
  145. empathy_os/models/fallback.py +671 -0
  146. empathy_os/models/provider_config.py +563 -0
  147. empathy_os/models/registry.py +382 -0
  148. empathy_os/models/tasks.py +302 -0
  149. empathy_os/models/telemetry.py +548 -0
  150. empathy_os/models/token_estimator.py +378 -0
  151. empathy_os/models/validation.py +274 -0
  152. empathy_os/monitoring/__init__.py +52 -0
  153. empathy_os/monitoring/alerts.py +23 -0
  154. empathy_os/monitoring/alerts_cli.py +268 -0
  155. empathy_os/monitoring/multi_backend.py +271 -0
  156. empathy_os/monitoring/otel_backend.py +363 -0
  157. empathy_os/optimization/__init__.py +19 -0
  158. empathy_os/optimization/context_optimizer.py +272 -0
  159. empathy_os/pattern_library.py +29 -28
  160. empathy_os/persistence.py +30 -34
  161. empathy_os/platform_utils.py +261 -0
  162. empathy_os/plugins/__init__.py +28 -0
  163. empathy_os/plugins/base.py +361 -0
  164. empathy_os/plugins/registry.py +268 -0
  165. empathy_os/project_index/__init__.py +30 -0
  166. empathy_os/project_index/cli.py +335 -0
  167. empathy_os/project_index/crew_integration.py +430 -0
  168. empathy_os/project_index/index.py +425 -0
  169. empathy_os/project_index/models.py +501 -0
  170. empathy_os/project_index/reports.py +473 -0
  171. empathy_os/project_index/scanner.py +538 -0
  172. empathy_os/prompts/__init__.py +61 -0
  173. empathy_os/prompts/config.py +77 -0
  174. empathy_os/prompts/context.py +177 -0
  175. empathy_os/prompts/parser.py +285 -0
  176. empathy_os/prompts/registry.py +313 -0
  177. empathy_os/prompts/templates.py +208 -0
  178. empathy_os/redis_config.py +144 -58
  179. empathy_os/redis_memory.py +53 -56
  180. empathy_os/resilience/__init__.py +56 -0
  181. empathy_os/resilience/circuit_breaker.py +256 -0
  182. empathy_os/resilience/fallback.py +179 -0
  183. empathy_os/resilience/health.py +300 -0
  184. empathy_os/resilience/retry.py +209 -0
  185. empathy_os/resilience/timeout.py +135 -0
  186. empathy_os/routing/__init__.py +43 -0
  187. empathy_os/routing/chain_executor.py +433 -0
  188. empathy_os/routing/classifier.py +217 -0
  189. empathy_os/routing/smart_router.py +234 -0
  190. empathy_os/routing/wizard_registry.py +307 -0
  191. empathy_os/templates.py +12 -11
  192. empathy_os/trust/__init__.py +28 -0
  193. empathy_os/trust/circuit_breaker.py +579 -0
  194. empathy_os/trust_building.py +44 -36
  195. empathy_os/validation/__init__.py +19 -0
  196. empathy_os/validation/xml_validator.py +281 -0
  197. empathy_os/wizard_factory_cli.py +170 -0
  198. empathy_os/{workflows.py → workflow_commands.py} +123 -31
  199. empathy_os/workflows/__init__.py +360 -0
  200. empathy_os/workflows/base.py +1660 -0
  201. empathy_os/workflows/bug_predict.py +962 -0
  202. empathy_os/workflows/code_review.py +960 -0
  203. empathy_os/workflows/code_review_adapters.py +310 -0
  204. empathy_os/workflows/code_review_pipeline.py +720 -0
  205. empathy_os/workflows/config.py +600 -0
  206. empathy_os/workflows/dependency_check.py +648 -0
  207. empathy_os/workflows/document_gen.py +1069 -0
  208. empathy_os/workflows/documentation_orchestrator.py +1205 -0
  209. empathy_os/workflows/health_check.py +679 -0
  210. empathy_os/workflows/keyboard_shortcuts/__init__.py +39 -0
  211. empathy_os/workflows/keyboard_shortcuts/generators.py +386 -0
  212. empathy_os/workflows/keyboard_shortcuts/parsers.py +414 -0
  213. empathy_os/workflows/keyboard_shortcuts/prompts.py +295 -0
  214. empathy_os/workflows/keyboard_shortcuts/schema.py +193 -0
  215. empathy_os/workflows/keyboard_shortcuts/workflow.py +505 -0
  216. empathy_os/workflows/manage_documentation.py +804 -0
  217. empathy_os/workflows/new_sample_workflow1.py +146 -0
  218. empathy_os/workflows/new_sample_workflow1_README.md +150 -0
  219. empathy_os/workflows/perf_audit.py +687 -0
  220. empathy_os/workflows/pr_review.py +748 -0
  221. empathy_os/workflows/progress.py +445 -0
  222. empathy_os/workflows/progress_server.py +322 -0
  223. empathy_os/workflows/refactor_plan.py +693 -0
  224. empathy_os/workflows/release_prep.py +808 -0
  225. empathy_os/workflows/research_synthesis.py +404 -0
  226. empathy_os/workflows/secure_release.py +585 -0
  227. empathy_os/workflows/security_adapters.py +297 -0
  228. empathy_os/workflows/security_audit.py +1046 -0
  229. empathy_os/workflows/step_config.py +234 -0
  230. empathy_os/workflows/test5.py +125 -0
  231. empathy_os/workflows/test5_README.md +158 -0
  232. empathy_os/workflows/test_gen.py +1855 -0
  233. empathy_os/workflows/test_lifecycle.py +526 -0
  234. empathy_os/workflows/test_maintenance.py +626 -0
  235. empathy_os/workflows/test_maintenance_cli.py +590 -0
  236. empathy_os/workflows/test_maintenance_crew.py +821 -0
  237. empathy_os/workflows/xml_enhanced_crew.py +285 -0
  238. empathy_software_plugin/__init__.py +1 -2
  239. empathy_software_plugin/cli/__init__.py +120 -0
  240. empathy_software_plugin/cli/inspect.py +362 -0
  241. empathy_software_plugin/cli.py +35 -26
  242. empathy_software_plugin/plugin.py +4 -8
  243. empathy_software_plugin/wizards/__init__.py +42 -0
  244. empathy_software_plugin/wizards/advanced_debugging_wizard.py +392 -0
  245. empathy_software_plugin/wizards/agent_orchestration_wizard.py +511 -0
  246. empathy_software_plugin/wizards/ai_collaboration_wizard.py +503 -0
  247. empathy_software_plugin/wizards/ai_context_wizard.py +441 -0
  248. empathy_software_plugin/wizards/ai_documentation_wizard.py +503 -0
  249. empathy_software_plugin/wizards/base_wizard.py +288 -0
  250. empathy_software_plugin/wizards/book_chapter_wizard.py +519 -0
  251. empathy_software_plugin/wizards/code_review_wizard.py +606 -0
  252. empathy_software_plugin/wizards/debugging/__init__.py +50 -0
  253. empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +414 -0
  254. empathy_software_plugin/wizards/debugging/config_loaders.py +442 -0
  255. empathy_software_plugin/wizards/debugging/fix_applier.py +469 -0
  256. empathy_software_plugin/wizards/debugging/language_patterns.py +383 -0
  257. empathy_software_plugin/wizards/debugging/linter_parsers.py +470 -0
  258. empathy_software_plugin/wizards/debugging/verification.py +369 -0
  259. empathy_software_plugin/wizards/enhanced_testing_wizard.py +537 -0
  260. empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +816 -0
  261. empathy_software_plugin/wizards/multi_model_wizard.py +501 -0
  262. empathy_software_plugin/wizards/pattern_extraction_wizard.py +422 -0
  263. empathy_software_plugin/wizards/pattern_retriever_wizard.py +400 -0
  264. empathy_software_plugin/wizards/performance/__init__.py +9 -0
  265. empathy_software_plugin/wizards/performance/bottleneck_detector.py +221 -0
  266. empathy_software_plugin/wizards/performance/profiler_parsers.py +278 -0
  267. empathy_software_plugin/wizards/performance/trajectory_analyzer.py +429 -0
  268. empathy_software_plugin/wizards/performance_profiling_wizard.py +305 -0
  269. empathy_software_plugin/wizards/prompt_engineering_wizard.py +425 -0
  270. empathy_software_plugin/wizards/rag_pattern_wizard.py +461 -0
  271. empathy_software_plugin/wizards/security/__init__.py +32 -0
  272. empathy_software_plugin/wizards/security/exploit_analyzer.py +290 -0
  273. empathy_software_plugin/wizards/security/owasp_patterns.py +241 -0
  274. empathy_software_plugin/wizards/security/vulnerability_scanner.py +604 -0
  275. empathy_software_plugin/wizards/security_analysis_wizard.py +322 -0
  276. empathy_software_plugin/wizards/security_learning_wizard.py +740 -0
  277. empathy_software_plugin/wizards/tech_debt_wizard.py +726 -0
  278. empathy_software_plugin/wizards/testing/__init__.py +27 -0
  279. empathy_software_plugin/wizards/testing/coverage_analyzer.py +459 -0
  280. empathy_software_plugin/wizards/testing/quality_analyzer.py +531 -0
  281. empathy_software_plugin/wizards/testing/test_suggester.py +533 -0
  282. empathy_software_plugin/wizards/testing_wizard.py +274 -0
  283. hot_reload/README.md +473 -0
  284. hot_reload/__init__.py +62 -0
  285. hot_reload/config.py +84 -0
  286. hot_reload/integration.py +228 -0
  287. hot_reload/reloader.py +298 -0
  288. hot_reload/watcher.py +179 -0
  289. hot_reload/websocket.py +176 -0
  290. scaffolding/README.md +589 -0
  291. scaffolding/__init__.py +35 -0
  292. scaffolding/__main__.py +14 -0
  293. scaffolding/cli.py +240 -0
  294. test_generator/__init__.py +38 -0
  295. test_generator/__main__.py +14 -0
  296. test_generator/cli.py +226 -0
  297. test_generator/generator.py +325 -0
  298. test_generator/risk_analyzer.py +216 -0
  299. workflow_patterns/__init__.py +33 -0
  300. workflow_patterns/behavior.py +249 -0
  301. workflow_patterns/core.py +76 -0
  302. workflow_patterns/output.py +99 -0
  303. workflow_patterns/registry.py +255 -0
  304. workflow_patterns/structural.py +288 -0
  305. workflow_scaffolding/__init__.py +11 -0
  306. workflow_scaffolding/__main__.py +12 -0
  307. workflow_scaffolding/cli.py +206 -0
  308. workflow_scaffolding/generator.py +265 -0
  309. agents/code_inspection/patterns/inspection/recurring_B112.json +0 -18
  310. agents/code_inspection/patterns/inspection/recurring_F541.json +0 -16
  311. agents/code_inspection/patterns/inspection/recurring_FORMAT.json +0 -25
  312. agents/code_inspection/patterns/inspection/recurring_bug_20250822_def456.json +0 -16
  313. agents/code_inspection/patterns/inspection/recurring_bug_20250915_abc123.json +0 -16
  314. agents/code_inspection/patterns/inspection/recurring_bug_20251212_3c5b9951.json +0 -16
  315. agents/code_inspection/patterns/inspection/recurring_bug_20251212_97c0f72f.json +0 -16
  316. agents/code_inspection/patterns/inspection/recurring_bug_20251212_a0871d53.json +0 -16
  317. agents/code_inspection/patterns/inspection/recurring_bug_20251212_a9b6ec41.json +0 -16
  318. agents/code_inspection/patterns/inspection/recurring_bug_null_001.json +0 -16
  319. agents/code_inspection/patterns/inspection/recurring_builtin.json +0 -16
  320. agents/compliance_anticipation_agent.py +0 -1427
  321. agents/epic_integration_wizard.py +0 -541
  322. agents/trust_building_behaviors.py +0 -891
  323. empathy_framework-3.2.3.dist-info/RECORD +0 -104
  324. empathy_framework-3.2.3.dist-info/entry_points.txt +0 -7
  325. empathy_llm_toolkit/htmlcov/status.json +0 -1
  326. empathy_llm_toolkit/security/htmlcov/status.json +0 -1
  327. {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/WHEEL +0 -0
  328. {empathy_framework-3.2.3.dist-info → empathy_framework-3.8.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,228 @@
1
+ """Integration example for hot-reload with wizard API.
2
+
3
+ Shows how to integrate hot-reload into the existing wizard_api.py.
4
+
5
+ Copyright 2025 Smart AI Memory, LLC
6
+ Licensed under Fair Source 0.9
7
+ """
8
+
9
+ import logging
10
+
11
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect
12
+
13
+ from .config import get_hot_reload_config
14
+ from .reloader import WizardReloader
15
+ from .watcher import WizardFileWatcher
16
+ from .websocket import create_notification_callback, get_notification_manager
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class HotReloadIntegration:
22
+ """Integrates hot-reload with wizard API.
23
+
24
+ Example usage in wizard_api.py:
25
+
26
+ from hot_reload.integration import HotReloadIntegration
27
+
28
+ # Create FastAPI app
29
+ app = FastAPI()
30
+
31
+ # Initialize hot-reload (if enabled)
32
+ hot_reload = HotReloadIntegration(app, register_wizard)
33
+
34
+ @app.on_event("startup")
35
+ async def startup_event():
36
+ init_wizards() # Initialize wizards
37
+ hot_reload.start() # Start hot-reload watcher
38
+
39
+ @app.on_event("shutdown")
40
+ async def shutdown_event():
41
+ hot_reload.stop()
42
+
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ app: FastAPI,
48
+ register_callback: callable,
49
+ ):
50
+ """Initialize hot-reload integration.
51
+
52
+ Args:
53
+ app: FastAPI application instance
54
+ register_callback: Function to register wizard (wizard_id, wizard_class) -> bool
55
+
56
+ """
57
+ self.app = app
58
+ self.register_callback = register_callback
59
+ self.config = get_hot_reload_config()
60
+
61
+ # Initialize components
62
+ self.notification_callback = create_notification_callback()
63
+ self.reloader = WizardReloader(
64
+ register_callback=self._register_wizard_wrapper,
65
+ notification_callback=self.notification_callback,
66
+ )
67
+
68
+ self.watcher: WizardFileWatcher | None = None
69
+
70
+ # Add WebSocket endpoint to app
71
+ if self.config.enabled:
72
+ self._setup_websocket_endpoint()
73
+
74
+ def _register_wizard_wrapper(self, wizard_id: str, wizard_class: type) -> bool:
75
+ """Wrapper for register callback that handles errors.
76
+
77
+ Args:
78
+ wizard_id: Wizard identifier
79
+ wizard_class: Wizard class to register
80
+
81
+ Returns:
82
+ True if registration succeeded
83
+
84
+ """
85
+ try:
86
+ return self.register_callback(wizard_id, wizard_class)
87
+ except Exception as e:
88
+ logger.error(f"Error registering wizard {wizard_id}: {e}")
89
+ return False
90
+
91
+ def _setup_websocket_endpoint(self) -> None:
92
+ """Add WebSocket endpoint to FastAPI app."""
93
+
94
+ @self.app.websocket(self.config.websocket_path)
95
+ async def hot_reload_websocket(websocket: WebSocket):
96
+ """WebSocket endpoint for hot-reload notifications."""
97
+ manager = get_notification_manager()
98
+
99
+ await manager.connect(websocket)
100
+
101
+ try:
102
+ # Keep connection alive
103
+ while True:
104
+ # Receive ping messages
105
+ await websocket.receive_text()
106
+
107
+ except WebSocketDisconnect:
108
+ await manager.disconnect(websocket)
109
+ except Exception as e:
110
+ logger.error(f"WebSocket error: {e}")
111
+ await manager.disconnect(websocket)
112
+
113
+ logger.info(f"WebSocket endpoint added: {self.config.websocket_path}")
114
+
115
+ def start(self) -> None:
116
+ """Start hot-reload watcher."""
117
+ if not self.config.enabled:
118
+ logger.info("Hot-reload disabled (set HOT_RELOAD_ENABLED=true to enable)")
119
+ return
120
+
121
+ if not self.config.watch_dirs:
122
+ logger.warning("No wizard directories found to watch")
123
+ return
124
+
125
+ if self.watcher and self.watcher.is_running():
126
+ logger.warning("Hot-reload already started")
127
+ return
128
+
129
+ # Create watcher
130
+ self.watcher = WizardFileWatcher(
131
+ wizard_dirs=self.config.watch_dirs,
132
+ reload_callback=self._on_file_change,
133
+ )
134
+
135
+ # Start watching
136
+ self.watcher.start()
137
+
138
+ logger.info(f"🔥 Hot-reload started - watching {len(self.config.watch_dirs)} directories")
139
+
140
+ def stop(self) -> None:
141
+ """Stop hot-reload watcher."""
142
+ if self.watcher:
143
+ self.watcher.stop()
144
+ self.watcher = None
145
+ logger.info("Hot-reload stopped")
146
+
147
+ def _on_file_change(self, wizard_id: str, file_path: str) -> None:
148
+ """Handle file change event.
149
+
150
+ Args:
151
+ wizard_id: ID of wizard that changed
152
+ file_path: Path to changed file
153
+
154
+ """
155
+ logger.info(f"File change detected: {wizard_id} ({file_path})")
156
+
157
+ # Reload wizard
158
+ result = self.reloader.reload_wizard(wizard_id, file_path)
159
+
160
+ if result.success:
161
+ logger.info(f"✓ {result.message}")
162
+ else:
163
+ logger.error(f"✗ Reload failed: {result.error}")
164
+
165
+ def get_status(self) -> dict:
166
+ """Get hot-reload status.
167
+
168
+ Returns:
169
+ Status dictionary
170
+
171
+ """
172
+ return {
173
+ "enabled": self.config.enabled,
174
+ "running": self.watcher.is_running() if self.watcher else False,
175
+ "watch_dirs": [str(d) for d in self.config.watch_dirs],
176
+ "reload_count": self.reloader.get_reload_count(),
177
+ "websocket_connections": get_notification_manager().get_connection_count(),
178
+ "websocket_path": self.config.websocket_path,
179
+ }
180
+
181
+
182
+ # Example usage in wizard_api.py:
183
+ """
184
+ from fastapi import FastAPI
185
+ from hot_reload.integration import HotReloadIntegration
186
+
187
+ app = FastAPI(title="Empathy Wizard API")
188
+
189
+ # Global hot-reload instance
190
+ hot_reload = None
191
+
192
+
193
+ def register_wizard(wizard_id: str, wizard_class: type, *args, **kwargs) -> bool:
194
+ '''Register wizard with WIZARDS dict'''
195
+ try:
196
+ WIZARDS[wizard_id] = wizard_class(*args, **kwargs)
197
+ logger.info(f"✓ Registered wizard: {wizard_id}")
198
+ return True
199
+ except Exception as e:
200
+ logger.error(f"Failed to register {wizard_id}: {e}")
201
+ return False
202
+
203
+
204
+ @app.on_event("startup")
205
+ async def startup_event():
206
+ global hot_reload
207
+
208
+ # Initialize wizards
209
+ init_wizards()
210
+
211
+ # Start hot-reload
212
+ hot_reload = HotReloadIntegration(app, register_wizard)
213
+ hot_reload.start()
214
+
215
+
216
+ @app.on_event("shutdown")
217
+ async def shutdown_event():
218
+ if hot_reload:
219
+ hot_reload.stop()
220
+
221
+
222
+ @app.get("/api/hot-reload/status")
223
+ async def get_hot_reload_status():
224
+ '''Get hot-reload status'''
225
+ if not hot_reload:
226
+ return {"enabled": False}
227
+ return hot_reload.get_status()
228
+ """
hot_reload/reloader.py ADDED
@@ -0,0 +1,298 @@
1
+ """Dynamic wizard reloader for hot-reload.
2
+
3
+ Handles reloading wizard modules without server restart.
4
+
5
+ Copyright 2025 Smart AI Memory, LLC
6
+ Licensed under Fair Source 0.9
7
+ """
8
+
9
+ import importlib
10
+ import logging
11
+ import sys
12
+ from collections.abc import Callable
13
+ from pathlib import Path
14
+ from typing import Any
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class ReloadResult:
20
+ """Result of a wizard reload operation."""
21
+
22
+ def __init__(
23
+ self,
24
+ success: bool,
25
+ wizard_id: str,
26
+ message: str,
27
+ error: str | None = None,
28
+ ):
29
+ """Initialize reload result.
30
+
31
+ Args:
32
+ success: Whether reload succeeded
33
+ wizard_id: ID of wizard that was reloaded
34
+ message: Status message
35
+ error: Error message if failed
36
+
37
+ """
38
+ self.success = success
39
+ self.wizard_id = wizard_id
40
+ self.message = message
41
+ self.error = error
42
+
43
+ def to_dict(self) -> dict[str, Any]:
44
+ """Convert to dictionary."""
45
+ return {
46
+ "success": self.success,
47
+ "wizard_id": self.wizard_id,
48
+ "message": self.message,
49
+ "error": self.error,
50
+ }
51
+
52
+
53
+ class WizardReloader:
54
+ """Handles dynamic reloading of wizard modules.
55
+
56
+ Supports hot-reload of wizards without server restart by:
57
+ 1. Unloading old module from sys.modules
58
+ 2. Reloading module with importlib
59
+ 3. Re-registering wizard with wizard API
60
+ 4. Notifying clients via callback
61
+ """
62
+
63
+ def __init__(
64
+ self,
65
+ register_callback: Callable[[str, type], bool],
66
+ notification_callback: Callable[[dict], None] | None = None,
67
+ ):
68
+ """Initialize reloader.
69
+
70
+ Args:
71
+ register_callback: Function to register wizard (wizard_id, wizard_class) -> success
72
+ notification_callback: Optional function to notify clients of reload events
73
+
74
+ """
75
+ self.register_callback = register_callback
76
+ self.notification_callback = notification_callback
77
+ self._reload_count = 0
78
+
79
+ def reload_wizard(self, wizard_id: str, file_path: str) -> ReloadResult:
80
+ """Reload a wizard module.
81
+
82
+ Args:
83
+ wizard_id: Wizard identifier
84
+ file_path: Path to wizard file
85
+
86
+ Returns:
87
+ ReloadResult with outcome
88
+
89
+ """
90
+ logger.info(f"Attempting to reload wizard: {wizard_id} from {file_path}")
91
+
92
+ try:
93
+ # Get module name from file path
94
+ module_name = self._get_module_name(file_path)
95
+ if not module_name:
96
+ error_msg = f"Could not determine module name from {file_path}"
97
+ logger.error(error_msg)
98
+ return ReloadResult(
99
+ success=False,
100
+ wizard_id=wizard_id,
101
+ message="Failed to reload",
102
+ error=error_msg,
103
+ )
104
+
105
+ # Unload old module
106
+ self._unload_module(module_name)
107
+
108
+ # Reload module
109
+ try:
110
+ module = importlib.import_module(module_name)
111
+ except ImportError as e:
112
+ error_msg = f"Failed to import module {module_name}: {e}"
113
+ logger.error(error_msg)
114
+ self._notify_reload_failed(wizard_id, error_msg)
115
+ return ReloadResult(
116
+ success=False,
117
+ wizard_id=wizard_id,
118
+ message="Import failed",
119
+ error=error_msg,
120
+ )
121
+
122
+ # Find wizard class in module
123
+ wizard_class = self._find_wizard_class(module)
124
+ if not wizard_class:
125
+ error_msg = f"No wizard class found in {module_name}"
126
+ logger.error(error_msg)
127
+ self._notify_reload_failed(wizard_id, error_msg)
128
+ return ReloadResult(
129
+ success=False,
130
+ wizard_id=wizard_id,
131
+ message="No wizard class found",
132
+ error=error_msg,
133
+ )
134
+
135
+ # Re-register wizard
136
+ success = self.register_callback(wizard_id, wizard_class)
137
+
138
+ if success:
139
+ self._reload_count += 1
140
+ logger.info(
141
+ f"✓ Successfully reloaded {wizard_id} ({self._reload_count} total reloads)"
142
+ )
143
+ self._notify_reload_success(wizard_id)
144
+
145
+ return ReloadResult(
146
+ success=True,
147
+ wizard_id=wizard_id,
148
+ message=f"Reloaded successfully (reload #{self._reload_count})",
149
+ )
150
+ else:
151
+ error_msg = "Registration failed"
152
+ logger.error(f"Failed to re-register {wizard_id}")
153
+ self._notify_reload_failed(wizard_id, error_msg)
154
+ return ReloadResult(
155
+ success=False,
156
+ wizard_id=wizard_id,
157
+ message="Registration failed",
158
+ error=error_msg,
159
+ )
160
+
161
+ except Exception as e:
162
+ error_msg = f"Unexpected error reloading {wizard_id}: {e}"
163
+ logger.exception(error_msg)
164
+ self._notify_reload_failed(wizard_id, str(e))
165
+ return ReloadResult(
166
+ success=False,
167
+ wizard_id=wizard_id,
168
+ message="Unexpected error",
169
+ error=str(e),
170
+ )
171
+
172
+ def _get_module_name(self, file_path: str) -> str | None:
173
+ """Get Python module name from file path.
174
+
175
+ Args:
176
+ file_path: Path to Python file
177
+
178
+ Returns:
179
+ Module name or None if cannot determine
180
+
181
+ """
182
+ try:
183
+ path = Path(file_path).resolve()
184
+
185
+ # Remove .py extension
186
+ if not path.suffix == ".py":
187
+ return None
188
+
189
+ # Get parts relative to project root
190
+ # Try to find common patterns: wizards/, coach_wizards/, empathy_software_plugin/wizards/
191
+ parts = path.parts
192
+
193
+ # Find wizard directory in path
194
+ wizard_dir_indices = [i for i, part in enumerate(parts) if "wizard" in part.lower()]
195
+
196
+ if not wizard_dir_indices:
197
+ return None
198
+
199
+ # Take from first wizard directory
200
+ start_idx = wizard_dir_indices[0]
201
+
202
+ # Build module name
203
+ module_parts = list(parts[start_idx:])
204
+ module_parts[-1] = module_parts[-1].replace(".py", "")
205
+
206
+ module_name = ".".join(module_parts)
207
+ return module_name
208
+
209
+ except Exception as e:
210
+ logger.error(f"Error getting module name from {file_path}: {e}")
211
+ return None
212
+
213
+ def _unload_module(self, module_name: str) -> None:
214
+ """Unload module from sys.modules.
215
+
216
+ Args:
217
+ module_name: Name of module to unload
218
+
219
+ """
220
+ # Unload exact module
221
+ if module_name in sys.modules:
222
+ del sys.modules[module_name]
223
+ logger.debug(f"Unloaded module: {module_name}")
224
+
225
+ # Also unload any submodules
226
+ submodules = [name for name in sys.modules.keys() if name.startswith(f"{module_name}.")]
227
+ for submodule in submodules:
228
+ del sys.modules[submodule]
229
+ logger.debug(f"Unloaded submodule: {submodule}")
230
+
231
+ def _find_wizard_class(self, module: Any) -> type | None:
232
+ """Find wizard class in module.
233
+
234
+ Args:
235
+ module: Python module
236
+
237
+ Returns:
238
+ Wizard class or None if not found
239
+
240
+ """
241
+ # Look for classes ending with "Wizard"
242
+ for name in dir(module):
243
+ if name.endswith("Wizard") and not name.startswith("_"):
244
+ attr = getattr(module, name)
245
+ if isinstance(attr, type):
246
+ return attr
247
+
248
+ return None
249
+
250
+ def _notify_reload_success(self, wizard_id: str) -> None:
251
+ """Notify clients of successful reload.
252
+
253
+ Args:
254
+ wizard_id: ID of reloaded wizard
255
+
256
+ """
257
+ if self.notification_callback:
258
+ try:
259
+ self.notification_callback(
260
+ {
261
+ "event": "wizard_reloaded",
262
+ "wizard_id": wizard_id,
263
+ "success": True,
264
+ "reload_count": self._reload_count,
265
+ }
266
+ )
267
+ except Exception as e:
268
+ logger.error(f"Error sending reload notification: {e}")
269
+
270
+ def _notify_reload_failed(self, wizard_id: str, error: str) -> None:
271
+ """Notify clients of failed reload.
272
+
273
+ Args:
274
+ wizard_id: ID of wizard that failed to reload
275
+ error: Error message
276
+
277
+ """
278
+ if self.notification_callback:
279
+ try:
280
+ self.notification_callback(
281
+ {
282
+ "event": "wizard_reload_failed",
283
+ "wizard_id": wizard_id,
284
+ "success": False,
285
+ "error": error,
286
+ }
287
+ )
288
+ except Exception as e:
289
+ logger.error(f"Error sending failure notification: {e}")
290
+
291
+ def get_reload_count(self) -> int:
292
+ """Get total number of successful reloads.
293
+
294
+ Returns:
295
+ Reload count
296
+
297
+ """
298
+ return self._reload_count
hot_reload/watcher.py ADDED
@@ -0,0 +1,179 @@
1
+ """File system watcher for wizard hot-reload.
2
+
3
+ Monitors wizard directories for changes and triggers reloads.
4
+
5
+ Copyright 2025 Smart AI Memory, LLC
6
+ Licensed under Fair Source 0.9
7
+ """
8
+
9
+ import logging
10
+ from collections.abc import Callable
11
+ from pathlib import Path
12
+
13
+ from watchdog.events import FileSystemEvent, FileSystemEventHandler
14
+ from watchdog.observers import Observer
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class WizardFileHandler(FileSystemEventHandler):
20
+ """Handles file system events for wizard files."""
21
+
22
+ def __init__(self, reload_callback: Callable[[str], None]):
23
+ """Initialize handler.
24
+
25
+ Args:
26
+ reload_callback: Function to call when wizard file changes
27
+
28
+ """
29
+ super().__init__()
30
+ self.reload_callback = reload_callback
31
+ self._processing = set() # Prevent duplicate events
32
+
33
+ def on_modified(self, event: FileSystemEvent) -> None:
34
+ """Handle file modification events.
35
+
36
+ Args:
37
+ event: File system event
38
+
39
+ """
40
+ if event.is_directory:
41
+ return
42
+
43
+ file_path = event.src_path
44
+
45
+ # Only process Python files
46
+ if not file_path.endswith(".py"):
47
+ return
48
+
49
+ # Skip __pycache__ and test files
50
+ if "__pycache__" in file_path or "test_" in file_path:
51
+ return
52
+
53
+ # Prevent duplicate processing
54
+ if file_path in self._processing:
55
+ return
56
+
57
+ try:
58
+ self._processing.add(file_path)
59
+
60
+ wizard_id = self._extract_wizard_id(file_path)
61
+ if wizard_id:
62
+ logger.info(f"Detected change in {wizard_id} ({file_path})")
63
+ self.reload_callback(wizard_id, file_path)
64
+
65
+ except Exception as e:
66
+ logger.error(f"Error processing file change {file_path}: {e}")
67
+ finally:
68
+ self._processing.discard(file_path)
69
+
70
+ def _extract_wizard_id(self, file_path: str) -> str | None:
71
+ """Extract wizard ID from file path.
72
+
73
+ Args:
74
+ file_path: Path to wizard file
75
+
76
+ Returns:
77
+ Wizard ID or None if cannot extract
78
+
79
+ """
80
+ path = Path(file_path)
81
+
82
+ # Get filename without extension
83
+ filename = path.stem
84
+
85
+ # Remove common suffixes
86
+ wizard_id = filename.replace("_wizard", "").replace("wizard_", "")
87
+
88
+ # Convert to wizard ID format (snake_case)
89
+ wizard_id = wizard_id.lower()
90
+
91
+ return wizard_id if wizard_id else None
92
+
93
+
94
+ class WizardFileWatcher:
95
+ """Watches wizard directories for file changes.
96
+
97
+ Monitors specified directories and triggers reload callbacks
98
+ when wizard files are modified.
99
+ """
100
+
101
+ def __init__(self, wizard_dirs: list[Path], reload_callback: Callable[[str, str], None]):
102
+ """Initialize watcher.
103
+
104
+ Args:
105
+ wizard_dirs: List of directories to watch
106
+ reload_callback: Function to call on file changes (wizard_id, file_path)
107
+
108
+ """
109
+ self.wizard_dirs = [Path(d) for d in wizard_dirs]
110
+ self.reload_callback = reload_callback
111
+ self.observer = Observer()
112
+ self.event_handler = WizardFileHandler(reload_callback)
113
+ self._running = False
114
+
115
+ def start(self) -> None:
116
+ """Start watching wizard directories."""
117
+ if self._running:
118
+ logger.warning("Watcher already running")
119
+ return
120
+
121
+ valid_dirs = []
122
+ for directory in self.wizard_dirs:
123
+ if not directory.exists():
124
+ logger.warning(f"Directory does not exist: {directory}")
125
+ continue
126
+
127
+ if not directory.is_dir():
128
+ logger.warning(f"Not a directory: {directory}")
129
+ continue
130
+
131
+ # Schedule watching
132
+ self.observer.schedule(
133
+ self.event_handler,
134
+ str(directory),
135
+ recursive=True,
136
+ )
137
+ valid_dirs.append(directory)
138
+ logger.info(f"Watching directory: {directory}")
139
+
140
+ if not valid_dirs:
141
+ logger.error("No valid directories to watch")
142
+ return
143
+
144
+ self.observer.start()
145
+ self._running = True
146
+
147
+ logger.info(
148
+ f"Hot-reload enabled for {len(valid_dirs)} "
149
+ f"{'directory' if len(valid_dirs) == 1 else 'directories'}"
150
+ )
151
+
152
+ def stop(self) -> None:
153
+ """Stop watching wizard directories."""
154
+ if not self._running:
155
+ return
156
+
157
+ self.observer.stop()
158
+ self.observer.join(timeout=5.0)
159
+ self._running = False
160
+
161
+ logger.info("Hot-reload watcher stopped")
162
+
163
+ def is_running(self) -> bool:
164
+ """Check if watcher is running.
165
+
166
+ Returns:
167
+ True if watching, False otherwise
168
+
169
+ """
170
+ return self._running
171
+
172
+ def __enter__(self):
173
+ """Context manager entry."""
174
+ self.start()
175
+ return self
176
+
177
+ def __exit__(self, exc_type, exc_val, exc_tb):
178
+ """Context manager exit."""
179
+ self.stop()