claude-mpm 5.0.2__py3-none-any.whl → 5.1.9__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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
- claude_mpm/agents/PM_INSTRUCTIONS.md +1176 -909
- claude_mpm/agents/base_agent_loader.py +10 -35
- claude_mpm/agents/frontmatter_validator.py +68 -0
- claude_mpm/agents/templates/circuit-breakers.md +293 -44
- claude_mpm/cli/__init__.py +0 -1
- claude_mpm/cli/commands/__init__.py +2 -0
- claude_mpm/cli/commands/agent_state_manager.py +64 -11
- claude_mpm/cli/commands/agents.py +446 -25
- claude_mpm/cli/commands/auto_configure.py +535 -233
- claude_mpm/cli/commands/configure.py +545 -89
- claude_mpm/cli/commands/postmortem.py +401 -0
- claude_mpm/cli/commands/run.py +1 -39
- claude_mpm/cli/commands/skills.py +322 -19
- claude_mpm/cli/interactive/agent_wizard.py +302 -195
- claude_mpm/cli/parsers/agents_parser.py +137 -0
- claude_mpm/cli/parsers/auto_configure_parser.py +13 -0
- claude_mpm/cli/parsers/base_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +7 -0
- claude_mpm/cli/startup.py +73 -32
- claude_mpm/commands/mpm-agents-auto-configure.md +2 -2
- claude_mpm/commands/mpm-agents-list.md +2 -2
- claude_mpm/commands/mpm-config-view.md +2 -2
- claude_mpm/commands/mpm-help.md +3 -0
- claude_mpm/commands/mpm-postmortem.md +123 -0
- claude_mpm/commands/mpm-session-resume.md +2 -2
- claude_mpm/commands/mpm-ticket-organize.md +2 -2
- claude_mpm/commands/mpm-ticket-view.md +2 -2
- claude_mpm/config/agent_presets.py +312 -82
- claude_mpm/config/skill_presets.py +392 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/claude_runner.py +2 -25
- claude_mpm/core/framework/loaders/file_loader.py +54 -101
- claude_mpm/core/interactive_session.py +19 -5
- claude_mpm/core/oneshot_session.py +16 -4
- claude_mpm/core/output_style_manager.py +173 -43
- claude_mpm/core/protocols/__init__.py +23 -0
- claude_mpm/core/protocols/runner_protocol.py +103 -0
- claude_mpm/core/protocols/session_protocol.py +131 -0
- claude_mpm/core/shared/singleton_manager.py +11 -4
- claude_mpm/core/system_context.py +38 -0
- claude_mpm/core/unified_agent_registry.py +129 -1
- claude_mpm/core/unified_config.py +22 -0
- claude_mpm/hooks/claude_hooks/memory_integration.py +12 -1
- claude_mpm/models/agent_definition.py +7 -0
- claude_mpm/services/agents/cache_git_manager.py +621 -0
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +110 -3
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +195 -1
- claude_mpm/services/agents/sources/git_source_sync_service.py +37 -5
- claude_mpm/services/analysis/__init__.py +25 -0
- claude_mpm/services/analysis/postmortem_reporter.py +474 -0
- claude_mpm/services/analysis/postmortem_service.py +765 -0
- claude_mpm/services/command_deployment_service.py +108 -5
- claude_mpm/services/core/base.py +7 -2
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +7 -15
- claude_mpm/services/git/git_operations_service.py +8 -8
- claude_mpm/services/mcp_config_manager.py +75 -145
- claude_mpm/services/mcp_gateway/core/process_pool.py +22 -16
- claude_mpm/services/mcp_service_verifier.py +6 -3
- claude_mpm/services/monitor/daemon.py +28 -8
- claude_mpm/services/monitor/daemon_manager.py +96 -19
- claude_mpm/services/project/project_organizer.py +4 -0
- claude_mpm/services/runner_configuration_service.py +16 -3
- claude_mpm/services/session_management_service.py +16 -4
- claude_mpm/utils/agent_filters.py +288 -0
- claude_mpm/utils/gitignore.py +3 -0
- claude_mpm/utils/migration.py +372 -0
- claude_mpm/utils/progress.py +5 -1
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/METADATA +69 -8
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/RECORD +76 -62
- /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/WHEEL +0 -0
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/top_level.txt +0 -0
|
@@ -51,6 +51,63 @@ class MultiSourceAgentDeploymentService:
|
|
|
51
51
|
self.logger = get_logger(__name__)
|
|
52
52
|
self.version_manager = AgentVersionManager()
|
|
53
53
|
|
|
54
|
+
def _build_canonical_id_for_agent(self, agent_info: Dict[str, Any]) -> str:
|
|
55
|
+
"""Build or retrieve canonical_id for an agent.
|
|
56
|
+
|
|
57
|
+
NEW: Supports enhanced agent matching via canonical_id.
|
|
58
|
+
|
|
59
|
+
Priority:
|
|
60
|
+
1. Use existing canonical_id from agent_info if present
|
|
61
|
+
2. Generate from collection_id + agent_id if available
|
|
62
|
+
3. Fallback to legacy:{filename} for backward compatibility
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
agent_info: Agent dictionary with metadata
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Canonical ID string for matching
|
|
69
|
+
|
|
70
|
+
Example:
|
|
71
|
+
Remote agent: "bobmatnyc/claude-mpm-agents:pm"
|
|
72
|
+
Legacy agent: "legacy:custom-agent"
|
|
73
|
+
"""
|
|
74
|
+
# Priority 1: Existing canonical_id
|
|
75
|
+
if "canonical_id" in agent_info:
|
|
76
|
+
return agent_info["canonical_id"]
|
|
77
|
+
|
|
78
|
+
# Priority 2: Generate from collection_id + agent_id
|
|
79
|
+
collection_id = agent_info.get("collection_id")
|
|
80
|
+
agent_id = agent_info.get("agent_id")
|
|
81
|
+
|
|
82
|
+
if collection_id and agent_id:
|
|
83
|
+
canonical_id = f"{collection_id}:{agent_id}"
|
|
84
|
+
# Cache it in agent_info for future use
|
|
85
|
+
agent_info["canonical_id"] = canonical_id
|
|
86
|
+
return canonical_id
|
|
87
|
+
|
|
88
|
+
# Priority 3: Fallback to legacy format
|
|
89
|
+
# Use filename or agent name
|
|
90
|
+
agent_name = agent_info.get("name") or agent_info.get("metadata", {}).get(
|
|
91
|
+
"name", "unknown"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Extract filename from path
|
|
95
|
+
path_str = (
|
|
96
|
+
agent_info.get("path")
|
|
97
|
+
or agent_info.get("file_path")
|
|
98
|
+
or agent_info.get("source_file")
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
if path_str:
|
|
102
|
+
filename = Path(path_str).stem
|
|
103
|
+
canonical_id = f"legacy:{filename}"
|
|
104
|
+
else:
|
|
105
|
+
canonical_id = f"legacy:{agent_name}"
|
|
106
|
+
|
|
107
|
+
# Cache it
|
|
108
|
+
agent_info["canonical_id"] = canonical_id
|
|
109
|
+
return canonical_id
|
|
110
|
+
|
|
54
111
|
def discover_agents_from_all_sources(
|
|
55
112
|
self,
|
|
56
113
|
system_templates_dir: Optional[Path] = None,
|
|
@@ -156,11 +213,19 @@ class MultiSourceAgentDeploymentService:
|
|
|
156
213
|
agent_info["source"] = source_name
|
|
157
214
|
agent_info["source_dir"] = str(source_dir)
|
|
158
215
|
|
|
216
|
+
# NEW: Build canonical_id for enhanced matching
|
|
217
|
+
canonical_id = self._build_canonical_id_for_agent(agent_info)
|
|
218
|
+
|
|
219
|
+
# Group by canonical_id (PRIMARY) for enhanced matching
|
|
220
|
+
# This allows matching agents from different sources with same canonical_id
|
|
221
|
+
# while maintaining backward compatibility with name-based matching
|
|
222
|
+
matching_key = canonical_id
|
|
223
|
+
|
|
159
224
|
# Initialize list if this is the first occurrence of this agent
|
|
160
|
-
if
|
|
161
|
-
agents_by_name[
|
|
225
|
+
if matching_key not in agents_by_name:
|
|
226
|
+
agents_by_name[matching_key] = []
|
|
162
227
|
|
|
163
|
-
agents_by_name[
|
|
228
|
+
agents_by_name[matching_key].append(agent_info)
|
|
164
229
|
|
|
165
230
|
# Use more specific log message
|
|
166
231
|
self.logger.info(
|
|
@@ -189,6 +254,48 @@ class MultiSourceAgentDeploymentService:
|
|
|
189
254
|
|
|
190
255
|
return agents_by_name
|
|
191
256
|
|
|
257
|
+
def get_agents_by_collection(
|
|
258
|
+
self,
|
|
259
|
+
collection_id: str,
|
|
260
|
+
remote_agents_dir: Optional[Path] = None,
|
|
261
|
+
) -> List[Dict[str, Any]]:
|
|
262
|
+
"""Get all agents from a specific collection.
|
|
263
|
+
|
|
264
|
+
NEW: Enables collection-based agent selection.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
collection_id: Collection identifier (e.g., "bobmatnyc/claude-mpm-agents")
|
|
268
|
+
remote_agents_dir: Directory containing remote agents cache
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
List of agent dictionaries from the specified collection
|
|
272
|
+
|
|
273
|
+
Example:
|
|
274
|
+
>>> service = MultiSourceAgentDeploymentService()
|
|
275
|
+
>>> agents = service.get_agents_by_collection("bobmatnyc/claude-mpm-agents")
|
|
276
|
+
>>> len(agents)
|
|
277
|
+
45
|
|
278
|
+
"""
|
|
279
|
+
if not remote_agents_dir:
|
|
280
|
+
cache_dir = Path.home() / ".claude-mpm" / "cache"
|
|
281
|
+
remote_agents_dir = cache_dir / "remote-agents"
|
|
282
|
+
|
|
283
|
+
if not remote_agents_dir.exists():
|
|
284
|
+
self.logger.warning(
|
|
285
|
+
f"Remote agents directory not found: {remote_agents_dir}"
|
|
286
|
+
)
|
|
287
|
+
return []
|
|
288
|
+
|
|
289
|
+
# Use RemoteAgentDiscoveryService to get collection agents
|
|
290
|
+
remote_service = RemoteAgentDiscoveryService(remote_agents_dir)
|
|
291
|
+
collection_agents = remote_service.get_agents_by_collection(collection_id)
|
|
292
|
+
|
|
293
|
+
self.logger.info(
|
|
294
|
+
f"Retrieved {len(collection_agents)} agents from collection '{collection_id}'"
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
return collection_agents
|
|
298
|
+
|
|
192
299
|
def select_highest_version_agents(
|
|
193
300
|
self, agents_by_name: Dict[str, List[Dict[str, Any]]]
|
|
194
301
|
) -> Dict[str, Dict[str, Any]]:
|
|
@@ -32,6 +32,9 @@ class RemoteAgentMetadata:
|
|
|
32
32
|
routing_priority: int
|
|
33
33
|
source_file: Path
|
|
34
34
|
version: str # SHA-256 hash from cache metadata
|
|
35
|
+
collection_id: Optional[str] = None # Format: owner/repo-name
|
|
36
|
+
source_path: Optional[str] = None # Relative path in repo
|
|
37
|
+
canonical_id: Optional[str] = None # Format: collection_id:agent_id
|
|
35
38
|
|
|
36
39
|
|
|
37
40
|
class RemoteAgentDiscoveryService:
|
|
@@ -65,6 +68,91 @@ class RemoteAgentDiscoveryService:
|
|
|
65
68
|
self.remote_agents_dir = remote_agents_dir
|
|
66
69
|
self.logger = get_logger(__name__)
|
|
67
70
|
|
|
71
|
+
def _extract_collection_id_from_path(self, file_path: Path) -> Optional[str]:
|
|
72
|
+
"""Extract collection_id from repository path structure.
|
|
73
|
+
|
|
74
|
+
Collection ID is derived from the repository path structure:
|
|
75
|
+
~/.claude-mpm/cache/remote-agents/{owner}/{repo}/agents/...
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
file_path: Absolute path to agent Markdown file
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Collection ID in format "owner/repo-name" or None if not found
|
|
82
|
+
|
|
83
|
+
Example:
|
|
84
|
+
Input: ~/.claude-mpm/cache/remote-agents/bobmatnyc/claude-mpm-agents/agents/pm.md
|
|
85
|
+
Output: "bobmatnyc/claude-mpm-agents"
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
# Find "remote-agents" in the path
|
|
89
|
+
path_parts = file_path.parts
|
|
90
|
+
remote_agents_idx = -1
|
|
91
|
+
|
|
92
|
+
for i, part in enumerate(path_parts):
|
|
93
|
+
if part == "remote-agents":
|
|
94
|
+
remote_agents_idx = i
|
|
95
|
+
break
|
|
96
|
+
|
|
97
|
+
if remote_agents_idx == -1 or remote_agents_idx + 2 >= len(path_parts):
|
|
98
|
+
self.logger.debug(
|
|
99
|
+
f"Could not extract collection_id from path: {file_path}"
|
|
100
|
+
)
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
# Extract owner and repo (next two parts after "remote-agents")
|
|
104
|
+
owner = path_parts[remote_agents_idx + 1]
|
|
105
|
+
repo = path_parts[remote_agents_idx + 2]
|
|
106
|
+
|
|
107
|
+
collection_id = f"{owner}/{repo}"
|
|
108
|
+
self.logger.debug(f"Extracted collection_id: {collection_id}")
|
|
109
|
+
return collection_id
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
self.logger.warning(
|
|
113
|
+
f"Failed to extract collection_id from {file_path}: {e}"
|
|
114
|
+
)
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
def _extract_source_path_from_file(self, file_path: Path) -> Optional[str]:
|
|
118
|
+
"""Extract relative source path within repository.
|
|
119
|
+
|
|
120
|
+
Source path is relative to the repository root (not the agents subdirectory).
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
file_path: Absolute path to agent Markdown file
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Relative path from repo root, or None if not found
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
Input: ~/.claude-mpm/cache/remote-agents/bobmatnyc/claude-mpm-agents/agents/pm.md
|
|
130
|
+
Output: "agents/pm.md"
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
# Find "remote-agents" in the path
|
|
134
|
+
path_parts = file_path.parts
|
|
135
|
+
remote_agents_idx = -1
|
|
136
|
+
|
|
137
|
+
for i, part in enumerate(path_parts):
|
|
138
|
+
if part == "remote-agents":
|
|
139
|
+
remote_agents_idx = i
|
|
140
|
+
break
|
|
141
|
+
|
|
142
|
+
if remote_agents_idx == -1 or remote_agents_idx + 3 >= len(path_parts):
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
# Path after owner/repo is the source path
|
|
146
|
+
# remote-agents/{owner}/{repo}/{source_path}
|
|
147
|
+
repo_root_idx = remote_agents_idx + 3
|
|
148
|
+
source_parts = path_parts[repo_root_idx:]
|
|
149
|
+
|
|
150
|
+
return "/".join(source_parts)
|
|
151
|
+
|
|
152
|
+
except Exception as e:
|
|
153
|
+
self.logger.warning(f"Failed to extract source_path from {file_path}: {e}")
|
|
154
|
+
return None
|
|
155
|
+
|
|
68
156
|
def _generate_hierarchical_id(self, file_path: Path) -> str:
|
|
69
157
|
"""Generate hierarchical agent ID from file path.
|
|
70
158
|
|
|
@@ -275,17 +363,34 @@ class RemoteAgentDiscoveryService:
|
|
|
275
363
|
# Bug #1 fix: Detect category from directory path
|
|
276
364
|
category = self._detect_category_from_path(md_file)
|
|
277
365
|
|
|
366
|
+
# NEW: Extract collection metadata from path
|
|
367
|
+
collection_id = self._extract_collection_id_from_path(md_file)
|
|
368
|
+
source_path = self._extract_source_path_from_file(md_file)
|
|
369
|
+
|
|
370
|
+
# NEW: Generate canonical_id (collection_id:agent_id)
|
|
371
|
+
if collection_id:
|
|
372
|
+
canonical_id = f"{collection_id}:{agent_id}"
|
|
373
|
+
else:
|
|
374
|
+
# Fallback for legacy agents without collection
|
|
375
|
+
canonical_id = f"legacy:{agent_id}"
|
|
376
|
+
|
|
278
377
|
# Convert to JSON template format and return
|
|
279
378
|
# IMPORTANT: Include 'path' field for compatibility with deployment validation (ticket 1M-480)
|
|
280
379
|
# Git-sourced agents must have 'path' field to match structure from AgentDiscoveryService
|
|
281
380
|
return {
|
|
282
381
|
"agent_id": agent_id,
|
|
382
|
+
"canonical_id": canonical_id, # NEW: Primary matching key
|
|
383
|
+
"collection_id": collection_id, # NEW: Collection identifier
|
|
384
|
+
"source_path": source_path, # NEW: Path within repository
|
|
283
385
|
"metadata": {
|
|
284
386
|
"name": name,
|
|
285
387
|
"description": description,
|
|
286
388
|
"version": version,
|
|
287
389
|
"author": "remote", # Mark as remote agent
|
|
288
390
|
"category": category, # Use detected category from path
|
|
391
|
+
"collection_id": collection_id, # NEW: Also in metadata
|
|
392
|
+
"source_path": source_path, # NEW: Also in metadata
|
|
393
|
+
"canonical_id": canonical_id, # NEW: Also in metadata
|
|
289
394
|
},
|
|
290
395
|
"model": model,
|
|
291
396
|
"source": "remote", # Mark as remote agent
|
|
@@ -347,7 +452,12 @@ class RemoteAgentDiscoveryService:
|
|
|
347
452
|
Returns:
|
|
348
453
|
RemoteAgentMetadata if found, None otherwise
|
|
349
454
|
"""
|
|
350
|
-
|
|
455
|
+
# Bug #4 fix: Search in /agents/ subdirectory, not root directory
|
|
456
|
+
agents_dir = self.remote_agents_dir / "agents"
|
|
457
|
+
if not agents_dir.exists():
|
|
458
|
+
return None
|
|
459
|
+
|
|
460
|
+
for md_file in agents_dir.rglob("*.md"):
|
|
351
461
|
agent_dict = self._parse_markdown_agent(md_file)
|
|
352
462
|
if agent_dict and agent_dict["metadata"]["name"] == agent_name:
|
|
353
463
|
return RemoteAgentMetadata(
|
|
@@ -359,5 +469,89 @@ class RemoteAgentDiscoveryService:
|
|
|
359
469
|
routing_priority=agent_dict["routing"]["priority"],
|
|
360
470
|
source_file=Path(agent_dict["source_file"]),
|
|
361
471
|
version=agent_dict["version"],
|
|
472
|
+
collection_id=agent_dict.get("collection_id"),
|
|
473
|
+
source_path=agent_dict.get("source_path"),
|
|
474
|
+
canonical_id=agent_dict.get("canonical_id"),
|
|
362
475
|
)
|
|
363
476
|
return None
|
|
477
|
+
|
|
478
|
+
def get_agents_by_collection(self, collection_id: str) -> List[Dict[str, Any]]:
|
|
479
|
+
"""Get all agents belonging to a specific collection.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
collection_id: Collection identifier in format "owner/repo-name"
|
|
483
|
+
|
|
484
|
+
Returns:
|
|
485
|
+
List of agent dictionaries from the specified collection
|
|
486
|
+
|
|
487
|
+
Example:
|
|
488
|
+
>>> service = RemoteAgentDiscoveryService(Path("~/.claude-mpm/cache/remote-agents"))
|
|
489
|
+
>>> agents = service.get_agents_by_collection("bobmatnyc/claude-mpm-agents")
|
|
490
|
+
>>> len(agents)
|
|
491
|
+
45
|
|
492
|
+
"""
|
|
493
|
+
all_agents = self.discover_remote_agents()
|
|
494
|
+
|
|
495
|
+
# Filter by collection_id
|
|
496
|
+
collection_agents = [
|
|
497
|
+
agent for agent in all_agents if agent.get("collection_id") == collection_id
|
|
498
|
+
]
|
|
499
|
+
|
|
500
|
+
self.logger.info(
|
|
501
|
+
f"Found {len(collection_agents)} agents in collection '{collection_id}'"
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
return collection_agents
|
|
505
|
+
|
|
506
|
+
def list_collections(self) -> List[Dict[str, Any]]:
|
|
507
|
+
"""List all available collections with agent counts.
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
List of collection info dictionaries with:
|
|
511
|
+
- collection_id: Collection identifier
|
|
512
|
+
- agent_count: Number of agents in collection
|
|
513
|
+
- agents: List of agent IDs in collection
|
|
514
|
+
|
|
515
|
+
Example:
|
|
516
|
+
>>> service = RemoteAgentDiscoveryService(Path("~/.claude-mpm/cache/remote-agents"))
|
|
517
|
+
>>> collections = service.list_collections()
|
|
518
|
+
>>> collections
|
|
519
|
+
[
|
|
520
|
+
{
|
|
521
|
+
"collection_id": "bobmatnyc/claude-mpm-agents",
|
|
522
|
+
"agent_count": 45,
|
|
523
|
+
"agents": ["pm", "engineer", "qa", ...]
|
|
524
|
+
}
|
|
525
|
+
]
|
|
526
|
+
"""
|
|
527
|
+
all_agents = self.discover_remote_agents()
|
|
528
|
+
|
|
529
|
+
# Group by collection_id
|
|
530
|
+
collections_map: Dict[str, List[str]] = {}
|
|
531
|
+
|
|
532
|
+
for agent in all_agents:
|
|
533
|
+
collection_id = agent.get("collection_id")
|
|
534
|
+
if not collection_id:
|
|
535
|
+
# Skip agents without collection (legacy)
|
|
536
|
+
continue
|
|
537
|
+
|
|
538
|
+
if collection_id not in collections_map:
|
|
539
|
+
collections_map[collection_id] = []
|
|
540
|
+
|
|
541
|
+
agent_id = agent.get("agent_id", agent.get("metadata", {}).get("name"))
|
|
542
|
+
if agent_id:
|
|
543
|
+
collections_map[collection_id].append(agent_id)
|
|
544
|
+
|
|
545
|
+
# Convert to list format
|
|
546
|
+
collections = [
|
|
547
|
+
{
|
|
548
|
+
"collection_id": coll_id,
|
|
549
|
+
"agent_count": len(agent_ids),
|
|
550
|
+
"agents": sorted(agent_ids),
|
|
551
|
+
}
|
|
552
|
+
for coll_id, agent_ids in collections_map.items()
|
|
553
|
+
]
|
|
554
|
+
|
|
555
|
+
self.logger.info(f"Found {len(collections)} collections")
|
|
556
|
+
|
|
557
|
+
return collections
|
|
@@ -188,10 +188,10 @@ class GitSourceSyncService:
|
|
|
188
188
|
|
|
189
189
|
Args:
|
|
190
190
|
source_url: Base URL for raw files (without trailing slash)
|
|
191
|
-
cache_dir: Local cache directory (defaults to ~/.claude-mpm/cache/agents/)
|
|
191
|
+
cache_dir: Local cache directory (defaults to ~/.claude-mpm/cache/remote-agents/)
|
|
192
192
|
source_id: Unique identifier for this source (for multi-source support)
|
|
193
193
|
|
|
194
|
-
Design Decision: Cache to ~/.claude-mpm/cache/agents/ (
|
|
194
|
+
Design Decision: Cache to ~/.claude-mpm/cache/remote-agents/ (canonical location)
|
|
195
195
|
|
|
196
196
|
Rationale: Separates cached repository structure from deployed agents.
|
|
197
197
|
This allows preserving nested directory structure in cache while
|
|
@@ -207,13 +207,13 @@ class GitSourceSyncService:
|
|
|
207
207
|
self.source_url = source_url.rstrip("/")
|
|
208
208
|
self.source_id = source_id
|
|
209
209
|
|
|
210
|
-
# Setup cache directory (
|
|
210
|
+
# Setup cache directory (canonical: ~/.claude-mpm/cache/remote-agents/)
|
|
211
211
|
if cache_dir:
|
|
212
212
|
self.cache_dir = Path(cache_dir)
|
|
213
213
|
else:
|
|
214
|
-
# Default to ~/.claude-mpm/cache/agents/ (
|
|
214
|
+
# Default to ~/.claude-mpm/cache/remote-agents/ (canonical cache location)
|
|
215
215
|
home = Path.home()
|
|
216
|
-
self.cache_dir = home / ".claude-mpm" / "cache" / "agents"
|
|
216
|
+
self.cache_dir = home / ".claude-mpm" / "cache" / "remote-agents"
|
|
217
217
|
|
|
218
218
|
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
219
219
|
|
|
@@ -237,6 +237,11 @@ class GitSourceSyncService:
|
|
|
237
237
|
if etag_cache_file.exists():
|
|
238
238
|
self._migrate_etag_cache(etag_cache_file)
|
|
239
239
|
|
|
240
|
+
# NEW: Initialize git manager for cache (Phase 1 integration)
|
|
241
|
+
from claude_mpm.services.agents.cache_git_manager import CacheGitManager
|
|
242
|
+
|
|
243
|
+
self.git_manager = CacheGitManager(self.cache_dir)
|
|
244
|
+
|
|
240
245
|
def sync_agents(
|
|
241
246
|
self,
|
|
242
247
|
force_refresh: bool = False,
|
|
@@ -273,6 +278,33 @@ class GitSourceSyncService:
|
|
|
273
278
|
|
|
274
279
|
start_time = time.time()
|
|
275
280
|
|
|
281
|
+
# NEW: Pre-sync git operations (Phase 1 integration)
|
|
282
|
+
if self.git_manager.is_git_repo():
|
|
283
|
+
logger.debug("Cache is a git repository, checking for updates...")
|
|
284
|
+
|
|
285
|
+
# Warn about uncommitted changes
|
|
286
|
+
if self.git_manager.has_uncommitted_changes():
|
|
287
|
+
uncommitted_count = len(
|
|
288
|
+
self.git_manager.get_status().get("uncommitted", [])
|
|
289
|
+
)
|
|
290
|
+
logger.warning(
|
|
291
|
+
f"Cache has {uncommitted_count} uncommitted change(s). "
|
|
292
|
+
"These will be preserved, but consider committing them."
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Pull latest if online (non-blocking)
|
|
296
|
+
try:
|
|
297
|
+
success, msg = self.git_manager.pull_latest()
|
|
298
|
+
if success:
|
|
299
|
+
logger.info(f"✅ Git pull: {msg}")
|
|
300
|
+
else:
|
|
301
|
+
logger.warning(f"⚠️ Git pull failed: {msg}")
|
|
302
|
+
logger.info("Continuing with HTTP sync as fallback")
|
|
303
|
+
except Exception as e:
|
|
304
|
+
logger.warning(f"Git pull error (continuing with HTTP sync): {e}")
|
|
305
|
+
else:
|
|
306
|
+
logger.debug("Cache is not a git repository, skipping git operations")
|
|
307
|
+
|
|
276
308
|
results = {
|
|
277
309
|
"synced": [],
|
|
278
310
|
"cached": [],
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Analysis services for Claude MPM.
|
|
3
|
+
|
|
4
|
+
Provides postmortem analysis and error improvement suggestions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .postmortem_service import (
|
|
8
|
+
ActionType,
|
|
9
|
+
ErrorAnalysis,
|
|
10
|
+
ErrorCategory,
|
|
11
|
+
ImprovementAction,
|
|
12
|
+
PostmortemReport,
|
|
13
|
+
PostmortemService,
|
|
14
|
+
get_postmortem_service,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"ActionType",
|
|
19
|
+
"ErrorAnalysis",
|
|
20
|
+
"ErrorCategory",
|
|
21
|
+
"ImprovementAction",
|
|
22
|
+
"PostmortemReport",
|
|
23
|
+
"PostmortemService",
|
|
24
|
+
"get_postmortem_service",
|
|
25
|
+
]
|