claude-mpm 3.7.4__py3-none-any.whl → 3.8.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_PM.md +0 -106
  3. claude_mpm/agents/INSTRUCTIONS.md +0 -78
  4. claude_mpm/agents/MEMORY.md +88 -0
  5. claude_mpm/agents/WORKFLOW.md +86 -0
  6. claude_mpm/agents/schema/agent_schema.json +1 -1
  7. claude_mpm/agents/templates/code_analyzer.json +26 -11
  8. claude_mpm/agents/templates/data_engineer.json +4 -7
  9. claude_mpm/agents/templates/documentation.json +2 -2
  10. claude_mpm/agents/templates/engineer.json +2 -2
  11. claude_mpm/agents/templates/ops.json +3 -8
  12. claude_mpm/agents/templates/qa.json +2 -3
  13. claude_mpm/agents/templates/research.json +2 -3
  14. claude_mpm/agents/templates/security.json +3 -6
  15. claude_mpm/agents/templates/ticketing.json +4 -9
  16. claude_mpm/agents/templates/version_control.json +3 -3
  17. claude_mpm/agents/templates/web_qa.json +4 -4
  18. claude_mpm/agents/templates/web_ui.json +4 -4
  19. claude_mpm/cli/__init__.py +2 -2
  20. claude_mpm/cli/commands/__init__.py +2 -1
  21. claude_mpm/cli/commands/agents.py +118 -1
  22. claude_mpm/cli/commands/tickets.py +596 -19
  23. claude_mpm/cli/parser.py +228 -5
  24. claude_mpm/config/__init__.py +30 -39
  25. claude_mpm/config/socketio_config.py +8 -5
  26. claude_mpm/constants.py +13 -0
  27. claude_mpm/core/__init__.py +8 -18
  28. claude_mpm/core/cache.py +596 -0
  29. claude_mpm/core/claude_runner.py +166 -622
  30. claude_mpm/core/config.py +5 -1
  31. claude_mpm/core/constants.py +339 -0
  32. claude_mpm/core/container.py +461 -22
  33. claude_mpm/core/exceptions.py +392 -0
  34. claude_mpm/core/framework_loader.py +208 -93
  35. claude_mpm/core/interactive_session.py +432 -0
  36. claude_mpm/core/interfaces.py +424 -0
  37. claude_mpm/core/lazy.py +467 -0
  38. claude_mpm/core/logging_config.py +444 -0
  39. claude_mpm/core/oneshot_session.py +465 -0
  40. claude_mpm/core/optimized_agent_loader.py +485 -0
  41. claude_mpm/core/optimized_startup.py +490 -0
  42. claude_mpm/core/service_registry.py +52 -26
  43. claude_mpm/core/socketio_pool.py +162 -5
  44. claude_mpm/core/types.py +292 -0
  45. claude_mpm/core/typing_utils.py +477 -0
  46. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +46 -2
  47. claude_mpm/dashboard/templates/index.html +5 -5
  48. claude_mpm/hooks/claude_hooks/hook_handler.py +213 -99
  49. claude_mpm/init.py +2 -1
  50. claude_mpm/services/__init__.py +78 -14
  51. claude_mpm/services/agent/__init__.py +24 -0
  52. claude_mpm/services/agent/deployment.py +2548 -0
  53. claude_mpm/services/agent/management.py +598 -0
  54. claude_mpm/services/agent/registry.py +813 -0
  55. claude_mpm/services/agents/deployment/agent_deployment.py +592 -269
  56. claude_mpm/services/agents/deployment/async_agent_deployment.py +5 -1
  57. claude_mpm/services/agents/management/agent_capabilities_generator.py +21 -11
  58. claude_mpm/services/agents/memory/agent_memory_manager.py +156 -1
  59. claude_mpm/services/async_session_logger.py +8 -3
  60. claude_mpm/services/communication/__init__.py +21 -0
  61. claude_mpm/services/communication/socketio.py +1933 -0
  62. claude_mpm/services/communication/websocket.py +479 -0
  63. claude_mpm/services/core/__init__.py +123 -0
  64. claude_mpm/services/core/base.py +247 -0
  65. claude_mpm/services/core/interfaces.py +951 -0
  66. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +23 -23
  67. claude_mpm/services/framework_claude_md_generator.py +3 -2
  68. claude_mpm/services/health_monitor.py +4 -3
  69. claude_mpm/services/hook_service.py +64 -4
  70. claude_mpm/services/infrastructure/__init__.py +21 -0
  71. claude_mpm/services/infrastructure/logging.py +202 -0
  72. claude_mpm/services/infrastructure/monitoring.py +893 -0
  73. claude_mpm/services/memory/indexed_memory.py +648 -0
  74. claude_mpm/services/project/__init__.py +21 -0
  75. claude_mpm/services/project/analyzer.py +864 -0
  76. claude_mpm/services/project/registry.py +608 -0
  77. claude_mpm/services/project_analyzer.py +95 -2
  78. claude_mpm/services/recovery_manager.py +15 -9
  79. claude_mpm/services/socketio/__init__.py +25 -0
  80. claude_mpm/services/socketio/handlers/__init__.py +25 -0
  81. claude_mpm/services/socketio/handlers/base.py +121 -0
  82. claude_mpm/services/socketio/handlers/connection.py +198 -0
  83. claude_mpm/services/socketio/handlers/file.py +213 -0
  84. claude_mpm/services/socketio/handlers/git.py +723 -0
  85. claude_mpm/services/socketio/handlers/memory.py +27 -0
  86. claude_mpm/services/socketio/handlers/project.py +25 -0
  87. claude_mpm/services/socketio/handlers/registry.py +145 -0
  88. claude_mpm/services/socketio_client_manager.py +12 -7
  89. claude_mpm/services/socketio_server.py +156 -30
  90. claude_mpm/services/ticket_manager.py +377 -51
  91. claude_mpm/utils/agent_dependency_loader.py +66 -15
  92. claude_mpm/utils/error_handler.py +1 -1
  93. claude_mpm/utils/robust_installer.py +587 -0
  94. claude_mpm/validation/agent_validator.py +27 -14
  95. claude_mpm/validation/frontmatter_validator.py +231 -0
  96. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/METADATA +74 -41
  97. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/RECORD +101 -76
  98. claude_mpm/.claude-mpm/logs/hooks_20250728.log +0 -10
  99. claude_mpm/agents/agent-template.yaml +0 -83
  100. claude_mpm/cli/README.md +0 -108
  101. claude_mpm/cli_module/refactoring_guide.md +0 -253
  102. claude_mpm/config/async_logging_config.yaml +0 -145
  103. claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +0 -34
  104. claude_mpm/dashboard/.claude-mpm/memories/README.md +0 -36
  105. claude_mpm/dashboard/README.md +0 -121
  106. claude_mpm/dashboard/static/js/dashboard.js.backup +0 -1973
  107. claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +0 -36
  108. claude_mpm/dashboard/templates/.claude-mpm/memories/engineer_agent.md +0 -39
  109. claude_mpm/dashboard/templates/.claude-mpm/memories/version_control_agent.md +0 -38
  110. claude_mpm/hooks/README.md +0 -96
  111. claude_mpm/schemas/agent_schema.json +0 -435
  112. claude_mpm/services/framework_claude_md_generator/README.md +0 -92
  113. claude_mpm/services/version_control/VERSION +0 -1
  114. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/WHEEL +0 -0
  115. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/entry_points.txt +0 -0
  116. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/licenses/LICENSE +0 -0
  117. {claude_mpm-3.7.4.dist-info → claude_mpm-3.8.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,598 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Agent Management Service
4
+ ========================
5
+
6
+ Comprehensive service for managing agent definitions with CRUD operations,
7
+ section extraction/updates, and version management.
8
+
9
+ Uses python-frontmatter and mistune for markdown parsing as recommended.
10
+ """
11
+
12
+ import os
13
+ import re
14
+ import json
15
+ import yaml
16
+ import logging
17
+ from pathlib import Path
18
+ from typing import Dict, List, Optional, Tuple, Any
19
+ from datetime import datetime
20
+
21
+ import frontmatter
22
+ import mistune
23
+
24
+ from claude_mpm.models.agent_definition import (
25
+ AgentDefinition, AgentMetadata, AgentType,
26
+ AgentSection, AgentWorkflow, AgentPermissions
27
+ )
28
+ from ..deployment.agent_versioning import AgentVersionManager
29
+ from claude_mpm.services.memory.cache.shared_prompt_cache import SharedPromptCache
30
+ from claude_mpm.utils.paths import PathResolver
31
+ from claude_mpm.core.config_paths import ConfigPaths
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ class AgentManager:
37
+ """Manages agent definitions with CRUD operations and versioning."""
38
+
39
+ def __init__(self, framework_dir: Optional[Path] = None, project_dir: Optional[Path] = None):
40
+ """
41
+ Initialize AgentManager.
42
+
43
+ Args:
44
+ framework_dir: Path to agents templates directory
45
+ project_dir: Path to project-specific agents directory
46
+ """
47
+ # Use PathResolver for consistent path discovery
48
+ if framework_dir is None:
49
+ try:
50
+ # Use agents templates directory
51
+ self.framework_dir = Path(__file__).parent.parent / "agents" / "templates"
52
+ except Exception:
53
+ # Fallback to agents directory
54
+ self.framework_dir = PathResolver.get_agents_dir()
55
+ else:
56
+ self.framework_dir = framework_dir
57
+
58
+ if project_dir is None:
59
+ project_root = PathResolver.get_project_root()
60
+ # Use direct agents directory without subdirectory to match deployment expectations
61
+ self.project_dir = project_root / ConfigPaths.CONFIG_DIR / "agents"
62
+ else:
63
+ self.project_dir = project_dir
64
+ self.version_manager = AgentVersionManager()
65
+ self.cache = SharedPromptCache.get_instance()
66
+ self._markdown = mistune.create_markdown()
67
+
68
+ def create_agent(self, name: str, definition: AgentDefinition, location: str = "project") -> Path:
69
+ """
70
+ Create a new agent definition file.
71
+
72
+ Args:
73
+ name: Agent name (e.g., "performance-agent")
74
+ definition: Agent definition object
75
+ location: "project" or "framework"
76
+
77
+ Returns:
78
+ Path to created file
79
+ """
80
+ # Determine target directory
81
+ target_dir = self.project_dir if location == "project" else self.framework_dir
82
+ target_dir.mkdir(parents=True, exist_ok=True)
83
+
84
+ # Generate markdown content
85
+ content = self._definition_to_markdown(definition)
86
+
87
+ # Write file
88
+ file_path = target_dir / f"{name}.md"
89
+ file_path.write_text(content, encoding='utf-8')
90
+
91
+ # Clear cache
92
+ self._clear_agent_cache(name)
93
+
94
+ logger.info(f"Created agent '{name}' at {file_path}")
95
+ return file_path
96
+
97
+ def read_agent(self, name: str) -> Optional[AgentDefinition]:
98
+ """
99
+ Read an agent definition.
100
+
101
+ Args:
102
+ name: Agent name (without .md extension)
103
+
104
+ Returns:
105
+ AgentDefinition or None if not found
106
+ """
107
+ # Try to find the agent file
108
+ agent_path = self._find_agent_file(name)
109
+ if not agent_path:
110
+ logger.warning(f"Agent '{name}' not found")
111
+ return None
112
+
113
+ try:
114
+ # Read and parse the file
115
+ content = agent_path.read_text(encoding='utf-8')
116
+ return self._parse_agent_markdown(content, name, str(agent_path))
117
+ except Exception as e:
118
+ logger.error(f"Error reading agent '{name}': {e}")
119
+ return None
120
+
121
+ def update_agent(self, name: str, updates: Dict[str, Any],
122
+ increment_version: bool = True) -> Optional[AgentDefinition]:
123
+ """
124
+ Update an agent definition.
125
+
126
+ Args:
127
+ name: Agent name
128
+ updates: Dictionary of updates to apply
129
+ increment_version: Whether to increment serial version
130
+
131
+ Returns:
132
+ Updated AgentDefinition or None if failed
133
+ """
134
+ # Read current definition
135
+ agent_def = self.read_agent(name)
136
+ if not agent_def:
137
+ return None
138
+
139
+ # Apply updates
140
+ for key, value in updates.items():
141
+ if hasattr(agent_def, key):
142
+ setattr(agent_def, key, value)
143
+ elif key in ["type", "model_preference", "tags", "specializations"]:
144
+ setattr(agent_def.metadata, key, value)
145
+
146
+ # Increment version if requested
147
+ if increment_version:
148
+ agent_def.metadata.increment_serial_version()
149
+ agent_def.metadata.last_updated = datetime.now()
150
+
151
+ # Write back
152
+ agent_path = self._find_agent_file(name)
153
+ if agent_path:
154
+ content = self._definition_to_markdown(agent_def)
155
+ agent_path.write_text(content, encoding='utf-8')
156
+
157
+ # Clear cache
158
+ self._clear_agent_cache(name)
159
+
160
+ logger.info(f"Updated agent '{name}' to version {agent_def.metadata.version}")
161
+ return agent_def
162
+
163
+ return None
164
+
165
+ def update_section(self, name: str, section: AgentSection, content: str,
166
+ increment_version: bool = True) -> Optional[AgentDefinition]:
167
+ """
168
+ Update a specific section of an agent.
169
+
170
+ Args:
171
+ name: Agent name
172
+ section: Section to update
173
+ content: New section content
174
+ increment_version: Whether to increment version
175
+
176
+ Returns:
177
+ Updated AgentDefinition or None
178
+ """
179
+ agent_def = self.read_agent(name)
180
+ if not agent_def:
181
+ return None
182
+
183
+ # Map section to attribute
184
+ section_map = {
185
+ AgentSection.PRIMARY_ROLE: "primary_role",
186
+ AgentSection.CAPABILITIES: "capabilities",
187
+ AgentSection.TOOLS: "tools_commands",
188
+ AgentSection.ESCALATION: "escalation_triggers",
189
+ AgentSection.KPI: "kpis",
190
+ AgentSection.DEPENDENCIES: "dependencies"
191
+ }
192
+
193
+ if section in section_map:
194
+ attr_name = section_map[section]
195
+ if section in [AgentSection.CAPABILITIES, AgentSection.ESCALATION,
196
+ AgentSection.KPI, AgentSection.DEPENDENCIES]:
197
+ # Parse list content
198
+ setattr(agent_def, attr_name, self._parse_list_content(content))
199
+ else:
200
+ setattr(agent_def, attr_name, content.strip())
201
+
202
+ # Special handling for complex sections
203
+ elif section == AgentSection.WHEN_TO_USE:
204
+ agent_def.when_to_use = self._parse_when_to_use(content)
205
+ elif section == AgentSection.AUTHORITY:
206
+ agent_def.authority = self._parse_authority(content)
207
+ elif section == AgentSection.WORKFLOWS:
208
+ agent_def.workflows = self._parse_workflows(content)
209
+
210
+ # Update raw section
211
+ agent_def.raw_sections[section.value] = content
212
+
213
+ # Increment version
214
+ if increment_version:
215
+ agent_def.metadata.increment_serial_version()
216
+ agent_def.metadata.last_updated = datetime.now()
217
+
218
+ # Write back
219
+ return self.update_agent(name, {}, increment_version=False)
220
+
221
+ def delete_agent(self, name: str) -> bool:
222
+ """
223
+ Delete an agent definition.
224
+
225
+ Args:
226
+ name: Agent name
227
+
228
+ Returns:
229
+ True if deleted, False otherwise
230
+ """
231
+ agent_path = self._find_agent_file(name)
232
+ if not agent_path:
233
+ return False
234
+
235
+ try:
236
+ agent_path.unlink()
237
+ self._clear_agent_cache(name)
238
+ logger.info(f"Deleted agent '{name}'")
239
+ return True
240
+ except Exception as e:
241
+ logger.error(f"Error deleting agent '{name}': {e}")
242
+ return False
243
+
244
+ def list_agents(self, location: Optional[str] = None) -> Dict[str, Dict[str, Any]]:
245
+ """
246
+ List all available agents.
247
+
248
+ Args:
249
+ location: Filter by location ("project", "framework", or None for all)
250
+
251
+ Returns:
252
+ Dictionary of agent info
253
+ """
254
+ agents = {}
255
+
256
+ # Check framework agents
257
+ if location in [None, "framework"]:
258
+ for agent_file in self.framework_dir.glob("*.md"):
259
+ if agent_file.name != "base_agent.md":
260
+ agent_name = agent_file.stem
261
+ agent_def = self.read_agent(agent_name)
262
+ if agent_def:
263
+ agents[agent_name] = {
264
+ "location": "framework",
265
+ "path": str(agent_file),
266
+ "version": agent_def.metadata.version,
267
+ "type": agent_def.metadata.type.value,
268
+ "specializations": agent_def.metadata.specializations
269
+ }
270
+
271
+ # Check project agents
272
+ if location in [None, "project"] and self.project_dir.exists():
273
+ for agent_file in self.project_dir.glob("*.md"):
274
+ agent_name = agent_file.stem
275
+ agent_def = self.read_agent(agent_name)
276
+ if agent_def:
277
+ agents[agent_name] = {
278
+ "location": "project",
279
+ "path": str(agent_file),
280
+ "version": agent_def.metadata.version,
281
+ "type": agent_def.metadata.type.value,
282
+ "specializations": agent_def.metadata.specializations
283
+ }
284
+
285
+ return agents
286
+
287
+ def get_agent_api(self, name: str) -> Optional[Dict[str, Any]]:
288
+ """
289
+ Get agent data in API-friendly format.
290
+
291
+ Args:
292
+ name: Agent name
293
+
294
+ Returns:
295
+ Agent data dictionary or None
296
+ """
297
+ agent_def = self.read_agent(name)
298
+ if not agent_def:
299
+ return None
300
+
301
+ return agent_def.to_dict()
302
+
303
+ # Private helper methods
304
+
305
+ def _find_agent_file(self, name: str) -> Optional[Path]:
306
+ """Find agent file in project or framework directories."""
307
+ # Check project first (higher precedence)
308
+ if self.project_dir.exists():
309
+ project_path = self.project_dir / f"{name}.md"
310
+ if project_path.exists():
311
+ return project_path
312
+
313
+ # Check framework
314
+ framework_path = self.framework_dir / f"{name}.md"
315
+ if framework_path.exists():
316
+ return framework_path
317
+
318
+ return None
319
+
320
+ def _parse_agent_markdown(self, content: str, name: str, file_path: str) -> AgentDefinition:
321
+ """Parse markdown content into AgentDefinition."""
322
+ # Parse frontmatter
323
+ post = frontmatter.loads(content)
324
+
325
+ # Extract metadata
326
+ metadata = AgentMetadata(
327
+ type=AgentType(post.metadata.get("type", "core")),
328
+ model_preference=post.metadata.get("model_preference", "claude-3-sonnet"),
329
+ version=post.metadata.get("version", "1.0.0"),
330
+ last_updated=post.metadata.get("last_updated"),
331
+ author=post.metadata.get("author"),
332
+ tags=post.metadata.get("tags", []),
333
+ specializations=post.metadata.get("specializations", [])
334
+ )
335
+
336
+ # Extract version from content if not in frontmatter
337
+ if not post.metadata.get("version"):
338
+ version = self.version_manager.extract_version_from_markdown(content)
339
+ if version:
340
+ metadata.version = version
341
+
342
+ # Parse sections
343
+ sections = self._extract_sections(post.content)
344
+
345
+ # Extract title
346
+ title_match = re.search(r'^#\s+(.+)$', post.content, re.MULTILINE)
347
+ title = title_match.group(1) if title_match else name.replace('-', ' ').title()
348
+
349
+ # Build definition
350
+ definition = AgentDefinition(
351
+ name=name,
352
+ title=title,
353
+ file_path=file_path,
354
+ metadata=metadata,
355
+ primary_role=sections.get("Primary Role", ""),
356
+ when_to_use=self._parse_when_to_use(sections.get("When to Use This Agent", "")),
357
+ capabilities=self._parse_list_content(sections.get("Core Capabilities", "")),
358
+ authority=self._parse_authority(sections.get("Authority & Permissions", "")),
359
+ workflows=self._parse_workflows(sections.get("Agent-Specific Workflows", "")),
360
+ escalation_triggers=self._parse_list_content(sections.get("Unique Escalation Triggers", "")),
361
+ kpis=self._parse_list_content(sections.get("Key Performance Indicators", "")),
362
+ dependencies=self._parse_list_content(sections.get("Critical Dependencies", "")),
363
+ tools_commands=sections.get("Specialized Tools/Commands", ""),
364
+ raw_content=content,
365
+ raw_sections=sections
366
+ )
367
+
368
+ return definition
369
+
370
+ def _extract_sections(self, content: str) -> Dict[str, str]:
371
+ """Extract sections from markdown content."""
372
+ sections = {}
373
+ current_section = None
374
+ current_content = []
375
+
376
+ # Split into lines
377
+ lines = content.split('\n')
378
+
379
+ for line in lines:
380
+ # Check if this is a section header
381
+ header_match = re.match(r'^##\s+(?:🎯|🔧|🔑|📋|🚨|📊|🔄|🛠️)?\s*(.+)$', line)
382
+ if header_match:
383
+ # Save previous section
384
+ if current_section:
385
+ sections[current_section] = '\n'.join(current_content).strip()
386
+
387
+ # Start new section
388
+ current_section = header_match.group(1).strip()
389
+ current_content = []
390
+ else:
391
+ # Add to current section
392
+ if current_section:
393
+ current_content.append(line)
394
+
395
+ # Save last section
396
+ if current_section:
397
+ sections[current_section] = '\n'.join(current_content).strip()
398
+
399
+ return sections
400
+
401
+ def _parse_list_content(self, content: str) -> List[str]:
402
+ """Parse bullet point or numbered list content."""
403
+ items = []
404
+ for line in content.split('\n'):
405
+ # Match bullet points or numbered items
406
+ match = re.match(r'^[-*•]\s+(.+)$|^\d+\.\s+(.+)$', line.strip())
407
+ if match:
408
+ item = match.group(1) or match.group(2)
409
+ items.append(item.strip())
410
+ return items
411
+
412
+ def _parse_when_to_use(self, content: str) -> Dict[str, List[str]]:
413
+ """Parse When to Use section."""
414
+ result = {"select": [], "do_not_select": []}
415
+ current_mode = None
416
+
417
+ for line in content.split('\n'):
418
+ if "Select this agent when:" in line or "**Select this agent when:**" in line:
419
+ current_mode = "select"
420
+ elif "Do NOT select for:" in line or "**Do NOT select for:**" in line:
421
+ current_mode = "do_not_select"
422
+ elif current_mode and line.strip().startswith('-'):
423
+ item = line.strip()[1:].strip()
424
+ result[current_mode].append(item)
425
+
426
+ return result
427
+
428
+ def _parse_authority(self, content: str) -> AgentPermissions:
429
+ """Parse Authority & Permissions section."""
430
+ permissions = AgentPermissions()
431
+ current_section = None
432
+
433
+ for line in content.split('\n'):
434
+ if "Exclusive Write Access" in line:
435
+ current_section = "write"
436
+ elif "Forbidden Operations" in line:
437
+ current_section = "forbidden"
438
+ elif "Read Access" in line:
439
+ current_section = "read"
440
+ elif current_section and line.strip().startswith('-'):
441
+ item = line.strip()[1:].strip()
442
+ # Remove inline comments
443
+ item = re.sub(r'\s*#.*$', '', item).strip()
444
+
445
+ if current_section == "write":
446
+ permissions.exclusive_write_access.append(item)
447
+ elif current_section == "forbidden":
448
+ permissions.forbidden_operations.append(item)
449
+ elif current_section == "read":
450
+ permissions.read_access.append(item)
451
+
452
+ return permissions
453
+
454
+ def _parse_workflows(self, content: str) -> List[AgentWorkflow]:
455
+ """Parse workflows from YAML blocks."""
456
+ workflows = []
457
+
458
+ # Find all YAML blocks
459
+ yaml_blocks = re.findall(r'```yaml\n(.*?)\n```', content, re.DOTALL)
460
+
461
+ for block in yaml_blocks:
462
+ try:
463
+ data = yaml.safe_load(block)
464
+ if isinstance(data, dict) and all(k in data for k in ["trigger", "process", "output"]):
465
+ # Extract workflow name from preceding heading if available
466
+ name_match = re.search(r'###\s+(.+)\n```yaml\n' + re.escape(block), content)
467
+ name = name_match.group(1) if name_match else "Unnamed Workflow"
468
+
469
+ workflow = AgentWorkflow(
470
+ name=name,
471
+ trigger=data["trigger"],
472
+ process=data["process"] if isinstance(data["process"], list) else [data["process"]],
473
+ output=data["output"],
474
+ raw_yaml=block
475
+ )
476
+ workflows.append(workflow)
477
+ except yaml.YAMLError:
478
+ logger.warning("Failed to parse YAML workflow block")
479
+
480
+ return workflows
481
+
482
+ def _definition_to_markdown(self, definition: AgentDefinition) -> str:
483
+ """Convert AgentDefinition back to markdown."""
484
+ # Start with frontmatter
485
+ frontmatter_data = {
486
+ "type": definition.metadata.type.value,
487
+ "model_preference": definition.metadata.model_preference,
488
+ "version": definition.metadata.version,
489
+ "last_updated": definition.metadata.last_updated,
490
+ "author": definition.metadata.author,
491
+ "tags": definition.metadata.tags,
492
+ "specializations": definition.metadata.specializations
493
+ }
494
+
495
+ # Remove None values
496
+ frontmatter_data = {k: v for k, v in frontmatter_data.items() if v is not None}
497
+
498
+ # Build content
499
+ content = []
500
+ content.append(f"# {definition.title}\n")
501
+
502
+ # Primary Role
503
+ content.append("## 🎯 Primary Role")
504
+ content.append(definition.primary_role)
505
+ content.append("")
506
+
507
+ # When to Use
508
+ content.append("## 🎯 When to Use This Agent")
509
+ content.append("")
510
+ content.append("**Select this agent when:**")
511
+ for item in definition.when_to_use.get("select", []):
512
+ content.append(f"- {item}")
513
+ content.append("")
514
+ content.append("**Do NOT select for:**")
515
+ for item in definition.when_to_use.get("do_not_select", []):
516
+ content.append(f"- {item}")
517
+ content.append("")
518
+
519
+ # Capabilities
520
+ content.append("## 🔧 Core Capabilities")
521
+ for capability in definition.capabilities:
522
+ content.append(f"- {capability}")
523
+ content.append("")
524
+
525
+ # Authority
526
+ content.append("## 🔑 Authority & Permissions")
527
+ content.append("")
528
+ content.append("### ✅ Exclusive Write Access")
529
+ for item in definition.authority.exclusive_write_access:
530
+ content.append(f"- {item}")
531
+ content.append("")
532
+ content.append("### ❌ Forbidden Operations")
533
+ for item in definition.authority.forbidden_operations:
534
+ content.append(f"- {item}")
535
+ content.append("")
536
+
537
+ # Workflows
538
+ if definition.workflows:
539
+ content.append("## 📋 Agent-Specific Workflows")
540
+ content.append("")
541
+ for workflow in definition.workflows:
542
+ content.append(f"### {workflow.name}")
543
+ content.append("```yaml")
544
+ yaml_content = {
545
+ "trigger": workflow.trigger,
546
+ "process": workflow.process,
547
+ "output": workflow.output
548
+ }
549
+ content.append(yaml.dump(yaml_content, default_flow_style=False).strip())
550
+ content.append("```")
551
+ content.append("")
552
+
553
+ # Escalation
554
+ if definition.escalation_triggers:
555
+ content.append("## 🚨 Unique Escalation Triggers")
556
+ for trigger in definition.escalation_triggers:
557
+ content.append(f"- {trigger}")
558
+ content.append("")
559
+
560
+ # KPIs
561
+ if definition.kpis:
562
+ content.append("## 📊 Key Performance Indicators")
563
+ for i, kpi in enumerate(definition.kpis, 1):
564
+ content.append(f"{i}. {kpi}")
565
+ content.append("")
566
+
567
+ # Dependencies
568
+ if definition.dependencies:
569
+ content.append("## 🔄 Critical Dependencies")
570
+ for dep in definition.dependencies:
571
+ content.append(f"- {dep}")
572
+ content.append("")
573
+
574
+ # Tools
575
+ if definition.tools_commands:
576
+ content.append("## 🛠️ Specialized Tools/Commands")
577
+ content.append(definition.tools_commands)
578
+ content.append("")
579
+
580
+ # Footer
581
+ content.append("---")
582
+ content.append(f"**Agent Type**: {definition.metadata.type.value}")
583
+ content.append(f"**Model Preference**: {definition.metadata.model_preference}")
584
+ content.append(f"**Version**: {definition.metadata.version}")
585
+ if definition.metadata.last_updated:
586
+ content.append(f"**Last Updated**: {definition.metadata.last_updated.strftime('%Y-%m-%d %H:%M:%S')}")
587
+
588
+ # Combine with frontmatter
589
+ post = frontmatter.Post('\n'.join(content), **frontmatter_data)
590
+ return frontmatter.dumps(post)
591
+
592
+ def _clear_agent_cache(self, name: str):
593
+ """Clear cache for a specific agent."""
594
+ try:
595
+ cache_key = f"agent_prompt:{name}:md"
596
+ self.cache.invalidate(cache_key)
597
+ except Exception as e:
598
+ logger.warning(f"Failed to clear cache for agent '{name}': {e}")