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,231 @@
1
+ """Command Configuration Models
2
+
3
+ Data models for command definitions loaded from markdown files.
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
+ from dataclasses import dataclass, field
16
+ from datetime import datetime
17
+ from enum import Enum
18
+ from pathlib import Path
19
+ from typing import Any
20
+
21
+
22
+ class CommandCategory(str, Enum):
23
+ """Categories for commands."""
24
+
25
+ WORKFLOW = "workflow" # Multi-step workflows
26
+ GIT = "git" # Git operations
27
+ TEST = "test" # Testing related
28
+ DOCS = "docs" # Documentation
29
+ SECURITY = "security" # Security analysis
30
+ PERFORMANCE = "performance" # Performance tools
31
+ LEARNING = "learning" # Pattern learning
32
+ CONTEXT = "context" # Context management
33
+ UTILITY = "utility" # General utilities
34
+
35
+
36
+ @dataclass
37
+ class CommandMetadata:
38
+ """Metadata for a command extracted from YAML frontmatter.
39
+
40
+ Example frontmatter:
41
+ ---
42
+ name: compact
43
+ description: Strategic context compaction
44
+ category: context
45
+ aliases: [comp, save-state]
46
+ hooks:
47
+ pre: PreCompact
48
+ post: PostCompact
49
+ requires_user_id: true
50
+ ---
51
+ """
52
+
53
+ name: str
54
+ description: str = ""
55
+ category: CommandCategory = CommandCategory.UTILITY
56
+ aliases: list[str] = field(default_factory=list)
57
+ hooks: dict[str, str] = field(default_factory=dict)
58
+ requires_user_id: bool = False
59
+ requires_context: bool = False
60
+ tags: list[str] = field(default_factory=list)
61
+ author: str = ""
62
+ version: str = "1.0"
63
+
64
+ def to_dict(self) -> dict[str, Any]:
65
+ """Convert to dictionary."""
66
+ return {
67
+ "name": self.name,
68
+ "description": self.description,
69
+ "category": self.category.value,
70
+ "aliases": self.aliases,
71
+ "hooks": self.hooks,
72
+ "requires_user_id": self.requires_user_id,
73
+ "requires_context": self.requires_context,
74
+ "tags": self.tags,
75
+ "author": self.author,
76
+ "version": self.version,
77
+ }
78
+
79
+ @classmethod
80
+ def from_dict(cls, data: dict[str, Any]) -> CommandMetadata:
81
+ """Create from dictionary."""
82
+ category_str = data.get("category", "utility")
83
+ try:
84
+ category = CommandCategory(category_str)
85
+ except ValueError:
86
+ category = CommandCategory.UTILITY
87
+
88
+ return cls(
89
+ name=data.get("name", ""),
90
+ description=data.get("description", ""),
91
+ category=category,
92
+ aliases=data.get("aliases", []),
93
+ hooks=data.get("hooks", {}),
94
+ requires_user_id=data.get("requires_user_id", False),
95
+ requires_context=data.get("requires_context", False),
96
+ tags=data.get("tags", []),
97
+ author=data.get("author", ""),
98
+ version=data.get("version", "1.0"),
99
+ )
100
+
101
+
102
+ @dataclass
103
+ class CommandConfig:
104
+ """Complete configuration for a command.
105
+
106
+ Combines metadata from YAML frontmatter with the markdown body
107
+ that contains the command instructions.
108
+ """
109
+
110
+ name: str
111
+ description: str
112
+ body: str # Markdown content with instructions
113
+ metadata: CommandMetadata
114
+ source_file: Path | None = None
115
+ loaded_at: datetime = field(default_factory=datetime.now)
116
+
117
+ @property
118
+ def aliases(self) -> list[str]:
119
+ """Get command aliases."""
120
+ return self.metadata.aliases
121
+
122
+ @property
123
+ def category(self) -> CommandCategory:
124
+ """Get command category."""
125
+ return self.metadata.category
126
+
127
+ @property
128
+ def hooks(self) -> dict[str, str]:
129
+ """Get hook configuration."""
130
+ return self.metadata.hooks
131
+
132
+ def get_all_names(self) -> list[str]:
133
+ """Get command name and all aliases."""
134
+ return [self.name] + self.aliases
135
+
136
+ def to_dict(self) -> dict[str, Any]:
137
+ """Convert to dictionary."""
138
+ return {
139
+ "name": self.name,
140
+ "description": self.description,
141
+ "body": self.body,
142
+ "metadata": self.metadata.to_dict(),
143
+ "source_file": str(self.source_file) if self.source_file else None,
144
+ "loaded_at": self.loaded_at.isoformat(),
145
+ }
146
+
147
+ @classmethod
148
+ def from_dict(cls, data: dict[str, Any]) -> CommandConfig:
149
+ """Create from dictionary."""
150
+ return cls(
151
+ name=data["name"],
152
+ description=data.get("description", ""),
153
+ body=data.get("body", ""),
154
+ metadata=CommandMetadata.from_dict(data.get("metadata", {})),
155
+ source_file=Path(data["source_file"]) if data.get("source_file") else None,
156
+ loaded_at=datetime.fromisoformat(data["loaded_at"])
157
+ if "loaded_at" in data
158
+ else datetime.now(),
159
+ )
160
+
161
+ def format_for_display(self) -> str:
162
+ """Format command for display in help."""
163
+ aliases_str = ""
164
+ if self.aliases:
165
+ aliases_str = f" (aliases: {', '.join(self.aliases)})"
166
+
167
+ return f"/{self.name}{aliases_str} - {self.description}"
168
+
169
+ def format_full_help(self) -> str:
170
+ """Format full help including body."""
171
+ lines = [
172
+ f"# /{self.name}",
173
+ "",
174
+ self.description,
175
+ "",
176
+ ]
177
+
178
+ if self.aliases:
179
+ lines.extend(
180
+ [
181
+ "## Aliases",
182
+ ", ".join(f"/{a}" for a in self.aliases),
183
+ "",
184
+ ]
185
+ )
186
+
187
+ if self.metadata.tags:
188
+ lines.extend(
189
+ [
190
+ "## Tags",
191
+ ", ".join(self.metadata.tags),
192
+ "",
193
+ ]
194
+ )
195
+
196
+ lines.extend(
197
+ [
198
+ "## Instructions",
199
+ "",
200
+ self.body,
201
+ ]
202
+ )
203
+
204
+ return "\n".join(lines)
205
+
206
+
207
+ @dataclass
208
+ class CommandResult:
209
+ """Result from executing a command."""
210
+
211
+ command_name: str
212
+ success: bool
213
+ output: str = ""
214
+ error: str | None = None
215
+ duration_ms: float = 0.0
216
+ hooks_fired: list[str] = field(default_factory=list)
217
+ context_saved: bool = False
218
+ patterns_applied: list[str] = field(default_factory=list)
219
+
220
+ def to_dict(self) -> dict[str, Any]:
221
+ """Convert to dictionary."""
222
+ return {
223
+ "command_name": self.command_name,
224
+ "success": self.success,
225
+ "output": self.output,
226
+ "error": self.error,
227
+ "duration_ms": self.duration_ms,
228
+ "hooks_fired": self.hooks_fired,
229
+ "context_saved": self.context_saved,
230
+ "patterns_applied": self.patterns_applied,
231
+ }
@@ -0,0 +1,371 @@
1
+ """Command Markdown Parser
2
+
3
+ Parses command markdown files with optional YAML frontmatter.
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
+ import re
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ from empathy_llm_toolkit.commands.models import CommandCategory, CommandConfig, CommandMetadata
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # YAML frontmatter regex pattern (matches --- at start)
25
+ FRONTMATTER_PATTERN = re.compile(
26
+ r"^---\s*\n(.*?)\n---\s*\n",
27
+ re.DOTALL,
28
+ )
29
+
30
+ # Pattern to extract title from first line (for files without frontmatter)
31
+ TITLE_PATTERN = re.compile(r"^#?\s*(.+?)(?:\s*-\s*(.+))?$")
32
+
33
+
34
+ class CommandParser:
35
+ """Parser for command markdown files.
36
+
37
+ Supports two formats:
38
+ 1. With YAML frontmatter (preferred for new commands)
39
+ 2. Plain markdown (legacy format, extracts name from filename/title)
40
+
41
+ Example with frontmatter:
42
+ ---
43
+ name: compact
44
+ description: Strategic context compaction
45
+ category: context
46
+ aliases: [comp]
47
+ ---
48
+
49
+ ## Overview
50
+ This command preserves collaboration state...
51
+
52
+ Example without frontmatter:
53
+ Create a git commit with a well-formatted message.
54
+
55
+ ## Execution Steps
56
+ ...
57
+ """
58
+
59
+ # Category inference from command name patterns
60
+ CATEGORY_PATTERNS: dict[str, CommandCategory] = {
61
+ r"commit|pr|review-pr": CommandCategory.GIT,
62
+ r"test|coverage": CommandCategory.TEST,
63
+ r"docs|manage-docs|explain": CommandCategory.DOCS,
64
+ r"security|scan": CommandCategory.SECURITY,
65
+ r"bench|profile|perf": CommandCategory.PERFORMANCE,
66
+ r"pattern|learn|evaluate": CommandCategory.LEARNING,
67
+ r"compact|context|memory": CommandCategory.CONTEXT,
68
+ }
69
+
70
+ def __init__(self):
71
+ """Initialize the parser."""
72
+ pass
73
+
74
+ def parse_file(self, file_path: str | Path) -> CommandConfig:
75
+ """Parse a command markdown file.
76
+
77
+ Args:
78
+ file_path: Path to the command markdown file
79
+
80
+ Returns:
81
+ CommandConfig instance
82
+
83
+ Raises:
84
+ FileNotFoundError: If file doesn't exist
85
+ ValueError: If file format is invalid
86
+
87
+ """
88
+ file_path = Path(file_path)
89
+
90
+ if not file_path.exists():
91
+ raise FileNotFoundError(f"Command file not found: {file_path}")
92
+
93
+ with open(file_path, encoding="utf-8") as f:
94
+ content = f.read()
95
+
96
+ return self.parse_content(content, source=file_path)
97
+
98
+ def parse_content(
99
+ self,
100
+ content: str,
101
+ source: Path | str | None = None,
102
+ ) -> CommandConfig:
103
+ """Parse command markdown content.
104
+
105
+ Args:
106
+ content: Markdown content (optionally with YAML frontmatter)
107
+ source: Source file path (used for name inference)
108
+
109
+ Returns:
110
+ CommandConfig instance
111
+
112
+ """
113
+ source_path = Path(source) if source else None
114
+ source_str = str(source) if source else "unknown"
115
+
116
+ # Try to extract frontmatter
117
+ match = FRONTMATTER_PATTERN.match(content)
118
+
119
+ if match:
120
+ # Has frontmatter
121
+ frontmatter_yaml = match.group(1)
122
+ body = content[match.end() :].strip()
123
+ metadata = self._parse_frontmatter(frontmatter_yaml, source_str)
124
+ else:
125
+ # No frontmatter - infer from content
126
+ body = content.strip()
127
+ metadata = self._infer_metadata(body, source_path)
128
+
129
+ # Ensure name is set
130
+ if not metadata.name:
131
+ if source_path:
132
+ metadata.name = source_path.stem
133
+ else:
134
+ raise ValueError(f"Cannot determine command name: {source_str}")
135
+
136
+ # Extract description from first line if not set
137
+ if not metadata.description:
138
+ metadata.description = self._extract_description(body)
139
+
140
+ return CommandConfig(
141
+ name=metadata.name,
142
+ description=metadata.description,
143
+ body=body,
144
+ metadata=metadata,
145
+ source_file=source_path,
146
+ )
147
+
148
+ def _parse_frontmatter(
149
+ self,
150
+ yaml_content: str,
151
+ source: str,
152
+ ) -> CommandMetadata:
153
+ """Parse YAML frontmatter into metadata.
154
+
155
+ Args:
156
+ yaml_content: YAML content from frontmatter
157
+ source: Source identifier for errors
158
+
159
+ Returns:
160
+ CommandMetadata instance
161
+
162
+ """
163
+ try:
164
+ import yaml
165
+
166
+ data = yaml.safe_load(yaml_content) or {}
167
+ except ImportError:
168
+ logger.warning("PyYAML not installed, using basic parsing")
169
+ data = self._basic_yaml_parse(yaml_content)
170
+ except Exception as e:
171
+ raise ValueError(f"Invalid YAML frontmatter in {source}: {e}")
172
+
173
+ return CommandMetadata.from_dict(data)
174
+
175
+ def _basic_yaml_parse(self, yaml_content: str) -> dict[str, Any]:
176
+ """Basic YAML parsing without PyYAML (for simple key: value pairs).
177
+
178
+ Args:
179
+ yaml_content: Simple YAML content
180
+
181
+ Returns:
182
+ Parsed dictionary
183
+
184
+ """
185
+ result: dict[str, Any] = {}
186
+
187
+ for line in yaml_content.strip().split("\n"):
188
+ line = line.strip()
189
+ if not line or line.startswith("#"):
190
+ continue
191
+
192
+ if ":" in line:
193
+ key, value = line.split(":", 1)
194
+ key = key.strip()
195
+ value = value.strip()
196
+
197
+ # Handle simple arrays [a, b, c]
198
+ if value.startswith("[") and value.endswith("]"):
199
+ value = [v.strip().strip("'\"") for v in value[1:-1].split(",") if v.strip()]
200
+ # Handle booleans
201
+ elif value.lower() in ("true", "yes"):
202
+ value = True
203
+ elif value.lower() in ("false", "no"):
204
+ value = False
205
+ # Handle strings
206
+ else:
207
+ value = value.strip("'\"")
208
+
209
+ result[key] = value
210
+
211
+ return result
212
+
213
+ def _infer_metadata(
214
+ self,
215
+ body: str,
216
+ source_path: Path | None,
217
+ ) -> CommandMetadata:
218
+ """Infer metadata from content when no frontmatter present.
219
+
220
+ Args:
221
+ body: Markdown body content
222
+ source_path: Source file path
223
+
224
+ Returns:
225
+ CommandMetadata with inferred values
226
+
227
+ """
228
+ name = ""
229
+ description = ""
230
+
231
+ # Get name from filename
232
+ if source_path:
233
+ name = source_path.stem
234
+
235
+ # Try to extract description from first line
236
+ first_line = body.split("\n")[0].strip() if body else ""
237
+ if first_line:
238
+ # Remove markdown heading prefix
239
+ if first_line.startswith("#"):
240
+ first_line = first_line.lstrip("#").strip()
241
+
242
+ # Check for "title - description" format
243
+ title_match = TITLE_PATTERN.match(first_line)
244
+ if title_match:
245
+ if title_match.group(2):
246
+ description = title_match.group(2).strip()
247
+ else:
248
+ description = title_match.group(1).strip()
249
+
250
+ # Infer category from name
251
+ category = self._infer_category(name)
252
+
253
+ return CommandMetadata(
254
+ name=name,
255
+ description=description,
256
+ category=category,
257
+ )
258
+
259
+ def _infer_category(self, name: str) -> CommandCategory:
260
+ """Infer command category from name.
261
+
262
+ Args:
263
+ name: Command name
264
+
265
+ Returns:
266
+ Inferred category
267
+
268
+ """
269
+ name_lower = name.lower()
270
+
271
+ for pattern, category in self.CATEGORY_PATTERNS.items():
272
+ if re.search(pattern, name_lower):
273
+ return category
274
+
275
+ return CommandCategory.UTILITY
276
+
277
+ def _extract_description(self, body: str) -> str:
278
+ """Extract description from body content.
279
+
280
+ Args:
281
+ body: Markdown body
282
+
283
+ Returns:
284
+ Extracted description
285
+
286
+ """
287
+ if not body:
288
+ return ""
289
+
290
+ lines = body.strip().split("\n")
291
+ first_line = lines[0].strip()
292
+
293
+ # Remove markdown heading prefix
294
+ if first_line.startswith("#"):
295
+ first_line = first_line.lstrip("#").strip()
296
+
297
+ # Check for "title - description" format
298
+ if " - " in first_line:
299
+ return first_line.split(" - ", 1)[1].strip()
300
+
301
+ # Use first line if it looks like a description
302
+ if first_line and not first_line.startswith("```"):
303
+ return first_line[:200] # Limit length
304
+
305
+ return ""
306
+
307
+ def validate_file(self, file_path: str | Path) -> list[str]:
308
+ """Validate a command file without fully parsing.
309
+
310
+ Args:
311
+ file_path: Path to validate
312
+
313
+ Returns:
314
+ List of validation error messages (empty if valid)
315
+
316
+ """
317
+ errors: list[str] = []
318
+ file_path = Path(file_path)
319
+
320
+ if not file_path.exists():
321
+ return [f"File not found: {file_path}"]
322
+
323
+ try:
324
+ with open(file_path, encoding="utf-8") as f:
325
+ content = f.read()
326
+ except OSError as e:
327
+ return [f"Cannot read file: {e}"]
328
+
329
+ # Check if file has content
330
+ if not content.strip():
331
+ errors.append("File is empty")
332
+ return errors
333
+
334
+ # Check frontmatter if present
335
+ match = FRONTMATTER_PATTERN.match(content)
336
+ if match:
337
+ try:
338
+ import yaml
339
+
340
+ frontmatter = yaml.safe_load(match.group(1)) or {}
341
+
342
+ # Validate name if provided
343
+ name = frontmatter.get("name", "")
344
+ if name and not re.match(r"^[a-z0-9][-a-z0-9]*$", name):
345
+ errors.append(
346
+ f"Invalid command name '{name}'. "
347
+ "Use lowercase letters, numbers, and hyphens."
348
+ )
349
+
350
+ # Validate category if provided
351
+ category = frontmatter.get("category", "")
352
+ if category:
353
+ try:
354
+ CommandCategory(category)
355
+ except ValueError:
356
+ valid = ", ".join(c.value for c in CommandCategory)
357
+ errors.append(f"Invalid category '{category}'. Valid: {valid}")
358
+
359
+ except ImportError:
360
+ pass # Skip YAML validation if not installed
361
+ except Exception as e:
362
+ errors.append(f"Invalid YAML frontmatter: {e}")
363
+
364
+ # Check body has content
365
+ body_start = match.end() if match else 0
366
+ body = content[body_start:].strip()
367
+
368
+ if not body:
369
+ errors.append("Command has no body content")
370
+
371
+ return errors