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.
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.0.dist-info}/METADATA +7 -6
- empathy_framework-4.7.0.dist-info/RECORD +354 -0
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.0.dist-info}/top_level.txt +0 -2
- empathy_healthcare_plugin/monitors/monitoring/__init__.py +9 -9
- empathy_llm_toolkit/agent_factory/__init__.py +6 -6
- empathy_llm_toolkit/agent_factory/adapters/wizard_adapter.py +7 -10
- empathy_llm_toolkit/agents_md/__init__.py +22 -0
- empathy_llm_toolkit/agents_md/loader.py +218 -0
- empathy_llm_toolkit/agents_md/parser.py +271 -0
- empathy_llm_toolkit/agents_md/registry.py +307 -0
- empathy_llm_toolkit/commands/__init__.py +51 -0
- empathy_llm_toolkit/commands/context.py +375 -0
- empathy_llm_toolkit/commands/loader.py +301 -0
- empathy_llm_toolkit/commands/models.py +231 -0
- empathy_llm_toolkit/commands/parser.py +371 -0
- empathy_llm_toolkit/commands/registry.py +429 -0
- empathy_llm_toolkit/config/__init__.py +8 -8
- empathy_llm_toolkit/config/unified.py +3 -7
- empathy_llm_toolkit/context/__init__.py +22 -0
- empathy_llm_toolkit/context/compaction.py +455 -0
- empathy_llm_toolkit/context/manager.py +434 -0
- empathy_llm_toolkit/hooks/__init__.py +24 -0
- empathy_llm_toolkit/hooks/config.py +306 -0
- empathy_llm_toolkit/hooks/executor.py +289 -0
- empathy_llm_toolkit/hooks/registry.py +302 -0
- empathy_llm_toolkit/hooks/scripts/__init__.py +39 -0
- empathy_llm_toolkit/hooks/scripts/evaluate_session.py +201 -0
- empathy_llm_toolkit/hooks/scripts/first_time_init.py +285 -0
- empathy_llm_toolkit/hooks/scripts/pre_compact.py +207 -0
- empathy_llm_toolkit/hooks/scripts/session_end.py +183 -0
- empathy_llm_toolkit/hooks/scripts/session_start.py +163 -0
- empathy_llm_toolkit/hooks/scripts/suggest_compact.py +225 -0
- empathy_llm_toolkit/learning/__init__.py +30 -0
- empathy_llm_toolkit/learning/evaluator.py +438 -0
- empathy_llm_toolkit/learning/extractor.py +514 -0
- empathy_llm_toolkit/learning/storage.py +560 -0
- empathy_llm_toolkit/providers.py +4 -11
- empathy_llm_toolkit/security/__init__.py +17 -17
- empathy_llm_toolkit/utils/tokens.py +2 -5
- empathy_os/__init__.py +202 -70
- empathy_os/cache_monitor.py +5 -3
- empathy_os/cli/__init__.py +11 -55
- empathy_os/cli/__main__.py +29 -15
- empathy_os/cli/commands/inspection.py +21 -12
- empathy_os/cli/commands/memory.py +4 -12
- empathy_os/cli/commands/profiling.py +198 -0
- empathy_os/cli/commands/utilities.py +27 -7
- empathy_os/cli.py +28 -57
- empathy_os/cli_unified.py +525 -1164
- empathy_os/cost_tracker.py +9 -3
- empathy_os/dashboard/server.py +200 -2
- empathy_os/hot_reload/__init__.py +7 -7
- empathy_os/hot_reload/config.py +6 -7
- empathy_os/hot_reload/integration.py +35 -35
- empathy_os/hot_reload/reloader.py +57 -57
- empathy_os/hot_reload/watcher.py +28 -28
- empathy_os/hot_reload/websocket.py +2 -2
- empathy_os/memory/__init__.py +11 -4
- empathy_os/memory/claude_memory.py +1 -1
- empathy_os/memory/cross_session.py +8 -12
- empathy_os/memory/edges.py +6 -6
- empathy_os/memory/file_session.py +770 -0
- empathy_os/memory/graph.py +30 -30
- empathy_os/memory/nodes.py +6 -6
- empathy_os/memory/short_term.py +15 -9
- empathy_os/memory/unified.py +606 -140
- empathy_os/meta_workflows/agent_creator.py +3 -9
- empathy_os/meta_workflows/cli_meta_workflows.py +113 -53
- empathy_os/meta_workflows/form_engine.py +6 -18
- empathy_os/meta_workflows/intent_detector.py +64 -24
- empathy_os/meta_workflows/models.py +3 -1
- empathy_os/meta_workflows/pattern_learner.py +13 -31
- empathy_os/meta_workflows/plan_generator.py +55 -47
- empathy_os/meta_workflows/session_context.py +2 -3
- empathy_os/meta_workflows/workflow.py +20 -51
- empathy_os/models/cli.py +2 -2
- empathy_os/models/tasks.py +1 -2
- empathy_os/models/telemetry.py +4 -1
- empathy_os/models/token_estimator.py +3 -1
- empathy_os/monitoring/alerts.py +938 -9
- empathy_os/monitoring/alerts_cli.py +346 -183
- empathy_os/orchestration/execution_strategies.py +12 -29
- empathy_os/orchestration/pattern_learner.py +20 -26
- empathy_os/orchestration/real_tools.py +6 -15
- empathy_os/platform_utils.py +2 -1
- empathy_os/plugins/__init__.py +2 -2
- empathy_os/plugins/base.py +64 -64
- empathy_os/plugins/registry.py +32 -32
- empathy_os/project_index/index.py +49 -15
- empathy_os/project_index/models.py +1 -2
- empathy_os/project_index/reports.py +1 -1
- empathy_os/project_index/scanner.py +1 -0
- empathy_os/redis_memory.py +10 -7
- empathy_os/resilience/__init__.py +1 -1
- empathy_os/resilience/health.py +10 -10
- empathy_os/routing/__init__.py +7 -7
- empathy_os/routing/chain_executor.py +37 -37
- empathy_os/routing/classifier.py +36 -36
- empathy_os/routing/smart_router.py +40 -40
- empathy_os/routing/{wizard_registry.py → workflow_registry.py} +47 -47
- empathy_os/scaffolding/__init__.py +8 -8
- empathy_os/scaffolding/__main__.py +1 -1
- empathy_os/scaffolding/cli.py +28 -28
- empathy_os/socratic/__init__.py +3 -19
- empathy_os/socratic/ab_testing.py +25 -36
- empathy_os/socratic/blueprint.py +38 -38
- empathy_os/socratic/cli.py +34 -20
- empathy_os/socratic/collaboration.py +30 -28
- empathy_os/socratic/domain_templates.py +9 -1
- empathy_os/socratic/embeddings.py +17 -13
- empathy_os/socratic/engine.py +135 -70
- empathy_os/socratic/explainer.py +70 -60
- empathy_os/socratic/feedback.py +24 -19
- empathy_os/socratic/forms.py +15 -10
- empathy_os/socratic/generator.py +51 -35
- empathy_os/socratic/llm_analyzer.py +25 -23
- empathy_os/socratic/mcp_server.py +99 -159
- empathy_os/socratic/session.py +19 -13
- empathy_os/socratic/storage.py +98 -67
- empathy_os/socratic/success.py +38 -27
- empathy_os/socratic/visual_editor.py +51 -39
- empathy_os/socratic/web_ui.py +99 -66
- empathy_os/telemetry/cli.py +3 -1
- empathy_os/telemetry/usage_tracker.py +1 -3
- empathy_os/test_generator/__init__.py +3 -3
- empathy_os/test_generator/cli.py +28 -28
- empathy_os/test_generator/generator.py +64 -66
- empathy_os/test_generator/risk_analyzer.py +11 -11
- empathy_os/vscode_bridge.py +173 -0
- empathy_os/workflows/__init__.py +212 -120
- empathy_os/workflows/batch_processing.py +8 -24
- empathy_os/workflows/bug_predict.py +1 -1
- empathy_os/workflows/code_review.py +20 -5
- empathy_os/workflows/code_review_pipeline.py +13 -8
- empathy_os/workflows/keyboard_shortcuts/workflow.py +6 -2
- empathy_os/workflows/manage_documentation.py +1 -0
- empathy_os/workflows/orchestrated_health_check.py +6 -11
- empathy_os/workflows/orchestrated_release_prep.py +3 -3
- empathy_os/workflows/pr_review.py +18 -10
- empathy_os/workflows/progressive/__init__.py +2 -12
- empathy_os/workflows/progressive/cli.py +14 -37
- empathy_os/workflows/progressive/core.py +12 -12
- empathy_os/workflows/progressive/orchestrator.py +166 -144
- empathy_os/workflows/progressive/reports.py +22 -31
- empathy_os/workflows/progressive/telemetry.py +8 -14
- empathy_os/workflows/progressive/test_gen.py +29 -48
- empathy_os/workflows/progressive/workflow.py +31 -70
- empathy_os/workflows/release_prep.py +21 -6
- empathy_os/workflows/release_prep_crew.py +1 -0
- empathy_os/workflows/secure_release.py +13 -6
- empathy_os/workflows/security_audit.py +8 -3
- empathy_os/workflows/test_coverage_boost_crew.py +3 -2
- empathy_os/workflows/test_maintenance_crew.py +1 -0
- empathy_os/workflows/test_runner.py +16 -12
- empathy_software_plugin/SOFTWARE_PLUGIN_README.md +25 -703
- empathy_software_plugin/cli.py +0 -122
- coach_wizards/__init__.py +0 -45
- coach_wizards/accessibility_wizard.py +0 -91
- coach_wizards/api_wizard.py +0 -91
- coach_wizards/base_wizard.py +0 -209
- coach_wizards/cicd_wizard.py +0 -91
- coach_wizards/code_reviewer_README.md +0 -60
- coach_wizards/code_reviewer_wizard.py +0 -180
- coach_wizards/compliance_wizard.py +0 -91
- coach_wizards/database_wizard.py +0 -91
- coach_wizards/debugging_wizard.py +0 -91
- coach_wizards/documentation_wizard.py +0 -91
- coach_wizards/generate_wizards.py +0 -347
- coach_wizards/localization_wizard.py +0 -173
- coach_wizards/migration_wizard.py +0 -91
- coach_wizards/monitoring_wizard.py +0 -91
- coach_wizards/observability_wizard.py +0 -91
- coach_wizards/performance_wizard.py +0 -91
- coach_wizards/prompt_engineering_wizard.py +0 -661
- coach_wizards/refactoring_wizard.py +0 -91
- coach_wizards/scaling_wizard.py +0 -90
- coach_wizards/security_wizard.py +0 -92
- coach_wizards/testing_wizard.py +0 -91
- empathy_framework-4.6.6.dist-info/RECORD +0 -410
- empathy_llm_toolkit/wizards/__init__.py +0 -43
- empathy_llm_toolkit/wizards/base_wizard.py +0 -364
- empathy_llm_toolkit/wizards/customer_support_wizard.py +0 -190
- empathy_llm_toolkit/wizards/healthcare_wizard.py +0 -378
- empathy_llm_toolkit/wizards/patient_assessment_README.md +0 -64
- empathy_llm_toolkit/wizards/patient_assessment_wizard.py +0 -193
- empathy_llm_toolkit/wizards/technology_wizard.py +0 -209
- empathy_os/wizard_factory_cli.py +0 -170
- empathy_software_plugin/wizards/__init__.py +0 -42
- empathy_software_plugin/wizards/advanced_debugging_wizard.py +0 -395
- empathy_software_plugin/wizards/agent_orchestration_wizard.py +0 -511
- empathy_software_plugin/wizards/ai_collaboration_wizard.py +0 -503
- empathy_software_plugin/wizards/ai_context_wizard.py +0 -441
- empathy_software_plugin/wizards/ai_documentation_wizard.py +0 -503
- empathy_software_plugin/wizards/base_wizard.py +0 -288
- empathy_software_plugin/wizards/book_chapter_wizard.py +0 -519
- empathy_software_plugin/wizards/code_review_wizard.py +0 -604
- empathy_software_plugin/wizards/debugging/__init__.py +0 -50
- empathy_software_plugin/wizards/debugging/bug_risk_analyzer.py +0 -414
- empathy_software_plugin/wizards/debugging/config_loaders.py +0 -446
- empathy_software_plugin/wizards/debugging/fix_applier.py +0 -469
- empathy_software_plugin/wizards/debugging/language_patterns.py +0 -385
- empathy_software_plugin/wizards/debugging/linter_parsers.py +0 -470
- empathy_software_plugin/wizards/debugging/verification.py +0 -369
- empathy_software_plugin/wizards/enhanced_testing_wizard.py +0 -537
- empathy_software_plugin/wizards/memory_enhanced_debugging_wizard.py +0 -816
- empathy_software_plugin/wizards/multi_model_wizard.py +0 -501
- empathy_software_plugin/wizards/pattern_extraction_wizard.py +0 -422
- empathy_software_plugin/wizards/pattern_retriever_wizard.py +0 -400
- empathy_software_plugin/wizards/performance/__init__.py +0 -9
- empathy_software_plugin/wizards/performance/bottleneck_detector.py +0 -221
- empathy_software_plugin/wizards/performance/profiler_parsers.py +0 -278
- empathy_software_plugin/wizards/performance/trajectory_analyzer.py +0 -429
- empathy_software_plugin/wizards/performance_profiling_wizard.py +0 -305
- empathy_software_plugin/wizards/prompt_engineering_wizard.py +0 -425
- empathy_software_plugin/wizards/rag_pattern_wizard.py +0 -461
- empathy_software_plugin/wizards/security/__init__.py +0 -32
- empathy_software_plugin/wizards/security/exploit_analyzer.py +0 -290
- empathy_software_plugin/wizards/security/owasp_patterns.py +0 -241
- empathy_software_plugin/wizards/security/vulnerability_scanner.py +0 -604
- empathy_software_plugin/wizards/security_analysis_wizard.py +0 -322
- empathy_software_plugin/wizards/security_learning_wizard.py +0 -740
- empathy_software_plugin/wizards/tech_debt_wizard.py +0 -726
- empathy_software_plugin/wizards/testing/__init__.py +0 -27
- empathy_software_plugin/wizards/testing/coverage_analyzer.py +0 -459
- empathy_software_plugin/wizards/testing/quality_analyzer.py +0 -525
- empathy_software_plugin/wizards/testing/test_suggester.py +0 -533
- empathy_software_plugin/wizards/testing_wizard.py +0 -274
- wizards/__init__.py +0 -82
- wizards/admission_assessment_wizard.py +0 -644
- wizards/care_plan.py +0 -321
- wizards/clinical_assessment.py +0 -769
- wizards/discharge_planning.py +0 -77
- wizards/discharge_summary_wizard.py +0 -468
- wizards/dosage_calculation.py +0 -497
- wizards/incident_report_wizard.py +0 -454
- wizards/medication_reconciliation.py +0 -85
- wizards/nursing_assessment.py +0 -171
- wizards/patient_education.py +0 -654
- wizards/quality_improvement.py +0 -705
- wizards/sbar_report.py +0 -324
- wizards/sbar_wizard.py +0 -608
- wizards/shift_handoff_wizard.py +0 -535
- wizards/soap_note_wizard.py +0 -679
- wizards/treatment_plan.py +0 -15
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.0.dist-info}/WHEEL +0 -0
- {empathy_framework-4.6.6.dist-info → empathy_framework-4.7.0.dist-info}/entry_points.txt +0 -0
- {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
|