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,390 @@
1
+ """Hybrid cache with hash + semantic similarity matching.
2
+
3
+ Combines fast hash-based exact matching with intelligent semantic similarity
4
+ for maximum cache hit rate (~70%).
5
+
6
+ Requires optional dependencies:
7
+ - sentence-transformers
8
+ - torch
9
+ - numpy
10
+
11
+ Copyright 2025 Smart-AI-Memory
12
+ Licensed under Fair Source License 0.9
13
+ """
14
+
15
+ import hashlib
16
+ import logging
17
+ import time
18
+ from typing import TYPE_CHECKING, Any
19
+
20
+ import numpy as np
21
+
22
+ from .base import BaseCache, CacheEntry, CacheStats
23
+
24
+ if TYPE_CHECKING:
25
+ from sentence_transformers import SentenceTransformer
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
31
+ """Calculate cosine similarity between two vectors.
32
+
33
+ Args:
34
+ a: First vector.
35
+ b: Second vector.
36
+
37
+ Returns:
38
+ Similarity score (0.0 to 1.0).
39
+
40
+ """
41
+ return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
42
+
43
+
44
+ class HybridCache(BaseCache):
45
+ """Hybrid hash + semantic similarity cache for maximum hit rate.
46
+
47
+ Provides two-tier caching:
48
+ 1. Fast path: Hash-based exact matching (~1-5μs lookup)
49
+ 2. Smart path: Semantic similarity matching (~50ms lookup)
50
+
51
+ Achieves ~70% cache hit rate vs ~30% for hash-only.
52
+
53
+ Example:
54
+ cache = HybridCache(similarity_threshold=0.95)
55
+
56
+ # First call (miss)
57
+ result = cache.get("code-review", "scan", "Add auth middleware", "sonnet")
58
+ # → None (cache miss)
59
+
60
+ cache.put("code-review", "scan", "Add auth middleware", "sonnet", response1)
61
+
62
+ # Exact match (hash hit, <5μs)
63
+ result = cache.get("code-review", "scan", "Add auth middleware", "sonnet")
64
+ # → response1 (hash cache hit)
65
+
66
+ # Similar prompt (semantic hit, ~50ms)
67
+ result = cache.get("code-review", "scan", "Add logging middleware", "sonnet")
68
+ # → response1 (92% similar, semantic cache hit)
69
+
70
+ """
71
+
72
+ def __init__(
73
+ self,
74
+ max_size_mb: int = 500,
75
+ default_ttl: int = 86400,
76
+ max_memory_mb: int = 100,
77
+ similarity_threshold: float = 0.95,
78
+ model_name: str = "all-MiniLM-L6-v2",
79
+ device: str = "cpu",
80
+ ):
81
+ """Initialize hybrid cache.
82
+
83
+ Args:
84
+ max_size_mb: Maximum disk cache size in MB.
85
+ default_ttl: Default TTL in seconds (24 hours).
86
+ max_memory_mb: Maximum in-memory cache size in MB.
87
+ similarity_threshold: Semantic similarity threshold (0.0-1.0, default: 0.95).
88
+ model_name: Sentence transformer model (default: all-MiniLM-L6-v2).
89
+ device: Device for embeddings ("cpu" or "cuda").
90
+
91
+ """
92
+ super().__init__(max_size_mb, default_ttl)
93
+ self.max_memory_mb = max_memory_mb
94
+ self.similarity_threshold = similarity_threshold
95
+ self.model_name = model_name
96
+ self.device = device
97
+
98
+ # Hash cache (fast path)
99
+ self._hash_cache: dict[str, CacheEntry] = {}
100
+ self._access_times: dict[str, float] = {}
101
+
102
+ # Semantic cache (smart path)
103
+ self._semantic_cache: list[tuple[np.ndarray, CacheEntry]] = []
104
+
105
+ # Load sentence transformer model
106
+ self._model: SentenceTransformer | None = None
107
+ self._load_model()
108
+
109
+ logger.info(
110
+ f"HybridCache initialized (model: {model_name}, threshold: {similarity_threshold}, "
111
+ f"device: {device}, max_memory: {max_memory_mb}MB)"
112
+ )
113
+
114
+ def _load_model(self) -> None:
115
+ """Load sentence transformer model for embeddings."""
116
+ try:
117
+ from sentence_transformers import SentenceTransformer
118
+
119
+ logger.debug(f"Loading sentence transformer model: {self.model_name}")
120
+ self._model = SentenceTransformer(self.model_name, device=self.device)
121
+ logger.info(f"Sentence transformer loaded successfully on {self.device}")
122
+
123
+ except ImportError as e:
124
+ logger.error(
125
+ f"Failed to load sentence-transformers: {e}. "
126
+ "Install with: pip install empathy-framework[cache]"
127
+ )
128
+ raise
129
+ except Exception as e:
130
+ logger.warning(f"Failed to load model {self.model_name}: {e}")
131
+ logger.warning("Falling back to hash-only mode")
132
+ self._model = None
133
+
134
+ def get(
135
+ self,
136
+ workflow: str,
137
+ stage: str,
138
+ prompt: str,
139
+ model: str,
140
+ ) -> Any | None:
141
+ """Get cached response using hybrid hash + semantic matching.
142
+
143
+ Args:
144
+ workflow: Workflow name.
145
+ stage: Stage name.
146
+ prompt: Prompt text.
147
+ model: Model identifier.
148
+
149
+ Returns:
150
+ Cached response if found (hash or semantic match), None otherwise.
151
+
152
+ """
153
+ cache_key = self._create_cache_key(workflow, stage, prompt, model)
154
+ current_time = time.time()
155
+
156
+ # Step 1: Try hash cache (fast path, <5μs)
157
+ if cache_key in self._hash_cache:
158
+ entry = self._hash_cache[cache_key]
159
+
160
+ if entry.is_expired(current_time):
161
+ self._evict_entry(cache_key)
162
+ self.stats.misses += 1
163
+ return None
164
+
165
+ # Hash hit!
166
+ self._access_times[cache_key] = current_time
167
+ self.stats.hits += 1
168
+ logger.debug(
169
+ f"Cache HIT (hash): {workflow}/{stage} " f"(hit_rate: {self.stats.hit_rate:.1f}%)"
170
+ )
171
+ return entry.response
172
+
173
+ # Step 2: Try semantic cache (smart path, ~50ms)
174
+ if self._model is not None:
175
+ semantic_result = self._semantic_lookup(prompt, workflow, stage, model)
176
+ if semantic_result is not None:
177
+ # Semantic hit! Add to hash cache for future fast lookups
178
+ entry, similarity = semantic_result
179
+ self._hash_cache[cache_key] = entry
180
+ self._access_times[cache_key] = current_time
181
+
182
+ self.stats.hits += 1
183
+ logger.debug(
184
+ f"Cache HIT (semantic): {workflow}/{stage} "
185
+ f"(similarity: {similarity:.3f}, hit_rate: {self.stats.hit_rate:.1f}%)"
186
+ )
187
+ return entry.response
188
+
189
+ # Step 3: Cache miss
190
+ self.stats.misses += 1
191
+ logger.debug(
192
+ f"Cache MISS (hybrid): {workflow}/{stage} " f"(hit_rate: {self.stats.hit_rate:.1f}%)"
193
+ )
194
+ return None
195
+
196
+ def _semantic_lookup(
197
+ self,
198
+ prompt: str,
199
+ workflow: str,
200
+ stage: str,
201
+ model: str,
202
+ ) -> tuple[CacheEntry, float] | None:
203
+ """Perform semantic similarity lookup.
204
+
205
+ Args:
206
+ prompt: Prompt text.
207
+ workflow: Workflow name.
208
+ stage: Stage name.
209
+ model: Model identifier.
210
+
211
+ Returns:
212
+ Tuple of (CacheEntry, similarity_score) if match found, None otherwise.
213
+
214
+ """
215
+ if not self._semantic_cache:
216
+ return None
217
+
218
+ if self._model is None:
219
+ raise RuntimeError("Sentence transformer model not loaded")
220
+
221
+ # Encode prompt
222
+ prompt_embedding = self._model.encode(prompt, convert_to_numpy=True)
223
+
224
+ # Find best match
225
+ best_similarity = 0.0
226
+ best_entry = None
227
+ current_time = time.time()
228
+
229
+ for cached_embedding, entry in self._semantic_cache:
230
+ # Only match same workflow, stage, and model
231
+ if entry.workflow != workflow or entry.stage != stage or entry.model != model:
232
+ continue
233
+
234
+ # Skip expired
235
+ if entry.is_expired(current_time):
236
+ continue
237
+
238
+ # Calculate similarity
239
+ similarity = cosine_similarity(prompt_embedding, cached_embedding)
240
+
241
+ if similarity > best_similarity:
242
+ best_similarity = similarity
243
+ best_entry = entry
244
+
245
+ # Check if best match exceeds threshold
246
+ if best_similarity >= self.similarity_threshold and best_entry is not None:
247
+ return (best_entry, best_similarity)
248
+
249
+ return None
250
+
251
+ def put(
252
+ self,
253
+ workflow: str,
254
+ stage: str,
255
+ prompt: str,
256
+ model: str,
257
+ response: Any,
258
+ ttl: int | None = None,
259
+ ) -> None:
260
+ """Store response in both hash and semantic caches.
261
+
262
+ Args:
263
+ workflow: Workflow name.
264
+ stage: Stage name.
265
+ prompt: Prompt text.
266
+ model: Model identifier.
267
+ response: LLM response to cache.
268
+ ttl: Optional custom TTL.
269
+
270
+ """
271
+ cache_key = self._create_cache_key(workflow, stage, prompt, model)
272
+ prompt_hash = hashlib.sha256(prompt.encode()).hexdigest()
273
+
274
+ # Create cache entry
275
+ entry = CacheEntry(
276
+ key=cache_key,
277
+ response=response,
278
+ workflow=workflow,
279
+ stage=stage,
280
+ model=model,
281
+ prompt_hash=prompt_hash,
282
+ timestamp=time.time(),
283
+ ttl=ttl or self.default_ttl,
284
+ )
285
+
286
+ # Maybe evict before adding
287
+ self._maybe_evict_lru()
288
+
289
+ # Store in hash cache
290
+ self._hash_cache[cache_key] = entry
291
+ self._access_times[cache_key] = entry.timestamp
292
+
293
+ # Store in semantic cache (if model available)
294
+ if self._model is not None:
295
+ prompt_embedding = self._model.encode(prompt, convert_to_numpy=True)
296
+ self._semantic_cache.append((prompt_embedding, entry))
297
+
298
+ logger.debug(
299
+ f"Cache PUT (hybrid): {workflow}/{stage} "
300
+ f"(hash_entries: {len(self._hash_cache)}, "
301
+ f"semantic_entries: {len(self._semantic_cache)})"
302
+ )
303
+
304
+ def clear(self) -> None:
305
+ """Clear all cached entries."""
306
+ hash_count = len(self._hash_cache)
307
+ semantic_count = len(self._semantic_cache)
308
+
309
+ self._hash_cache.clear()
310
+ self._access_times.clear()
311
+ self._semantic_cache.clear()
312
+
313
+ logger.info(f"Cache cleared (hash: {hash_count}, semantic: {semantic_count} entries)")
314
+
315
+ def get_stats(self) -> CacheStats:
316
+ """Get cache statistics."""
317
+ return self.stats
318
+
319
+ def _evict_entry(self, cache_key: str) -> None:
320
+ """Remove entry from both caches.
321
+
322
+ Args:
323
+ cache_key: Key to evict.
324
+
325
+ """
326
+ # Remove from hash cache
327
+ if cache_key in self._hash_cache:
328
+ entry = self._hash_cache[cache_key]
329
+ del self._hash_cache[cache_key]
330
+
331
+ # Remove from semantic cache
332
+ self._semantic_cache = [
333
+ (emb, e) for emb, e in self._semantic_cache if e.key != entry.key
334
+ ]
335
+
336
+ if cache_key in self._access_times:
337
+ del self._access_times[cache_key]
338
+
339
+ self.stats.evictions += 1
340
+
341
+ def _maybe_evict_lru(self) -> None:
342
+ """Evict least recently used entries if cache too large."""
343
+ # Estimate memory (rough)
344
+ estimated_mb = (len(self._hash_cache) * 0.01) + (len(self._semantic_cache) * 0.1)
345
+
346
+ if estimated_mb > self.max_memory_mb:
347
+ # Evict 10% of entries
348
+ num_to_evict = max(1, len(self._hash_cache) // 10)
349
+
350
+ # Sort by access time
351
+ sorted_keys = sorted(self._access_times.items(), key=lambda x: x[1])
352
+
353
+ for cache_key, _ in sorted_keys[:num_to_evict]:
354
+ self._evict_entry(cache_key)
355
+
356
+ logger.info(
357
+ f"LRU eviction: removed {num_to_evict} entries "
358
+ f"(hash: {len(self._hash_cache)}, semantic: {len(self._semantic_cache)})"
359
+ )
360
+
361
+ def evict_expired(self) -> int:
362
+ """Remove all expired entries."""
363
+ current_time = time.time()
364
+ expired_keys = [
365
+ key for key, entry in self._hash_cache.items() if entry.is_expired(current_time)
366
+ ]
367
+
368
+ for key in expired_keys:
369
+ self._evict_entry(key)
370
+
371
+ if expired_keys:
372
+ logger.info(f"Expired eviction: removed {len(expired_keys)} entries")
373
+
374
+ return len(expired_keys)
375
+
376
+ def size_info(self) -> dict[str, Any]:
377
+ """Get cache size information."""
378
+ hash_mb = len(self._hash_cache) * 0.01
379
+ semantic_mb = len(self._semantic_cache) * 0.1
380
+
381
+ return {
382
+ "hash_entries": len(self._hash_cache),
383
+ "semantic_entries": len(self._semantic_cache),
384
+ "hash_size_mb": round(hash_mb, 2),
385
+ "semantic_size_mb": round(semantic_mb, 2),
386
+ "total_size_mb": round(hash_mb + semantic_mb, 2),
387
+ "max_memory_mb": self.max_memory_mb,
388
+ "model": self.model_name,
389
+ "threshold": self.similarity_threshold,
390
+ }
@@ -0,0 +1,282 @@
1
+ """Persistent disk storage for cache with TTL support.
2
+
3
+ Provides hybrid in-memory + disk persistence for cache entries.
4
+
5
+ Copyright 2025 Smart-AI-Memory
6
+ Licensed under Fair Source License 0.9
7
+ """
8
+
9
+ import json
10
+ import logging
11
+ import time
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ from .base import CacheEntry
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class CacheStorage:
21
+ """Hybrid in-memory + disk cache storage with TTL.
22
+
23
+ Provides:
24
+ - In-memory LRU cache for fast access
25
+ - Persistent disk storage for cache survival across restarts
26
+ - TTL-based expiration
27
+ - Automatic cleanup of expired entries
28
+
29
+ Example:
30
+ storage = CacheStorage()
31
+ storage.put(entry)
32
+ entry = storage.get(cache_key)
33
+
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ cache_dir: Path | None = None,
39
+ max_disk_mb: int = 500,
40
+ auto_save: bool = True,
41
+ ):
42
+ """Initialize cache storage.
43
+
44
+ Args:
45
+ cache_dir: Directory for cache files (default: ~/.empathy/cache/).
46
+ max_disk_mb: Maximum disk cache size in MB.
47
+ auto_save: Automatically save to disk on put (default: True).
48
+
49
+ """
50
+ self.cache_dir = cache_dir or Path.home() / ".empathy" / "cache"
51
+ self.cache_file = self.cache_dir / "responses.json"
52
+ self.max_disk_mb = max_disk_mb
53
+ self.auto_save = auto_save
54
+
55
+ # Ensure cache directory exists
56
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
57
+
58
+ # Load cache from disk
59
+ self._entries: dict[str, CacheEntry] = {}
60
+ self.load()
61
+
62
+ logger.debug(
63
+ f"CacheStorage initialized (dir: {self.cache_dir}, "
64
+ f"max: {max_disk_mb}MB, entries: {len(self._entries)})"
65
+ )
66
+
67
+ def load(self) -> int:
68
+ """Load cache from disk into memory.
69
+
70
+ Returns:
71
+ Number of entries loaded.
72
+
73
+ """
74
+ if not self.cache_file.exists():
75
+ logger.debug("No cache file found, starting fresh")
76
+ return 0
77
+
78
+ try:
79
+ with open(self.cache_file) as f:
80
+ data = json.load(f)
81
+
82
+ version = data.get("version", "unknown")
83
+ entries_data = data.get("entries", [])
84
+
85
+ # Load entries
86
+ loaded = 0
87
+ current_time = time.time()
88
+
89
+ for entry_data in entries_data:
90
+ entry = CacheEntry(
91
+ key=entry_data["key"],
92
+ response=entry_data["response"],
93
+ workflow=entry_data["workflow"],
94
+ stage=entry_data["stage"],
95
+ model=entry_data["model"],
96
+ prompt_hash=entry_data["prompt_hash"],
97
+ timestamp=entry_data["timestamp"],
98
+ ttl=entry_data.get("ttl"),
99
+ )
100
+
101
+ # Skip expired entries
102
+ if entry.is_expired(current_time):
103
+ continue
104
+
105
+ self._entries[entry.key] = entry
106
+ loaded += 1
107
+
108
+ logger.info(
109
+ f"Loaded {loaded} cache entries from disk (version: {version}, "
110
+ f"skipped {len(entries_data) - loaded} expired)"
111
+ )
112
+ return loaded
113
+
114
+ except (json.JSONDecodeError, KeyError) as e:
115
+ logger.warning(f"Failed to load cache from disk: {e}")
116
+ return 0
117
+
118
+ def save(self) -> bool:
119
+ """Save cache to disk.
120
+
121
+ Returns:
122
+ True if saved successfully, False otherwise.
123
+
124
+ """
125
+ try:
126
+ # Prepare data
127
+ data = {
128
+ "version": "3.8.0",
129
+ "timestamp": time.time(),
130
+ "entries": [
131
+ {
132
+ "key": entry.key,
133
+ "response": entry.response,
134
+ "workflow": entry.workflow,
135
+ "stage": entry.stage,
136
+ "model": entry.model,
137
+ "prompt_hash": entry.prompt_hash,
138
+ "timestamp": entry.timestamp,
139
+ "ttl": entry.ttl,
140
+ }
141
+ for entry in self._entries.values()
142
+ ],
143
+ }
144
+
145
+ # Write to disk
146
+ with open(self.cache_file, "w") as f:
147
+ json.dump(data, f, indent=2)
148
+
149
+ logger.debug(f"Saved {len(self._entries)} cache entries to disk")
150
+ return True
151
+
152
+ except (OSError, TypeError) as e:
153
+ logger.error(f"Failed to save cache to disk: {e}")
154
+ return False
155
+
156
+ def get(self, cache_key: str) -> CacheEntry | None:
157
+ """Get entry from storage.
158
+
159
+ Args:
160
+ cache_key: Cache key to lookup.
161
+
162
+ Returns:
163
+ CacheEntry if found and not expired, None otherwise.
164
+
165
+ """
166
+ if cache_key not in self._entries:
167
+ return None
168
+
169
+ entry = self._entries[cache_key]
170
+
171
+ # Check expiration
172
+ if entry.is_expired(time.time()):
173
+ del self._entries[cache_key]
174
+ return None
175
+
176
+ return entry
177
+
178
+ def put(self, entry: CacheEntry) -> None:
179
+ """Store entry in storage.
180
+
181
+ Args:
182
+ entry: CacheEntry to store.
183
+
184
+ """
185
+ self._entries[entry.key] = entry
186
+
187
+ # Auto-save to disk if enabled
188
+ if self.auto_save:
189
+ self.save()
190
+
191
+ def delete(self, cache_key: str) -> bool:
192
+ """Delete entry from storage.
193
+
194
+ Args:
195
+ cache_key: Key to delete.
196
+
197
+ Returns:
198
+ True if entry was deleted, False if not found.
199
+
200
+ """
201
+ if cache_key in self._entries:
202
+ del self._entries[cache_key]
203
+ if self.auto_save:
204
+ self.save()
205
+ return True
206
+ return False
207
+
208
+ def clear(self) -> int:
209
+ """Clear all entries.
210
+
211
+ Returns:
212
+ Number of entries cleared.
213
+
214
+ """
215
+ count = len(self._entries)
216
+ self._entries.clear()
217
+
218
+ if self.auto_save:
219
+ self.save()
220
+
221
+ return count
222
+
223
+ def evict_expired(self) -> int:
224
+ """Remove all expired entries.
225
+
226
+ Returns:
227
+ Number of entries evicted.
228
+
229
+ """
230
+ current_time = time.time()
231
+ expired_keys = [
232
+ key for key, entry in self._entries.items() if entry.is_expired(current_time)
233
+ ]
234
+
235
+ for key in expired_keys:
236
+ del self._entries[key]
237
+
238
+ if expired_keys and self.auto_save:
239
+ self.save()
240
+
241
+ return len(expired_keys)
242
+
243
+ def get_all(self) -> list[CacheEntry]:
244
+ """Get all non-expired entries.
245
+
246
+ Returns:
247
+ List of CacheEntry objects.
248
+
249
+ """
250
+ current_time = time.time()
251
+ return [entry for entry in self._entries.values() if not entry.is_expired(current_time)]
252
+
253
+ def size_mb(self) -> float:
254
+ """Estimate cache size in MB.
255
+
256
+ Returns:
257
+ Estimated size in megabytes.
258
+
259
+ """
260
+ if not self.cache_file.exists():
261
+ return 0.0
262
+
263
+ return self.cache_file.stat().st_size / (1024 * 1024)
264
+
265
+ def stats(self) -> dict[str, Any]:
266
+ """Get storage statistics.
267
+
268
+ Returns:
269
+ Dictionary with storage metrics.
270
+
271
+ """
272
+ current_time = time.time()
273
+ expired = sum(1 for entry in self._entries.values() if entry.is_expired(current_time))
274
+
275
+ return {
276
+ "total_entries": len(self._entries),
277
+ "expired_entries": expired,
278
+ "active_entries": len(self._entries) - expired,
279
+ "disk_size_mb": round(self.size_mb(), 2),
280
+ "max_disk_mb": self.max_disk_mb,
281
+ "cache_dir": str(self.cache_dir),
282
+ }