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