empathy-framework 4.6.6__py3-none-any.whl → 4.7.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. empathy_framework-4.7.1.dist-info/METADATA +690 -0
  2. empathy_framework-4.7.1.dist-info/RECORD +379 -0
  3. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/top_level.txt +1 -2
  4. empathy_healthcare_plugin/monitors/monitoring/__init__.py +9 -9
  5. empathy_llm_toolkit/agent_factory/__init__.py +6 -6
  6. empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +7 -10
  7. empathy_llm_toolkit/agents_md/__init__.py +22 -0
  8. empathy_llm_toolkit/agents_md/loader.py +218 -0
  9. empathy_llm_toolkit/agents_md/parser.py +271 -0
  10. empathy_llm_toolkit/agents_md/registry.py +307 -0
  11. empathy_llm_toolkit/commands/__init__.py +51 -0
  12. empathy_llm_toolkit/commands/context.py +375 -0
  13. empathy_llm_toolkit/commands/loader.py +301 -0
  14. empathy_llm_toolkit/commands/models.py +231 -0
  15. empathy_llm_toolkit/commands/parser.py +371 -0
  16. empathy_llm_toolkit/commands/registry.py +429 -0
  17. empathy_llm_toolkit/config/__init__.py +8 -8
  18. empathy_llm_toolkit/config/unified.py +3 -7
  19. empathy_llm_toolkit/context/__init__.py +22 -0
  20. empathy_llm_toolkit/context/compaction.py +455 -0
  21. empathy_llm_toolkit/context/manager.py +434 -0
  22. empathy_llm_toolkit/hooks/__init__.py +24 -0
  23. empathy_llm_toolkit/hooks/config.py +306 -0
  24. empathy_llm_toolkit/hooks/executor.py +289 -0
  25. empathy_llm_toolkit/hooks/registry.py +302 -0
  26. empathy_llm_toolkit/hooks/scripts/__init__.py +39 -0
  27. empathy_llm_toolkit/hooks/scripts/evaluate_session.py +201 -0
  28. empathy_llm_toolkit/hooks/scripts/first_time_init.py +285 -0
  29. empathy_llm_toolkit/hooks/scripts/pre_compact.py +207 -0
  30. empathy_llm_toolkit/hooks/scripts/session_end.py +183 -0
  31. empathy_llm_toolkit/hooks/scripts/session_start.py +163 -0
  32. empathy_llm_toolkit/hooks/scripts/suggest_compact.py +225 -0
  33. empathy_llm_toolkit/learning/__init__.py +30 -0
  34. empathy_llm_toolkit/learning/evaluator.py +438 -0
  35. empathy_llm_toolkit/learning/extractor.py +514 -0
  36. empathy_llm_toolkit/learning/storage.py +560 -0
  37. empathy_llm_toolkit/providers.py +4 -11
  38. empathy_llm_toolkit/security/__init__.py +17 -17
  39. empathy_llm_toolkit/utils/tokens.py +2 -5
  40. empathy_os/__init__.py +202 -70
  41. empathy_os/cache_monitor.py +5 -3
  42. empathy_os/cli/__init__.py +11 -55
  43. empathy_os/cli/__main__.py +29 -15
  44. empathy_os/cli/commands/inspection.py +21 -12
  45. empathy_os/cli/commands/memory.py +4 -12
  46. empathy_os/cli/commands/profiling.py +198 -0
  47. empathy_os/cli/commands/utilities.py +27 -7
  48. empathy_os/cli.py +28 -57
  49. empathy_os/cli_unified.py +525 -1164
  50. empathy_os/cost_tracker.py +9 -3
  51. empathy_os/dashboard/server.py +200 -2
  52. empathy_os/hot_reload/__init__.py +7 -7
  53. empathy_os/hot_reload/config.py +6 -7
  54. empathy_os/hot_reload/integration.py +35 -35
  55. empathy_os/hot_reload/reloader.py +57 -57
  56. empathy_os/hot_reload/watcher.py +28 -28
  57. empathy_os/hot_reload/websocket.py +2 -2
  58. empathy_os/memory/__init__.py +11 -4
  59. empathy_os/memory/claude_memory.py +1 -1
  60. empathy_os/memory/cross_session.py +8 -12
  61. empathy_os/memory/edges.py +6 -6
  62. empathy_os/memory/file_session.py +770 -0
  63. empathy_os/memory/graph.py +30 -30
  64. empathy_os/memory/nodes.py +6 -6
  65. empathy_os/memory/short_term.py +15 -9
  66. empathy_os/memory/unified.py +606 -140
  67. empathy_os/meta_workflows/agent_creator.py +3 -9
  68. empathy_os/meta_workflows/cli_meta_workflows.py +113 -53
  69. empathy_os/meta_workflows/form_engine.py +6 -18
  70. empathy_os/meta_workflows/intent_detector.py +64 -24
  71. empathy_os/meta_workflows/models.py +3 -1
  72. empathy_os/meta_workflows/pattern_learner.py +13 -31
  73. empathy_os/meta_workflows/plan_generator.py +55 -47
  74. empathy_os/meta_workflows/session_context.py +2 -3
  75. empathy_os/meta_workflows/workflow.py +20 -51
  76. empathy_os/models/cli.py +2 -2
  77. empathy_os/models/tasks.py +1 -2
  78. empathy_os/models/telemetry.py +4 -1
  79. empathy_os/models/token_estimator.py +3 -1
  80. empathy_os/monitoring/alerts.py +938 -9
  81. empathy_os/monitoring/alerts_cli.py +346 -183
  82. empathy_os/orchestration/execution_strategies.py +12 -29
  83. empathy_os/orchestration/pattern_learner.py +20 -26
  84. empathy_os/orchestration/real_tools.py +6 -15
  85. empathy_os/platform_utils.py +2 -1
  86. empathy_os/plugins/__init__.py +2 -2
  87. empathy_os/plugins/base.py +64 -64
  88. empathy_os/plugins/registry.py +32 -32
  89. empathy_os/project_index/index.py +49 -15
  90. empathy_os/project_index/models.py +1 -2
  91. empathy_os/project_index/reports.py +1 -1
  92. empathy_os/project_index/scanner.py +1 -0
  93. empathy_os/redis_memory.py +10 -7
  94. empathy_os/resilience/__init__.py +1 -1
  95. empathy_os/resilience/health.py +10 -10
  96. empathy_os/routing/__init__.py +7 -7
  97. empathy_os/routing/chain_executor.py +37 -37
  98. empathy_os/routing/classifier.py +36 -36
  99. empathy_os/routing/smart_router.py +40 -40
  100. empathy_os/routing/{wizard_registry.py → workflow_registry.py} +47 -47
  101. empathy_os/scaffolding/__init__.py +8 -8
  102. empathy_os/scaffolding/__main__.py +1 -1
  103. empathy_os/scaffolding/cli.py +28 -28
  104. empathy_os/socratic/__init__.py +3 -19
  105. empathy_os/socratic/ab_testing.py +25 -36
  106. empathy_os/socratic/blueprint.py +38 -38
  107. empathy_os/socratic/cli.py +34 -20
  108. empathy_os/socratic/collaboration.py +30 -28
  109. empathy_os/socratic/domain_templates.py +9 -1
  110. empathy_os/socratic/embeddings.py +17 -13
  111. empathy_os/socratic/engine.py +135 -70
  112. empathy_os/socratic/explainer.py +70 -60
  113. empathy_os/socratic/feedback.py +24 -19
  114. empathy_os/socratic/forms.py +15 -10
  115. empathy_os/socratic/generator.py +51 -35
  116. empathy_os/socratic/llm_analyzer.py +25 -23
  117. empathy_os/socratic/mcp_server.py +99 -159
  118. empathy_os/socratic/session.py +19 -13
  119. empathy_os/socratic/storage.py +98 -67
  120. empathy_os/socratic/success.py +38 -27
  121. empathy_os/socratic/visual_editor.py +51 -39
  122. empathy_os/socratic/web_ui.py +99 -66
  123. empathy_os/telemetry/cli.py +3 -1
  124. empathy_os/telemetry/usage_tracker.py +1 -3
  125. empathy_os/test_generator/__init__.py +3 -3
  126. empathy_os/test_generator/cli.py +28 -28
  127. empathy_os/test_generator/generator.py +64 -66
  128. empathy_os/test_generator/risk_analyzer.py +11 -11
  129. empathy_os/vscode_bridge 2.py +173 -0
  130. empathy_os/vscode_bridge.py +173 -0
  131. empathy_os/workflows/__init__.py +212 -120
  132. empathy_os/workflows/batch_processing.py +8 -24
  133. empathy_os/workflows/bug_predict.py +1 -1
  134. empathy_os/workflows/code_review.py +20 -5
  135. empathy_os/workflows/code_review_pipeline.py +13 -8
  136. empathy_os/workflows/keyboard_shortcuts/workflow.py +6 -2
  137. empathy_os/workflows/manage_documentation.py +1 -0
  138. empathy_os/workflows/orchestrated_health_check.py +6 -11
  139. empathy_os/workflows/orchestrated_release_prep.py +3 -3
  140. empathy_os/workflows/pr_review.py +18 -10
  141. empathy_os/workflows/progressive/README 2.md +454 -0
  142. empathy_os/workflows/progressive/__init__ 2.py +92 -0
  143. empathy_os/workflows/progressive/__init__.py +2 -12
  144. empathy_os/workflows/progressive/cli 2.py +242 -0
  145. empathy_os/workflows/progressive/cli.py +14 -37
  146. empathy_os/workflows/progressive/core 2.py +488 -0
  147. empathy_os/workflows/progressive/core.py +12 -12
  148. empathy_os/workflows/progressive/orchestrator 2.py +701 -0
  149. empathy_os/workflows/progressive/orchestrator.py +166 -144
  150. empathy_os/workflows/progressive/reports 2.py +528 -0
  151. empathy_os/workflows/progressive/reports.py +22 -31
  152. empathy_os/workflows/progressive/telemetry 2.py +280 -0
  153. empathy_os/workflows/progressive/telemetry.py +8 -14
  154. empathy_os/workflows/progressive/test_gen 2.py +514 -0
  155. empathy_os/workflows/progressive/test_gen.py +29 -48
  156. empathy_os/workflows/progressive/workflow 2.py +628 -0
  157. empathy_os/workflows/progressive/workflow.py +31 -70
  158. empathy_os/workflows/release_prep.py +21 -6
  159. empathy_os/workflows/release_prep_crew.py +1 -0
  160. empathy_os/workflows/secure_release.py +13 -6
  161. empathy_os/workflows/security_audit.py +8 -3
  162. empathy_os/workflows/test_coverage_boost_crew.py +3 -2
  163. empathy_os/workflows/test_maintenance_crew.py +1 -0
  164. empathy_os/workflows/test_runner.py +16 -12
  165. empathy_software_plugin/SOFTWARE_PLUGIN_README.md +25 -703
  166. empathy_software_plugin/cli.py +0 -122
  167. patterns/README.md +119 -0
  168. patterns/__init__.py +95 -0
  169. patterns/behavior.py +298 -0
  170. patterns/code_review_memory.json +441 -0
  171. patterns/core.py +97 -0
  172. patterns/debugging.json +3763 -0
  173. patterns/empathy.py +268 -0
  174. patterns/health_check_memory.json +505 -0
  175. patterns/input.py +161 -0
  176. patterns/memory_graph.json +8 -0
  177. patterns/refactoring_memory.json +1113 -0
  178. patterns/registry.py +663 -0
  179. patterns/security_memory.json +8 -0
  180. patterns/structural.py +415 -0
  181. patterns/validation.py +194 -0
  182. coach_wizards/__init__.py +0 -45
  183. coach_wizards/accessibility_wizard.py +0 -91
  184. coach_wizards/api_wizard.py +0 -91
  185. coach_wizards/base_wizard.py +0 -209
  186. coach_wizards/cicd_wizard.py +0 -91
  187. coach_wizards/code_reviewer_README.md +0 -60
  188. coach_wizards/code_reviewer_wizard.py +0 -180
  189. coach_wizards/compliance_wizard.py +0 -91
  190. coach_wizards/database_wizard.py +0 -91
  191. coach_wizards/debugging_wizard.py +0 -91
  192. coach_wizards/documentation_wizard.py +0 -91
  193. coach_wizards/generate_wizards.py +0 -347
  194. coach_wizards/localization_wizard.py +0 -173
  195. coach_wizards/migration_wizard.py +0 -91
  196. coach_wizards/monitoring_wizard.py +0 -91
  197. coach_wizards/observability_wizard.py +0 -91
  198. coach_wizards/performance_wizard.py +0 -91
  199. coach_wizards/prompt_engineering_wizard.py +0 -661
  200. coach_wizards/refactoring_wizard.py +0 -91
  201. coach_wizards/scaling_wizard.py +0 -90
  202. coach_wizards/security_wizard.py +0 -92
  203. coach_wizards/testing_wizard.py +0 -91
  204. empathy_framework-4.6.6.dist-info/METADATA +0 -1597
  205. empathy_framework-4.6.6.dist-info/RECORD +0 -410
  206. empathy_llm_toolkit/wizards/__init__.py +0 -43
  207. empathy_llm_toolkit/wizards/base_wizard.py +0 -364
  208. empathy_llm_toolkit/wizards/customer_support_wizard.py +0 -190
  209. empathy_llm_toolkit/wizards/healthcare_wizard.py +0 -378
  210. empathy_llm_toolkit/wizards/patient_assessment_README.md +0 -64
  211. empathy_llm_toolkit/wizards/patient_assessment_wizard.py +0 -193
  212. empathy_llm_toolkit/wizards/technology_wizard.py +0 -209
  213. empathy_os/wizard_factory_cli.py +0 -170
  214. empathy_software_plugin/wizards/__init__.py +0 -42
  215. empathy_software_plugin/wizards/advanced_debugging_wizard.py +0 -395
  216. empathy_software_plugin/wizards/agent_orchestration_wizard.py +0 -511
  217. empathy_software_plugin/wizards/ai_collaboration_wizard.py +0 -503
  218. empathy_software_plugin/wizards/ai_context_wizard.py +0 -441
  219. empathy_software_plugin/wizards/ai_documentation_wizard.py +0 -503
  220. empathy_software_plugin/wizards/base_wizard.py +0 -288
  221. empathy_software_plugin/wizards/book_chapter_wizard.py +0 -519
  222. empathy_software_plugin/wizards/code_review_wizard.py +0 -604
  223. empathy_software_plugin/wizards/debugging/__init__.py +0 -50
  224. empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +0 -414
  225. empathy_software_plugin/wizards/debugging/config_loaders.py +0 -446
  226. empathy_software_plugin/wizards/debugging/fix_applier.py +0 -469
  227. empathy_software_plugin/wizards/debugging/language_patterns.py +0 -385
  228. empathy_software_plugin/wizards/debugging/linter_parsers.py +0 -470
  229. empathy_software_plugin/wizards/debugging/verification.py +0 -369
  230. empathy_software_plugin/wizards/enhanced_testing_wizard.py +0 -537
  231. empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +0 -816
  232. empathy_software_plugin/wizards/multi_model_wizard.py +0 -501
  233. empathy_software_plugin/wizards/pattern_extraction_wizard.py +0 -422
  234. empathy_software_plugin/wizards/pattern_retriever_wizard.py +0 -400
  235. empathy_software_plugin/wizards/performance/__init__.py +0 -9
  236. empathy_software_plugin/wizards/performance/bottleneck_detector.py +0 -221
  237. empathy_software_plugin/wizards/performance/profiler_parsers.py +0 -278
  238. empathy_software_plugin/wizards/performance/trajectory_analyzer.py +0 -429
  239. empathy_software_plugin/wizards/performance_profiling_wizard.py +0 -305
  240. empathy_software_plugin/wizards/prompt_engineering_wizard.py +0 -425
  241. empathy_software_plugin/wizards/rag_pattern_wizard.py +0 -461
  242. empathy_software_plugin/wizards/security/__init__.py +0 -32
  243. empathy_software_plugin/wizards/security/exploit_analyzer.py +0 -290
  244. empathy_software_plugin/wizards/security/owasp_patterns.py +0 -241
  245. empathy_software_plugin/wizards/security/vulnerability_scanner.py +0 -604
  246. empathy_software_plugin/wizards/security_analysis_wizard.py +0 -322
  247. empathy_software_plugin/wizards/security_learning_wizard.py +0 -740
  248. empathy_software_plugin/wizards/tech_debt_wizard.py +0 -726
  249. empathy_software_plugin/wizards/testing/__init__.py +0 -27
  250. empathy_software_plugin/wizards/testing/coverage_analyzer.py +0 -459
  251. empathy_software_plugin/wizards/testing/quality_analyzer.py +0 -525
  252. empathy_software_plugin/wizards/testing/test_suggester.py +0 -533
  253. empathy_software_plugin/wizards/testing_wizard.py +0 -274
  254. wizards/__init__.py +0 -82
  255. wizards/admission_assessment_wizard.py +0 -644
  256. wizards/care_plan.py +0 -321
  257. wizards/clinical_assessment.py +0 -769
  258. wizards/discharge_planning.py +0 -77
  259. wizards/discharge_summary_wizard.py +0 -468
  260. wizards/dosage_calculation.py +0 -497
  261. wizards/incident_report_wizard.py +0 -454
  262. wizards/medication_reconciliation.py +0 -85
  263. wizards/nursing_assessment.py +0 -171
  264. wizards/patient_education.py +0 -654
  265. wizards/quality_improvement.py +0 -705
  266. wizards/sbar_report.py +0 -324
  267. wizards/sbar_wizard.py +0 -608
  268. wizards/shift_handoff_wizard.py +0 -535
  269. wizards/soap_note_wizard.py +0 -679
  270. wizards/treatment_plan.py +0 -15
  271. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/WHEEL +0 -0
  272. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/entry_points.txt +0 -0
  273. {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,560 @@
1
+ """Learned Skills Storage for Continuous Learning
2
+
3
+ Persists and retrieves learned patterns and skills.
4
+ Provides storage for patterns extracted from sessions.
5
+
6
+ Architectural patterns inspired by everything-claude-code by Affaan Mustafa.
7
+ See: https://github.com/affaan-m/everything-claude-code (MIT License)
8
+ See: ACKNOWLEDGMENTS.md for full attribution.
9
+
10
+ Copyright 2025 Smart AI Memory, LLC
11
+ Licensed under Fair Source 0.9
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import logging
18
+ from dataclasses import dataclass, field
19
+ from datetime import datetime
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ from empathy_llm_toolkit.learning.extractor import ExtractedPattern, PatternCategory
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ @dataclass
29
+ class LearnedSkill:
30
+ """A skill learned from pattern aggregation.
31
+
32
+ Skills are higher-level learnings derived from
33
+ multiple related patterns.
34
+ """
35
+
36
+ skill_id: str
37
+ name: str
38
+ description: str
39
+ category: PatternCategory
40
+ patterns: list[str] # Pattern IDs
41
+ confidence: float
42
+ usage_count: int = 0
43
+ last_used: datetime | None = None
44
+ created_at: datetime = field(default_factory=datetime.now)
45
+ tags: list[str] = field(default_factory=list)
46
+ metadata: dict[str, Any] = field(default_factory=dict)
47
+
48
+ def to_dict(self) -> dict[str, Any]:
49
+ """Convert to dictionary."""
50
+ return {
51
+ "skill_id": self.skill_id,
52
+ "name": self.name,
53
+ "description": self.description,
54
+ "category": self.category.value,
55
+ "patterns": self.patterns,
56
+ "confidence": self.confidence,
57
+ "usage_count": self.usage_count,
58
+ "last_used": self.last_used.isoformat() if self.last_used else None,
59
+ "created_at": self.created_at.isoformat(),
60
+ "tags": self.tags,
61
+ "metadata": self.metadata,
62
+ }
63
+
64
+ @classmethod
65
+ def from_dict(cls, data: dict[str, Any]) -> LearnedSkill:
66
+ """Create from dictionary."""
67
+ return cls(
68
+ skill_id=data["skill_id"],
69
+ name=data["name"],
70
+ description=data["description"],
71
+ category=PatternCategory(data["category"]),
72
+ patterns=data.get("patterns", []),
73
+ confidence=data.get("confidence", 0.5),
74
+ usage_count=data.get("usage_count", 0),
75
+ last_used=datetime.fromisoformat(data["last_used"]) if data.get("last_used") else None,
76
+ created_at=datetime.fromisoformat(data["created_at"])
77
+ if "created_at" in data
78
+ else datetime.now(),
79
+ tags=data.get("tags", []),
80
+ metadata=data.get("metadata", {}),
81
+ )
82
+
83
+
84
+ class LearnedSkillsStorage:
85
+ """Manages storage of learned patterns and skills.
86
+
87
+ Provides persistence and retrieval for patterns extracted
88
+ from collaboration sessions, with support for querying
89
+ and filtering.
90
+ """
91
+
92
+ def __init__(
93
+ self,
94
+ storage_dir: str | Path = ".empathy/learned_skills",
95
+ max_patterns_per_user: int = 100,
96
+ max_skills_per_user: int = 50,
97
+ ):
98
+ """Initialize the storage.
99
+
100
+ Args:
101
+ storage_dir: Directory for storage files
102
+ max_patterns_per_user: Maximum patterns to store per user
103
+ max_skills_per_user: Maximum skills to store per user
104
+ """
105
+ self.storage_dir = Path(storage_dir)
106
+ self._max_patterns = max_patterns_per_user
107
+ self._max_skills = max_skills_per_user
108
+
109
+ def _ensure_storage(self) -> None:
110
+ """Ensure storage directory exists."""
111
+ self.storage_dir.mkdir(parents=True, exist_ok=True)
112
+
113
+ def _get_user_dir(self, user_id: str) -> Path:
114
+ """Get storage directory for a user."""
115
+ safe_id = "".join(c if c.isalnum() or c in "-_" else "_" for c in user_id)
116
+ return self.storage_dir / safe_id
117
+
118
+ def _get_patterns_file(self, user_id: str) -> Path:
119
+ """Get patterns file path for a user."""
120
+ return self._get_user_dir(user_id) / "patterns.json"
121
+
122
+ def _get_skills_file(self, user_id: str) -> Path:
123
+ """Get skills file path for a user."""
124
+ return self._get_user_dir(user_id) / "skills.json"
125
+
126
+ # Pattern operations
127
+
128
+ def save_pattern(
129
+ self,
130
+ user_id: str,
131
+ pattern: ExtractedPattern,
132
+ ) -> str:
133
+ """Save a pattern for a user.
134
+
135
+ Args:
136
+ user_id: User identifier
137
+ pattern: Pattern to save
138
+
139
+ Returns:
140
+ Pattern ID
141
+ """
142
+ self._ensure_storage()
143
+ user_dir = self._get_user_dir(user_id)
144
+ user_dir.mkdir(parents=True, exist_ok=True)
145
+
146
+ patterns = self._load_patterns(user_id)
147
+
148
+ # Check for duplicate
149
+ existing_ids = {p["pattern_id"] for p in patterns}
150
+ if pattern.pattern_id in existing_ids:
151
+ logger.debug(f"Pattern {pattern.pattern_id} already exists, updating")
152
+ patterns = [p for p in patterns if p["pattern_id"] != pattern.pattern_id]
153
+
154
+ patterns.append(pattern.to_dict())
155
+
156
+ # Enforce limit (remove oldest)
157
+ if len(patterns) > self._max_patterns:
158
+ patterns = sorted(
159
+ patterns,
160
+ key=lambda p: p.get("extracted_at", ""),
161
+ reverse=True,
162
+ )[: self._max_patterns]
163
+
164
+ self._save_patterns(user_id, patterns)
165
+ logger.info(f"Saved pattern {pattern.pattern_id} for user {user_id}")
166
+
167
+ return pattern.pattern_id
168
+
169
+ def save_patterns(
170
+ self,
171
+ user_id: str,
172
+ patterns: list[ExtractedPattern],
173
+ ) -> list[str]:
174
+ """Save multiple patterns.
175
+
176
+ Args:
177
+ user_id: User identifier
178
+ patterns: Patterns to save
179
+
180
+ Returns:
181
+ List of saved pattern IDs
182
+ """
183
+ return [self.save_pattern(user_id, p) for p in patterns]
184
+
185
+ def get_pattern(
186
+ self,
187
+ user_id: str,
188
+ pattern_id: str,
189
+ ) -> ExtractedPattern | None:
190
+ """Get a specific pattern.
191
+
192
+ Args:
193
+ user_id: User identifier
194
+ pattern_id: Pattern identifier
195
+
196
+ Returns:
197
+ Pattern or None if not found
198
+ """
199
+ patterns = self._load_patterns(user_id)
200
+
201
+ for p in patterns:
202
+ if p.get("pattern_id") == pattern_id:
203
+ return ExtractedPattern.from_dict(p)
204
+
205
+ return None
206
+
207
+ def get_all_patterns(self, user_id: str) -> list[ExtractedPattern]:
208
+ """Get all patterns for a user.
209
+
210
+ Args:
211
+ user_id: User identifier
212
+
213
+ Returns:
214
+ List of patterns
215
+ """
216
+ patterns = self._load_patterns(user_id)
217
+ return [ExtractedPattern.from_dict(p) for p in patterns]
218
+
219
+ def get_patterns_by_category(
220
+ self,
221
+ user_id: str,
222
+ category: PatternCategory,
223
+ ) -> list[ExtractedPattern]:
224
+ """Get patterns by category.
225
+
226
+ Args:
227
+ user_id: User identifier
228
+ category: Pattern category
229
+
230
+ Returns:
231
+ List of matching patterns
232
+ """
233
+ all_patterns = self.get_all_patterns(user_id)
234
+ return [p for p in all_patterns if p.category == category]
235
+
236
+ def get_patterns_by_tag(
237
+ self,
238
+ user_id: str,
239
+ tag: str,
240
+ ) -> list[ExtractedPattern]:
241
+ """Get patterns by tag.
242
+
243
+ Args:
244
+ user_id: User identifier
245
+ tag: Tag to filter by
246
+
247
+ Returns:
248
+ List of matching patterns
249
+ """
250
+ all_patterns = self.get_all_patterns(user_id)
251
+ return [p for p in all_patterns if tag in p.tags]
252
+
253
+ def search_patterns(
254
+ self,
255
+ user_id: str,
256
+ query: str,
257
+ ) -> list[ExtractedPattern]:
258
+ """Search patterns by trigger or context.
259
+
260
+ Args:
261
+ user_id: User identifier
262
+ query: Search query
263
+
264
+ Returns:
265
+ List of matching patterns
266
+ """
267
+ all_patterns = self.get_all_patterns(user_id)
268
+ query_lower = query.lower()
269
+
270
+ return [
271
+ p
272
+ for p in all_patterns
273
+ if query_lower in p.trigger.lower()
274
+ or query_lower in p.context.lower()
275
+ or query_lower in p.resolution.lower()
276
+ ]
277
+
278
+ def delete_pattern(self, user_id: str, pattern_id: str) -> bool:
279
+ """Delete a pattern.
280
+
281
+ Args:
282
+ user_id: User identifier
283
+ pattern_id: Pattern to delete
284
+
285
+ Returns:
286
+ True if deleted
287
+ """
288
+ patterns = self._load_patterns(user_id)
289
+ original_count = len(patterns)
290
+
291
+ patterns = [p for p in patterns if p.get("pattern_id") != pattern_id]
292
+
293
+ if len(patterns) < original_count:
294
+ self._save_patterns(user_id, patterns)
295
+ return True
296
+
297
+ return False
298
+
299
+ def _load_patterns(self, user_id: str) -> list[dict[str, Any]]:
300
+ """Load patterns from storage."""
301
+ patterns_file = self._get_patterns_file(user_id)
302
+
303
+ if not patterns_file.exists():
304
+ return []
305
+
306
+ try:
307
+ with open(patterns_file, encoding="utf-8") as f:
308
+ return json.load(f)
309
+ except (json.JSONDecodeError, OSError) as e:
310
+ logger.error(f"Failed to load patterns: {e}")
311
+ return []
312
+
313
+ def _save_patterns(
314
+ self,
315
+ user_id: str,
316
+ patterns: list[dict[str, Any]],
317
+ ) -> None:
318
+ """Save patterns to storage."""
319
+ patterns_file = self._get_patterns_file(user_id)
320
+
321
+ with open(patterns_file, "w", encoding="utf-8") as f:
322
+ json.dump(patterns, f, indent=2, default=str)
323
+
324
+ # Skill operations
325
+
326
+ def save_skill(
327
+ self,
328
+ user_id: str,
329
+ skill: LearnedSkill,
330
+ ) -> str:
331
+ """Save a learned skill.
332
+
333
+ Args:
334
+ user_id: User identifier
335
+ skill: Skill to save
336
+
337
+ Returns:
338
+ Skill ID
339
+ """
340
+ self._ensure_storage()
341
+ user_dir = self._get_user_dir(user_id)
342
+ user_dir.mkdir(parents=True, exist_ok=True)
343
+
344
+ skills = self._load_skills(user_id)
345
+
346
+ # Check for duplicate
347
+ existing_ids = {s["skill_id"] for s in skills}
348
+ if skill.skill_id in existing_ids:
349
+ skills = [s for s in skills if s["skill_id"] != skill.skill_id]
350
+
351
+ skills.append(skill.to_dict())
352
+
353
+ # Enforce limit
354
+ if len(skills) > self._max_skills:
355
+ skills = sorted(
356
+ skills,
357
+ key=lambda s: s.get("created_at", ""),
358
+ reverse=True,
359
+ )[: self._max_skills]
360
+
361
+ self._save_skills(user_id, skills)
362
+ return skill.skill_id
363
+
364
+ def get_skill(
365
+ self,
366
+ user_id: str,
367
+ skill_id: str,
368
+ ) -> LearnedSkill | None:
369
+ """Get a specific skill.
370
+
371
+ Args:
372
+ user_id: User identifier
373
+ skill_id: Skill identifier
374
+
375
+ Returns:
376
+ Skill or None
377
+ """
378
+ skills = self._load_skills(user_id)
379
+
380
+ for s in skills:
381
+ if s.get("skill_id") == skill_id:
382
+ return LearnedSkill.from_dict(s)
383
+
384
+ return None
385
+
386
+ def get_all_skills(self, user_id: str) -> list[LearnedSkill]:
387
+ """Get all skills for a user.
388
+
389
+ Args:
390
+ user_id: User identifier
391
+
392
+ Returns:
393
+ List of skills
394
+ """
395
+ skills = self._load_skills(user_id)
396
+ return [LearnedSkill.from_dict(s) for s in skills]
397
+
398
+ def record_skill_usage(
399
+ self,
400
+ user_id: str,
401
+ skill_id: str,
402
+ ) -> None:
403
+ """Record that a skill was used.
404
+
405
+ Args:
406
+ user_id: User identifier
407
+ skill_id: Skill that was used
408
+ """
409
+ skills = self._load_skills(user_id)
410
+
411
+ for s in skills:
412
+ if s.get("skill_id") == skill_id:
413
+ s["usage_count"] = s.get("usage_count", 0) + 1
414
+ s["last_used"] = datetime.now().isoformat()
415
+ break
416
+
417
+ self._save_skills(user_id, skills)
418
+
419
+ def delete_skill(self, user_id: str, skill_id: str) -> bool:
420
+ """Delete a skill.
421
+
422
+ Args:
423
+ user_id: User identifier
424
+ skill_id: Skill to delete
425
+
426
+ Returns:
427
+ True if deleted
428
+ """
429
+ skills = self._load_skills(user_id)
430
+ original_count = len(skills)
431
+
432
+ skills = [s for s in skills if s.get("skill_id") != skill_id]
433
+
434
+ if len(skills) < original_count:
435
+ self._save_skills(user_id, skills)
436
+ return True
437
+
438
+ return False
439
+
440
+ def _load_skills(self, user_id: str) -> list[dict[str, Any]]:
441
+ """Load skills from storage."""
442
+ skills_file = self._get_skills_file(user_id)
443
+
444
+ if not skills_file.exists():
445
+ return []
446
+
447
+ try:
448
+ with open(skills_file, encoding="utf-8") as f:
449
+ return json.load(f)
450
+ except (json.JSONDecodeError, OSError) as e:
451
+ logger.error(f"Failed to load skills: {e}")
452
+ return []
453
+
454
+ def _save_skills(
455
+ self,
456
+ user_id: str,
457
+ skills: list[dict[str, Any]],
458
+ ) -> None:
459
+ """Save skills to storage."""
460
+ skills_file = self._get_skills_file(user_id)
461
+
462
+ with open(skills_file, "w", encoding="utf-8") as f:
463
+ json.dump(skills, f, indent=2, default=str)
464
+
465
+ # Summary operations
466
+
467
+ def get_summary(self, user_id: str) -> dict[str, Any]:
468
+ """Get learning summary for a user.
469
+
470
+ Args:
471
+ user_id: User identifier
472
+
473
+ Returns:
474
+ Summary dictionary
475
+ """
476
+ patterns = self.get_all_patterns(user_id)
477
+ skills = self.get_all_skills(user_id)
478
+
479
+ # Count by category
480
+ category_counts: dict[str, int] = {}
481
+ for pattern in patterns:
482
+ cat = pattern.category.value
483
+ category_counts[cat] = category_counts.get(cat, 0) + 1
484
+
485
+ return {
486
+ "user_id": user_id,
487
+ "total_patterns": len(patterns),
488
+ "total_skills": len(skills),
489
+ "patterns_by_category": category_counts,
490
+ "avg_confidence": (
491
+ sum(p.confidence for p in patterns) / len(patterns) if patterns else 0.0
492
+ ),
493
+ "most_used_skill": (max(skills, key=lambda s: s.usage_count).name if skills else None),
494
+ }
495
+
496
+ def clear_user_data(self, user_id: str) -> int:
497
+ """Clear all data for a user.
498
+
499
+ Args:
500
+ user_id: User identifier
501
+
502
+ Returns:
503
+ Number of items cleared
504
+ """
505
+ user_dir = self._get_user_dir(user_id)
506
+ count = 0
507
+
508
+ if user_dir.exists():
509
+ for file in user_dir.glob("*.json"):
510
+ try:
511
+ # Count items before deleting
512
+ with open(file, encoding="utf-8") as f:
513
+ data = json.load(f)
514
+ count += len(data) if isinstance(data, list) else 1
515
+ file.unlink()
516
+ except (OSError, json.JSONDecodeError):
517
+ continue
518
+
519
+ try:
520
+ user_dir.rmdir()
521
+ except OSError:
522
+ pass
523
+
524
+ return count
525
+
526
+ def format_patterns_for_context(
527
+ self,
528
+ user_id: str,
529
+ max_patterns: int = 5,
530
+ categories: list[PatternCategory] | None = None,
531
+ ) -> str:
532
+ """Format patterns for injection into context.
533
+
534
+ Args:
535
+ user_id: User identifier
536
+ max_patterns: Maximum patterns to include
537
+ categories: Optional category filter
538
+
539
+ Returns:
540
+ Formatted markdown string
541
+ """
542
+ patterns = self.get_all_patterns(user_id)
543
+
544
+ if categories:
545
+ patterns = [p for p in patterns if p.category in categories]
546
+
547
+ # Sort by confidence
548
+ patterns = sorted(patterns, key=lambda p: p.confidence, reverse=True)
549
+ patterns = patterns[:max_patterns]
550
+
551
+ if not patterns:
552
+ return ""
553
+
554
+ lines = ["## Learned Patterns", ""]
555
+
556
+ for pattern in patterns:
557
+ lines.append(pattern.format_readable())
558
+ lines.append("")
559
+
560
+ return "\n".join(lines)
@@ -363,13 +363,10 @@ class AnthropicBatchProvider:
363
363
  self._batch_jobs: dict[str, Any] = {}
364
364
  except ImportError as e:
365
365
  raise ImportError(
366
- "anthropic package required for Batch API. "
367
- "Install with: pip install anthropic"
366
+ "anthropic package required for Batch API. Install with: pip install anthropic"
368
367
  ) from e
369
368
 
370
- def create_batch(
371
- self, requests: list[dict[str, Any]], job_id: str | None = None
372
- ) -> str:
369
+ def create_batch(self, requests: list[dict[str, Any]], job_id: str | None = None) -> str:
373
370
  """Create a batch job.
374
371
 
375
372
  Args:
@@ -454,9 +451,7 @@ class AnthropicBatchProvider:
454
451
  status = self.get_batch_status(batch_id)
455
452
 
456
453
  if status.status != "completed":
457
- raise ValueError(
458
- f"Batch {batch_id} not completed (status: {status.status})"
459
- )
454
+ raise ValueError(f"Batch {batch_id} not completed (status: {status.status})")
460
455
 
461
456
  try:
462
457
  results = self.client.batches.results(batch_id)
@@ -510,9 +505,7 @@ class AnthropicBatchProvider:
510
505
  # Check timeout
511
506
  elapsed = (datetime.now() - start_time).total_seconds()
512
507
  if elapsed > timeout:
513
- raise TimeoutError(
514
- f"Batch {batch_id} did not complete within {timeout}s"
515
- )
508
+ raise TimeoutError(f"Batch {batch_id} did not complete within {timeout}s")
516
509
 
517
510
  # Log progress
518
511
  logger.debug(f"Batch {batch_id} status: {status.status} (elapsed: {elapsed:.0f}s)")
@@ -16,25 +16,25 @@ License: Fair Source 0.9
16
16
 
17
17
  # Re-export from consolidated memory module for backwards compatibility
18
18
  from empathy_os.memory.long_term import (
19
- Classification,
20
- ClassificationRules,
21
- EncryptionManager,
22
- PatternMetadata,
23
- SecureMemDocsIntegration,
24
- SecurityError,
19
+ Classification,
20
+ ClassificationRules,
21
+ EncryptionManager,
22
+ PatternMetadata,
23
+ SecureMemDocsIntegration,
24
+ SecurityError,
25
25
  )
26
26
  from empathy_os.memory.security import (
27
- AuditEvent,
28
- AuditLogger,
29
- PIIDetection,
30
- PIIPattern,
31
- PIIScrubber,
32
- SecretDetection,
33
- SecretsDetector,
34
- SecretType,
35
- SecurityViolation,
36
- Severity,
37
- detect_secrets,
27
+ AuditEvent,
28
+ AuditLogger,
29
+ PIIDetection,
30
+ PIIPattern,
31
+ PIIScrubber,
32
+ SecretDetection,
33
+ SecretsDetector,
34
+ SecretType,
35
+ SecurityViolation,
36
+ Severity,
37
+ detect_secrets,
38
38
  )
39
39
 
40
40
  __all__ = [
@@ -23,8 +23,7 @@ def _get_client():
23
23
  _client = Anthropic()
24
24
  except ImportError as e:
25
25
  raise ImportError(
26
- "anthropic package required for token counting. "
27
- "Install with: pip install anthropic"
26
+ "anthropic package required for token counting. Install with: pip install anthropic"
28
27
  ) from e
29
28
  return _client
30
29
 
@@ -112,9 +111,7 @@ def count_message_tokens(
112
111
  return counts
113
112
 
114
113
 
115
- def estimate_cost(
116
- input_tokens: int, output_tokens: int, model: str = "claude-sonnet-4-5"
117
- ) -> float:
114
+ def estimate_cost(input_tokens: int, output_tokens: int, model: str = "claude-sonnet-4-5") -> float:
118
115
  """Estimate cost in USD based on token counts.
119
116
 
120
117
  Args: