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,375 @@
1
+ """Command Context
2
+
3
+ Provides execution context for commands with access to hooks, context management,
4
+ and learning modules.
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 logging
17
+ import time
18
+ from dataclasses import dataclass, field
19
+ from pathlib import Path
20
+ from typing import TYPE_CHECKING, Any
21
+
22
+ from empathy_llm_toolkit.commands.models import CommandConfig, CommandResult
23
+
24
+ if TYPE_CHECKING:
25
+ from empathy_llm_toolkit.context.manager import ContextManager
26
+ from empathy_llm_toolkit.hooks.config import HookEvent
27
+ from empathy_llm_toolkit.hooks.registry import HookRegistry
28
+ from empathy_llm_toolkit.learning.storage import LearnedSkillsStorage
29
+ from empathy_llm_toolkit.state import CollaborationState
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ @dataclass
35
+ class CommandContext:
36
+ """Execution context for commands.
37
+
38
+ Provides access to framework components that commands may need:
39
+ - Hook registry for firing events
40
+ - Context manager for state preservation
41
+ - Learning storage for pattern access
42
+ - Collaboration state for user context
43
+
44
+ Example:
45
+ # Create context with all components
46
+ ctx = CommandContext(
47
+ user_id="user123",
48
+ hook_registry=hooks,
49
+ context_manager=context_mgr,
50
+ learning_storage=storage,
51
+ collaboration_state=state,
52
+ )
53
+
54
+ # Commands can access components
55
+ patterns = ctx.get_patterns_for_context()
56
+ ctx.fire_hook("PreCommand", {"command": "compact"})
57
+ """
58
+
59
+ user_id: str
60
+ hook_registry: HookRegistry | None = None
61
+ context_manager: ContextManager | None = None
62
+ learning_storage: LearnedSkillsStorage | None = None
63
+ collaboration_state: CollaborationState | None = None
64
+ project_root: Path | None = None
65
+ extra: dict[str, Any] = field(default_factory=dict)
66
+
67
+ def fire_hook(
68
+ self,
69
+ event: str | HookEvent,
70
+ context: dict[str, Any] | None = None,
71
+ ) -> list[dict[str, Any]]:
72
+ """Fire a hook event.
73
+
74
+ Args:
75
+ event: Event type (string or HookEvent enum)
76
+ context: Additional context for the hook
77
+
78
+ Returns:
79
+ List of hook execution results
80
+
81
+ """
82
+ if self.hook_registry is None:
83
+ logger.debug("No hook registry available, skipping hook")
84
+ return []
85
+
86
+ from empathy_llm_toolkit.hooks.config import HookEvent
87
+
88
+ # Convert string to enum if needed
89
+ if isinstance(event, str):
90
+ try:
91
+ event = HookEvent(event)
92
+ except ValueError:
93
+ logger.warning("Unknown hook event: %s", event)
94
+ return []
95
+
96
+ hook_context = context or {}
97
+ hook_context["user_id"] = self.user_id
98
+
99
+ return self.hook_registry.fire_sync(event, hook_context)
100
+
101
+ def save_context_state(self) -> Path | None:
102
+ """Save current collaboration state for compaction.
103
+
104
+ Returns:
105
+ Path to saved state file or None
106
+
107
+ """
108
+ if self.context_manager is None:
109
+ logger.debug("No context manager available")
110
+ return None
111
+
112
+ if self.collaboration_state is None:
113
+ logger.debug("No collaboration state available")
114
+ return None
115
+
116
+ return self.context_manager.save_for_compaction(self.collaboration_state)
117
+
118
+ def restore_context_state(self) -> bool:
119
+ """Restore context state for user.
120
+
121
+ Returns:
122
+ True if state was restored
123
+
124
+ """
125
+ if self.context_manager is None:
126
+ logger.debug("No context manager available")
127
+ return False
128
+
129
+ state = self.context_manager.restore_state(self.user_id)
130
+ return state is not None
131
+
132
+ def get_patterns_for_context(
133
+ self,
134
+ max_patterns: int = 5,
135
+ ) -> str:
136
+ """Get learned patterns formatted for context injection.
137
+
138
+ Args:
139
+ max_patterns: Maximum patterns to include
140
+
141
+ Returns:
142
+ Formatted markdown string
143
+
144
+ """
145
+ if self.learning_storage is None:
146
+ return ""
147
+
148
+ return self.learning_storage.format_patterns_for_context(
149
+ self.user_id,
150
+ max_patterns=max_patterns,
151
+ )
152
+
153
+ def search_patterns(self, query: str) -> list[Any]:
154
+ """Search learned patterns.
155
+
156
+ Args:
157
+ query: Search query
158
+
159
+ Returns:
160
+ List of matching patterns
161
+
162
+ """
163
+ if self.learning_storage is None:
164
+ return []
165
+
166
+ return self.learning_storage.search_patterns(self.user_id, query)
167
+
168
+ def get_learning_summary(self) -> dict[str, Any]:
169
+ """Get learning summary for user.
170
+
171
+ Returns:
172
+ Summary dictionary
173
+
174
+ """
175
+ if self.learning_storage is None:
176
+ return {}
177
+
178
+ return self.learning_storage.get_summary(self.user_id)
179
+
180
+
181
+ class CommandExecutor:
182
+ """Executes commands with proper hook integration.
183
+
184
+ Handles the lifecycle of command execution:
185
+ 1. Fire PreCommand hook
186
+ 2. Execute command logic
187
+ 3. Fire PostCommand hook
188
+ 4. Return result
189
+
190
+ Example:
191
+ executor = CommandExecutor(context)
192
+
193
+ # Execute a command
194
+ result = await executor.execute(compact_command)
195
+
196
+ # Check result
197
+ if result.success:
198
+ print(f"Output: {result.output}")
199
+ """
200
+
201
+ def __init__(self, context: CommandContext):
202
+ """Initialize the executor.
203
+
204
+ Args:
205
+ context: Command execution context
206
+
207
+ """
208
+ self.context = context
209
+
210
+ def execute(
211
+ self,
212
+ command: CommandConfig,
213
+ args: dict[str, Any] | None = None,
214
+ ) -> CommandResult:
215
+ """Execute a command.
216
+
217
+ Args:
218
+ command: Command configuration to execute
219
+ args: Additional arguments
220
+
221
+ Returns:
222
+ CommandResult with execution details
223
+
224
+ """
225
+ args = args or {}
226
+ start_time = time.time()
227
+ hooks_fired: list[str] = []
228
+ patterns_applied: list[str] = []
229
+
230
+ # Fire pre-command hook if configured
231
+ pre_hook = command.hooks.get("pre")
232
+ if pre_hook:
233
+ try:
234
+ self.context.fire_hook(
235
+ pre_hook,
236
+ {
237
+ "command": command.name,
238
+ "args": args,
239
+ },
240
+ )
241
+ hooks_fired.append(f"pre:{pre_hook}")
242
+ logger.debug("Pre-command hook fired: %s", pre_hook)
243
+ except Exception as e:
244
+ logger.error("Pre-command hook failed: %s", e)
245
+
246
+ # Get relevant patterns
247
+ if self.context.learning_storage:
248
+ patterns = self.context.search_patterns(command.name)
249
+ patterns_applied = [p.pattern_id for p in patterns[:3]]
250
+
251
+ # The actual command execution happens in Claude
252
+ # This executor prepares the context and returns the command body
253
+ output = command.body
254
+
255
+ # Fire post-command hook if configured
256
+ post_hook = command.hooks.get("post")
257
+ if post_hook:
258
+ try:
259
+ self.context.fire_hook(
260
+ post_hook,
261
+ {
262
+ "command": command.name,
263
+ "args": args,
264
+ "success": True,
265
+ },
266
+ )
267
+ hooks_fired.append(f"post:{post_hook}")
268
+ logger.debug("Post-command hook fired: %s", post_hook)
269
+ except Exception as e:
270
+ logger.error("Post-command hook failed: %s", e)
271
+
272
+ duration_ms = (time.time() - start_time) * 1000
273
+
274
+ return CommandResult(
275
+ command_name=command.name,
276
+ success=True,
277
+ output=output,
278
+ duration_ms=duration_ms,
279
+ hooks_fired=hooks_fired,
280
+ patterns_applied=patterns_applied,
281
+ )
282
+
283
+ def prepare_command(
284
+ self,
285
+ command: CommandConfig,
286
+ args: dict[str, Any] | None = None,
287
+ ) -> dict[str, Any]:
288
+ """Prepare command for execution without running hooks.
289
+
290
+ Useful for getting command context before actual execution.
291
+
292
+ Args:
293
+ command: Command configuration
294
+ args: Additional arguments
295
+
296
+ Returns:
297
+ Dictionary with prepared command context
298
+
299
+ """
300
+ args = args or {}
301
+
302
+ # Get relevant patterns
303
+ patterns_context = ""
304
+ if self.context.learning_storage:
305
+ patterns_context = self.context.get_patterns_for_context(max_patterns=3)
306
+
307
+ return {
308
+ "command": command.name,
309
+ "description": command.description,
310
+ "body": command.body,
311
+ "args": args,
312
+ "patterns_context": patterns_context,
313
+ "user_id": self.context.user_id,
314
+ "has_hooks": bool(command.hooks),
315
+ }
316
+
317
+
318
+ def create_command_context(
319
+ user_id: str,
320
+ project_root: str | Path | None = None,
321
+ enable_hooks: bool = True,
322
+ enable_learning: bool = True,
323
+ enable_context: bool = True,
324
+ ) -> CommandContext:
325
+ """Create a CommandContext with available components.
326
+
327
+ Factory function that creates a context with the requested
328
+ components, handling import errors gracefully.
329
+
330
+ Args:
331
+ user_id: User identifier
332
+ project_root: Project root directory
333
+ enable_hooks: Enable hook integration
334
+ enable_learning: Enable learning integration
335
+ enable_context: Enable context management
336
+
337
+ Returns:
338
+ Configured CommandContext
339
+
340
+ """
341
+ hook_registry = None
342
+ context_manager = None
343
+ learning_storage = None
344
+
345
+ if enable_hooks:
346
+ try:
347
+ from empathy_llm_toolkit.hooks.registry import HookRegistry
348
+
349
+ hook_registry = HookRegistry()
350
+ except ImportError:
351
+ logger.debug("Hooks module not available")
352
+
353
+ if enable_context:
354
+ try:
355
+ from empathy_llm_toolkit.context.manager import ContextManager
356
+
357
+ context_manager = ContextManager()
358
+ except ImportError:
359
+ logger.debug("Context module not available")
360
+
361
+ if enable_learning:
362
+ try:
363
+ from empathy_llm_toolkit.learning.storage import LearnedSkillsStorage
364
+
365
+ learning_storage = LearnedSkillsStorage()
366
+ except ImportError:
367
+ logger.debug("Learning module not available")
368
+
369
+ return CommandContext(
370
+ user_id=user_id,
371
+ hook_registry=hook_registry,
372
+ context_manager=context_manager,
373
+ learning_storage=learning_storage,
374
+ project_root=Path(project_root) if project_root else None,
375
+ )
@@ -0,0 +1,301 @@
1
+ """Command Loader
2
+
3
+ Loads commands from directory structures.
4
+
5
+ Architectural patterns inspired by everything-claude-code by Affaan Mustafa.
6
+ See: https://github.com/affaan-m/everything-claude-code (MIT License)
7
+ See: ACKNOWLEDGMENTS.md for full attribution.
8
+
9
+ Copyright 2025 Smart AI Memory, LLC
10
+ Licensed under Fair Source 0.9
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import logging
16
+ from collections.abc import Iterator
17
+ from pathlib import Path
18
+
19
+ from empathy_llm_toolkit.commands.models import CommandConfig
20
+ from empathy_llm_toolkit.commands.parser import CommandParser
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # Default commands directory relative to project root
25
+ DEFAULT_COMMANDS_DIR = ".claude/commands"
26
+
27
+ # Files to skip when scanning
28
+ SKIP_FILES = frozenset(
29
+ {
30
+ "README.md",
31
+ "readme.md",
32
+ "CHANGELOG.md",
33
+ "changelog.md",
34
+ "INDEX.md",
35
+ "index.md",
36
+ }
37
+ )
38
+
39
+
40
+ class CommandLoader:
41
+ """Loader for discovering and loading command markdown files.
42
+
43
+ Scans directories for .md files with command definitions and loads
44
+ them into CommandConfig instances.
45
+
46
+ Example:
47
+ loader = CommandLoader()
48
+
49
+ # Load a single command
50
+ config = loader.load(".claude/commands/commit.md")
51
+
52
+ # Load all commands from a directory
53
+ commands = loader.load_directory(".claude/commands/")
54
+
55
+ # Discover and iterate commands lazily
56
+ for config in loader.discover(".claude/commands/"):
57
+ print(config.name)
58
+ """
59
+
60
+ def __init__(self, parser: CommandParser | None = None):
61
+ """Initialize the loader.
62
+
63
+ Args:
64
+ parser: Optional custom parser instance
65
+
66
+ """
67
+ self.parser = parser or CommandParser()
68
+
69
+ def load(self, file_path: str | Path) -> CommandConfig:
70
+ """Load a single command file.
71
+
72
+ Args:
73
+ file_path: Path to the command markdown file
74
+
75
+ Returns:
76
+ CommandConfig instance
77
+
78
+ """
79
+ return self.parser.parse_file(file_path)
80
+
81
+ def load_directory(
82
+ self,
83
+ directory: str | Path,
84
+ recursive: bool = False,
85
+ ) -> dict[str, CommandConfig]:
86
+ """Load all commands from a directory.
87
+
88
+ Args:
89
+ directory: Directory to scan for .md files
90
+ recursive: If True, scan subdirectories
91
+
92
+ Returns:
93
+ Dictionary mapping command names to configs
94
+
95
+ """
96
+ commands: dict[str, CommandConfig] = {}
97
+
98
+ for config in self.discover(directory, recursive=recursive):
99
+ if config.name in commands:
100
+ logger.warning(
101
+ "Duplicate command name '%s' - keeping first occurrence",
102
+ config.name,
103
+ )
104
+ continue
105
+ commands[config.name] = config
106
+
107
+ logger.info("Loaded %d command(s) from %s", len(commands), directory)
108
+ return commands
109
+
110
+ def discover(
111
+ self,
112
+ directory: str | Path,
113
+ recursive: bool = False,
114
+ ) -> Iterator[CommandConfig]:
115
+ """Discover and yield commands from a directory.
116
+
117
+ Args:
118
+ directory: Directory to scan
119
+ recursive: If True, scan subdirectories
120
+
121
+ Yields:
122
+ CommandConfig instances
123
+
124
+ """
125
+ directory = Path(directory)
126
+
127
+ if not directory.exists():
128
+ logger.warning("Commands directory not found: %s", directory)
129
+ return
130
+
131
+ if not directory.is_dir():
132
+ raise ValueError(f"Not a directory: {directory}")
133
+
134
+ # Get pattern for globbing
135
+ pattern = "**/*.md" if recursive else "*.md"
136
+
137
+ for file_path in sorted(directory.glob(pattern)):
138
+ if not file_path.is_file():
139
+ continue
140
+
141
+ # Skip non-command files
142
+ if file_path.name in SKIP_FILES:
143
+ continue
144
+ if file_path.name.startswith("_"):
145
+ continue
146
+ if file_path.name.startswith("."):
147
+ continue
148
+
149
+ try:
150
+ config = self.parser.parse_file(file_path)
151
+ yield config
152
+ except ValueError as e:
153
+ logger.warning("Skipping invalid command file %s: %s", file_path, e)
154
+ except FileNotFoundError as e:
155
+ logger.warning("Command file not found %s: %s", file_path, e)
156
+ except OSError as e:
157
+ logger.error("Error loading command file %s: %s", file_path, e)
158
+
159
+ def validate_directory(
160
+ self,
161
+ directory: str | Path,
162
+ recursive: bool = False,
163
+ ) -> dict[str, list[str]]:
164
+ """Validate all command files in a directory.
165
+
166
+ Args:
167
+ directory: Directory to validate
168
+ recursive: If True, scan subdirectories
169
+
170
+ Returns:
171
+ Dictionary mapping file paths to lists of errors
172
+
173
+ """
174
+ directory = Path(directory)
175
+ results: dict[str, list[str]] = {}
176
+
177
+ if not directory.exists():
178
+ return {str(directory): ["Directory not found"]}
179
+
180
+ pattern = "**/*.md" if recursive else "*.md"
181
+
182
+ for file_path in sorted(directory.glob(pattern)):
183
+ if not file_path.is_file():
184
+ continue
185
+ if file_path.name in SKIP_FILES:
186
+ continue
187
+ if file_path.name.startswith("_"):
188
+ continue
189
+
190
+ errors = self.parser.validate_file(file_path)
191
+ if errors:
192
+ results[str(file_path)] = errors
193
+
194
+ return results
195
+
196
+ def get_command_names(
197
+ self,
198
+ directory: str | Path,
199
+ recursive: bool = False,
200
+ ) -> list[str]:
201
+ """Get list of command names in a directory without fully loading.
202
+
203
+ Args:
204
+ directory: Directory to scan
205
+ recursive: If True, scan subdirectories
206
+
207
+ Returns:
208
+ List of command names
209
+
210
+ """
211
+ names: list[str] = []
212
+ for config in self.discover(directory, recursive=recursive):
213
+ names.append(config.name)
214
+ return names
215
+
216
+ def find_command_file(
217
+ self,
218
+ name: str,
219
+ directory: str | Path,
220
+ ) -> Path | None:
221
+ """Find a command file by name.
222
+
223
+ Args:
224
+ name: Command name to find
225
+ directory: Directory to search
226
+
227
+ Returns:
228
+ Path to command file or None
229
+
230
+ """
231
+ directory = Path(directory)
232
+
233
+ # Try exact match first
234
+ exact_path = directory / f"{name}.md"
235
+ if exact_path.exists():
236
+ return exact_path
237
+
238
+ # Search through files
239
+ for file_path in directory.glob("*.md"):
240
+ if file_path.stem == name:
241
+ return file_path
242
+
243
+ return None
244
+
245
+
246
+ def load_commands_from_paths(
247
+ paths: list[str | Path],
248
+ parser: CommandParser | None = None,
249
+ ) -> dict[str, CommandConfig]:
250
+ """Load commands from multiple paths (files or directories).
251
+
252
+ Args:
253
+ paths: List of file or directory paths
254
+ parser: Optional custom parser
255
+
256
+ Returns:
257
+ Dictionary mapping command names to configs
258
+
259
+ """
260
+ loader = CommandLoader(parser=parser)
261
+ commands: dict[str, CommandConfig] = {}
262
+
263
+ for path in paths:
264
+ path = Path(path)
265
+
266
+ if path.is_file():
267
+ config = loader.load(path)
268
+ commands[config.name] = config
269
+ elif path.is_dir():
270
+ dir_commands = loader.load_directory(path)
271
+ commands.update(dir_commands)
272
+ else:
273
+ logger.warning("Path not found: %s", path)
274
+
275
+ return commands
276
+
277
+
278
+ def get_default_commands_directory() -> Path:
279
+ """Get the default commands directory.
280
+
281
+ Searches for .claude/commands/ starting from current directory
282
+ and walking up to find project root.
283
+
284
+ Returns:
285
+ Path to commands directory
286
+
287
+ """
288
+ current = Path.cwd()
289
+
290
+ # Walk up looking for .claude/commands/
291
+ for parent in [current, *current.parents]:
292
+ commands_dir = parent / DEFAULT_COMMANDS_DIR
293
+ if commands_dir.exists():
294
+ return commands_dir
295
+
296
+ # Also check for .claude directory as project root indicator
297
+ if (parent / ".claude").exists():
298
+ return commands_dir
299
+
300
+ # Fall back to current directory
301
+ return current / DEFAULT_COMMANDS_DIR