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
|
@@ -311,42 +311,17 @@ def _remove_test_mode_instructions(content: str) -> str:
|
|
|
311
311
|
Returns:
|
|
312
312
|
str: Content with test-mode instructions removed
|
|
313
313
|
"""
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if line.strip() == "## Standard Test Response Protocol":
|
|
324
|
-
skip_section = True
|
|
325
|
-
i += 1
|
|
326
|
-
continue
|
|
327
|
-
|
|
328
|
-
# Check if we're in the test section and need to continue skipping
|
|
329
|
-
if skip_section:
|
|
330
|
-
# Check if we've reached a new top-level section (## but not ###)
|
|
331
|
-
# Only stop skipping when we hit another ## section (same level as test section)
|
|
332
|
-
if line.startswith("##") and not line.startswith("###"):
|
|
333
|
-
skip_section = False
|
|
334
|
-
# Don't skip this line - it's the start of a new section
|
|
335
|
-
filtered_lines.append(line)
|
|
336
|
-
i += 1
|
|
337
|
-
continue
|
|
338
|
-
# Skip this line as we're still in test section (includes ### subsections)
|
|
339
|
-
i += 1
|
|
340
|
-
continue
|
|
341
|
-
|
|
342
|
-
# Not in test section, keep the line
|
|
343
|
-
filtered_lines.append(line)
|
|
344
|
-
i += 1
|
|
345
|
-
|
|
346
|
-
# Join back and clean up extra blank lines
|
|
347
|
-
result = "\n".join(filtered_lines)
|
|
314
|
+
import re
|
|
315
|
+
|
|
316
|
+
# Pattern matches from "## Standard Test Response Protocol"
|
|
317
|
+
# until the next "##" (but not "###") or end of string
|
|
318
|
+
# Uses negative lookahead to stop at ## but not ###
|
|
319
|
+
pattern = r"## Standard Test Response Protocol\n.*?(?=\n##(?!#)|\Z)"
|
|
320
|
+
|
|
321
|
+
# Remove the test section (DOTALL allows . to match newlines)
|
|
322
|
+
result = re.sub(pattern, "", content, flags=re.DOTALL)
|
|
348
323
|
|
|
349
|
-
#
|
|
324
|
+
# Clean up multiple consecutive newlines
|
|
350
325
|
while "\n\n\n" in result:
|
|
351
326
|
result = result.replace("\n\n\n", "\n\n")
|
|
352
327
|
|
|
@@ -143,6 +143,10 @@ class FrontmatterValidator:
|
|
|
143
143
|
"dependencies",
|
|
144
144
|
"capabilities",
|
|
145
145
|
"color",
|
|
146
|
+
# NEW: Collection-based identification fields
|
|
147
|
+
"collection_id",
|
|
148
|
+
"source_path",
|
|
149
|
+
"canonical_id",
|
|
146
150
|
}
|
|
147
151
|
|
|
148
152
|
def validate_and_correct(self, frontmatter: Dict[str, Any]) -> ValidationResult:
|
|
@@ -176,6 +180,8 @@ class FrontmatterValidator:
|
|
|
176
180
|
self._validate_author_field(corrected, errors, warnings)
|
|
177
181
|
self._validate_tags_field(corrected, errors, warnings)
|
|
178
182
|
self._validate_numeric_fields(corrected, errors, warnings)
|
|
183
|
+
# NEW: Validate collection-based identification fields
|
|
184
|
+
self._validate_collection_fields(corrected, field_corrections, errors, warnings)
|
|
179
185
|
|
|
180
186
|
# Determine if valid
|
|
181
187
|
is_valid = len(errors) == 0
|
|
@@ -464,6 +470,68 @@ class FrontmatterValidator:
|
|
|
464
470
|
f"Field '{field_name}' value {value} outside recommended range [{min_val}, {max_val}]"
|
|
465
471
|
)
|
|
466
472
|
|
|
473
|
+
def _validate_collection_fields(
|
|
474
|
+
self,
|
|
475
|
+
corrected: Dict[str, Any],
|
|
476
|
+
field_corrections: Dict[str, Any],
|
|
477
|
+
errors: List[str],
|
|
478
|
+
warnings: List[str],
|
|
479
|
+
) -> None:
|
|
480
|
+
"""Validate collection-based identification fields.
|
|
481
|
+
|
|
482
|
+
NEW: Validates collection_id, source_path, and canonical_id fields.
|
|
483
|
+
|
|
484
|
+
These fields are auto-populated by RemoteAgentDiscoveryService for remote agents
|
|
485
|
+
and should follow specific formats:
|
|
486
|
+
- collection_id: "owner/repo-name" (e.g., "bobmatnyc/claude-mpm-agents")
|
|
487
|
+
- source_path: Relative path in repo (e.g., "agents/pm.md")
|
|
488
|
+
- canonical_id: "collection_id:agent_id" or "legacy:filename"
|
|
489
|
+
"""
|
|
490
|
+
# Validate collection_id format (optional field)
|
|
491
|
+
if "collection_id" in corrected:
|
|
492
|
+
collection_id = corrected["collection_id"]
|
|
493
|
+
if not isinstance(collection_id, str):
|
|
494
|
+
errors.append(
|
|
495
|
+
f"Field 'collection_id' must be a string, got {type(collection_id).__name__}"
|
|
496
|
+
)
|
|
497
|
+
elif "/" not in collection_id:
|
|
498
|
+
warnings.append(
|
|
499
|
+
f"Field 'collection_id' should be in format 'owner/repo-name', got '{collection_id}'"
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
# Validate source_path format (optional field)
|
|
503
|
+
if "source_path" in corrected:
|
|
504
|
+
source_path = corrected["source_path"]
|
|
505
|
+
if not isinstance(source_path, str):
|
|
506
|
+
errors.append(
|
|
507
|
+
f"Field 'source_path' must be a string, got {type(source_path).__name__}"
|
|
508
|
+
)
|
|
509
|
+
|
|
510
|
+
# Validate canonical_id format (optional field)
|
|
511
|
+
if "canonical_id" in corrected:
|
|
512
|
+
canonical_id = corrected["canonical_id"]
|
|
513
|
+
if not isinstance(canonical_id, str):
|
|
514
|
+
errors.append(
|
|
515
|
+
f"Field 'canonical_id' must be a string, got {type(canonical_id).__name__}"
|
|
516
|
+
)
|
|
517
|
+
elif ":" not in canonical_id:
|
|
518
|
+
warnings.append(
|
|
519
|
+
f"Field 'canonical_id' should be in format 'collection:agent_id' or 'legacy:filename', got '{canonical_id}'"
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
# Auto-generate canonical_id if collection_id is present but canonical_id is missing
|
|
523
|
+
if "collection_id" in corrected and "canonical_id" not in corrected:
|
|
524
|
+
collection_id = corrected["collection_id"]
|
|
525
|
+
agent_id = corrected.get("name", "unknown")
|
|
526
|
+
|
|
527
|
+
# Generate canonical_id
|
|
528
|
+
canonical_id = f"{collection_id}:{agent_id}"
|
|
529
|
+
corrected["canonical_id"] = canonical_id
|
|
530
|
+
field_corrections["canonical_id"] = canonical_id
|
|
531
|
+
warnings.append(
|
|
532
|
+
f"Auto-generated canonical_id: '{canonical_id}' from collection_id and name"
|
|
533
|
+
)
|
|
534
|
+
|
|
467
535
|
def _normalize_model(self, model: str) -> str:
|
|
468
536
|
"""
|
|
469
537
|
Normalize model name to standard tier using ModelTier enum.
|
|
@@ -134,73 +134,265 @@ PM: Task(agent="engineer", task="Add express dependency to package.json")
|
|
|
134
134
|
|
|
135
135
|
## Circuit Breaker #2: Investigation Detection
|
|
136
136
|
|
|
137
|
-
**Purpose**:
|
|
137
|
+
**Purpose**: Block PM from investigation work through pre-action enforcement
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
**Effectiveness Target**: 95% compliance (upgraded from 40% reactive detection)
|
|
140
|
+
**Model**: Pre-action blocking pattern (Circuit Breaker #6 architecture)
|
|
141
|
+
**Related Tests**: `tests/one-shot/pm-investigation-violations/test_001.md` through `test_005.md`
|
|
142
|
+
**Research Analysis**: `docs/research/pm-investigation-violation-analysis.md`
|
|
140
143
|
|
|
141
|
-
|
|
144
|
+
### Core Principle
|
|
142
145
|
|
|
143
|
-
|
|
144
|
-
- Reading more than 1 file per session
|
|
145
|
-
- Using `Read` tool for code exploration
|
|
146
|
-
- Checking file contents for investigation
|
|
147
|
-
- Reading documentation for understanding
|
|
146
|
+
PM must detect investigation intent BEFORE using investigation tools. This circuit breaker enforces mandatory Research delegation for any task requiring code analysis, multi-file reading, or solution exploration.
|
|
148
147
|
|
|
149
|
-
|
|
150
|
-
- Using `Grep` tool for code search
|
|
151
|
-
- Using `Glob` tool for file discovery
|
|
152
|
-
- Using `WebSearch` or `WebFetch` for research
|
|
153
|
-
- Analyzing code patterns or architecture
|
|
148
|
+
### Pre-Action Blocking Protocol
|
|
154
149
|
|
|
155
|
-
|
|
156
|
-
- Searching for solutions or approaches
|
|
157
|
-
- Examining dependencies or imports
|
|
158
|
-
- Checking logs for debugging
|
|
159
|
-
- Running git commands for history (`git log`, `git blame`)
|
|
150
|
+
**MANDATORY: PM checks for investigation signals before tool execution**
|
|
160
151
|
|
|
161
|
-
|
|
152
|
+
#### Step 1: User Request Analysis (BLOCKING)
|
|
162
153
|
|
|
163
|
-
|
|
154
|
+
Before any tool use, PM analyzes user request for investigation triggers:
|
|
164
155
|
|
|
165
|
-
|
|
156
|
+
**Investigation Trigger Keywords**:
|
|
166
157
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
158
|
+
| Category | Keywords | Action |
|
|
159
|
+
|----------|----------|--------|
|
|
160
|
+
| **Investigation Verbs** | "investigate", "check", "look at", "explore", "examine" | Block → Delegate to Research |
|
|
161
|
+
| **Analysis Requests** | "analyze", "review", "inspect", "understand", "figure out" | Block → Delegate to Research |
|
|
162
|
+
| **Problem Diagnosis** | "debug", "find out", "what's wrong", "why is", "how does" | Block → Delegate to Research |
|
|
163
|
+
| **Code Exploration** | "see what", "show me", "where is", "find the code" | Block → Delegate to Research |
|
|
172
164
|
|
|
173
|
-
|
|
165
|
+
**Detection Rule**: If user request contains ANY trigger keyword → PM MUST delegate to Research BEFORE using Read/Grep/Glob/WebSearch/WebFetch tools.
|
|
174
166
|
|
|
175
|
-
|
|
167
|
+
**Example**:
|
|
168
|
+
```
|
|
169
|
+
User: "Investigate why authentication is failing"
|
|
170
|
+
↓
|
|
171
|
+
PM detects: "investigate" (trigger keyword)
|
|
172
|
+
↓
|
|
173
|
+
BLOCK: Read/Grep/Glob tools forbidden
|
|
174
|
+
↓
|
|
175
|
+
PM delegates: Task(agent="research", task="Investigate authentication failure")
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### Step 2: PM Self-Awareness Check (BLOCKING)
|
|
179
|
+
|
|
180
|
+
PM monitors own statements for investigation language:
|
|
181
|
+
|
|
182
|
+
**Self-Detection Triggers**:
|
|
183
|
+
|
|
184
|
+
| PM Statement | Violation Type | Required Self-Correction |
|
|
185
|
+
|--------------|----------------|--------------------------|
|
|
186
|
+
| "I'll investigate..." | Investigation intent | "I'll have Research investigate..." |
|
|
187
|
+
| "Let me check..." | Investigation intent | "I'll delegate to Research to check..." |
|
|
188
|
+
| "I'll look at..." | Investigation intent | "I'll have Research analyze..." |
|
|
189
|
+
| "I'll analyze..." | Investigation intent | "I'll delegate to Research to analyze..." |
|
|
190
|
+
| "I'll explore..." | Investigation intent | "I'll have Research explore..." |
|
|
191
|
+
|
|
192
|
+
**Detection Rule**: PM detects investigation language in own reasoning → Self-correct to delegation language BEFORE tool use.
|
|
193
|
+
|
|
194
|
+
**Example**:
|
|
195
|
+
```
|
|
196
|
+
PM thinks: "I'll investigate this bug..."
|
|
197
|
+
↓
|
|
198
|
+
PM detects: "investigate" in own statement (trigger)
|
|
199
|
+
↓
|
|
200
|
+
PM corrects: "I'll have Research investigate this bug..."
|
|
201
|
+
↓
|
|
202
|
+
PM delegates: Task(agent="research", task="...")
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### Step 3: Read Tool Limit Enforcement (BLOCKING)
|
|
206
|
+
|
|
207
|
+
**Absolute Rule**: PM can read EXACTLY ONE file per task for delegation context only.
|
|
208
|
+
|
|
209
|
+
**Pre-Read Checkpoint** (MANDATORY before Read tool):
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
def before_read_tool(file_path, task_context):
|
|
213
|
+
# Checkpoint 1: Investigation keywords present?
|
|
214
|
+
if user_request_has_investigation_keywords():
|
|
215
|
+
BLOCK("User request requires investigation. Delegate to Research. Zero reads allowed.")
|
|
216
|
+
|
|
217
|
+
# Checkpoint 2: Already used Read once?
|
|
218
|
+
if read_count_this_task >= 1:
|
|
219
|
+
BLOCK("PM already read one file. Second read forbidden. Delegate to Research.")
|
|
220
|
+
|
|
221
|
+
# Checkpoint 3: Source code file?
|
|
222
|
+
if is_source_code(file_path): # .py, .js, .ts, .java, .go, etc.
|
|
223
|
+
BLOCK("PM cannot read source code. Delegate to Research for code investigation.")
|
|
224
|
+
|
|
225
|
+
# Checkpoint 4: Task requires codebase understanding?
|
|
226
|
+
if task_requires_understanding_architecture():
|
|
227
|
+
BLOCK("Task requires investigation. Delegate to Research. Zero reads allowed.")
|
|
228
|
+
|
|
229
|
+
# All checkpoints passed - allow ONE file read
|
|
230
|
+
read_count_this_task += 1
|
|
231
|
+
ALLOW(file_path)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Blocking Conditions**:
|
|
235
|
+
- Read count ≥ 1 → Block second read
|
|
236
|
+
- Source code file → Block (any .py/.js/.ts/.java/.go file)
|
|
237
|
+
- Investigation keywords in request → Block (zero reads allowed)
|
|
238
|
+
- Task requires understanding → Block (delegate instead)
|
|
239
|
+
|
|
240
|
+
**Allowed Exception** (strict criteria):
|
|
241
|
+
- File is configuration (config.json, database.yaml, package.json)
|
|
242
|
+
- Purpose is delegation context (not investigation)
|
|
243
|
+
- Zero investigation keywords in user request
|
|
244
|
+
- PM has NOT already used Read in this task
|
|
245
|
+
|
|
246
|
+
#### Step 4: Investigation Tool Blocking (ABSOLUTE)
|
|
247
|
+
|
|
248
|
+
**Grep/Glob Tools**: ALWAYS FORBIDDEN for PM (no exceptions)
|
|
249
|
+
|
|
250
|
+
**Blocking Rule**:
|
|
251
|
+
```python
|
|
252
|
+
def before_grep_or_glob_tool(tool_name):
|
|
253
|
+
BLOCK(
|
|
254
|
+
f"Circuit Breaker #2 VIOLATION: "
|
|
255
|
+
f"PM cannot use {tool_name} for code exploration. "
|
|
256
|
+
f"MUST delegate to Research agent."
|
|
257
|
+
)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**WebSearch/WebFetch Tools**: ALWAYS FORBIDDEN for PM (no exceptions)
|
|
261
|
+
|
|
262
|
+
**Blocking Rule**:
|
|
263
|
+
```python
|
|
264
|
+
def before_web_research_tool(tool_name):
|
|
265
|
+
BLOCK(
|
|
266
|
+
f"Circuit Breaker #2 VIOLATION: "
|
|
267
|
+
f"PM cannot use {tool_name} for research. "
|
|
268
|
+
f"MUST delegate to Research agent."
|
|
269
|
+
)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Rationale**: These tools are investigation tools by design. PM using them indicates investigation work that must be delegated.
|
|
273
|
+
|
|
274
|
+
### Trigger Conditions Summary
|
|
275
|
+
|
|
276
|
+
**BLOCK immediately if PM attempts**:
|
|
277
|
+
|
|
278
|
+
1. **Investigation Keywords Detected**
|
|
279
|
+
- User says: "investigate", "check", "analyze", "explore", "debug"
|
|
280
|
+
- PM must delegate BEFORE using Read/Grep/Glob/WebSearch
|
|
176
281
|
|
|
177
|
-
**
|
|
282
|
+
2. **PM Self-Investigation Statements**
|
|
283
|
+
- PM says: "I'll investigate", "let me check", "I'll look at"
|
|
284
|
+
- PM must self-correct to delegation language
|
|
178
285
|
|
|
179
|
-
|
|
286
|
+
3. **Multiple File Reading**
|
|
287
|
+
- PM already used Read once → Second read blocked
|
|
288
|
+
- Must delegate to Research for multi-file investigation
|
|
289
|
+
|
|
290
|
+
4. **Source Code Reading**
|
|
291
|
+
- PM attempts Read on .py/.js/.ts/.java/.go files → Blocked
|
|
292
|
+
- Must delegate to Research for code investigation
|
|
293
|
+
|
|
294
|
+
5. **Investigation Tools**
|
|
295
|
+
- Grep/Glob/WebSearch/WebFetch → Always blocked
|
|
296
|
+
- Must delegate to Research (no exceptions)
|
|
297
|
+
|
|
298
|
+
### Violation Response
|
|
299
|
+
|
|
300
|
+
**→ BLOCK BEFORE TOOL EXECUTION**
|
|
301
|
+
|
|
302
|
+
**→ ERROR MESSAGE**:
|
|
303
|
+
```
|
|
304
|
+
"Circuit Breaker #2 VIOLATION: [specific violation]
|
|
305
|
+
PM cannot investigate directly.
|
|
306
|
+
MUST delegate to Research agent."
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**→ REQUIRED ACTION**: Immediate delegation to Research agent
|
|
310
|
+
|
|
311
|
+
**→ VIOLATIONS LOGGED**: Track for session compliance report
|
|
312
|
+
|
|
313
|
+
### Delegation Targets
|
|
314
|
+
|
|
315
|
+
**Delegate investigation work to**:
|
|
316
|
+
- **Research**: Code investigation, multi-file analysis, web research, documentation reading
|
|
317
|
+
- **Code Analyzer**: Architecture review, pattern analysis (after Research provides context)
|
|
318
|
+
- **Ops**: Log analysis, debugging production issues
|
|
319
|
+
- **Version Control**: Git history investigation, code evolution analysis
|
|
180
320
|
|
|
181
321
|
### Examples
|
|
182
322
|
|
|
183
|
-
####
|
|
323
|
+
#### Pre-Action Blocking (CORRECT)
|
|
184
324
|
|
|
185
325
|
```
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
PM:
|
|
189
|
-
|
|
190
|
-
PM:
|
|
191
|
-
|
|
326
|
+
User: "Investigate authentication failure"
|
|
327
|
+
↓
|
|
328
|
+
PM detects: "investigate" keyword
|
|
329
|
+
↓
|
|
330
|
+
PM blocks: Read/Grep/Glob tools (BEFORE use)
|
|
331
|
+
↓
|
|
332
|
+
PM delegates: Task(agent="research", task="Investigate authentication failure")
|
|
333
|
+
↓
|
|
334
|
+
Tool usage count: 0 (zero tools used by PM)
|
|
192
335
|
```
|
|
193
336
|
|
|
194
|
-
####
|
|
337
|
+
#### Self-Correction (CORRECT)
|
|
195
338
|
|
|
196
339
|
```
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
PM:
|
|
200
|
-
|
|
201
|
-
PM:
|
|
340
|
+
User: "Check why login is broken"
|
|
341
|
+
↓
|
|
342
|
+
PM thinks: "I'll investigate the login code..."
|
|
343
|
+
↓
|
|
344
|
+
PM detects: "investigate" in own statement
|
|
345
|
+
↓
|
|
346
|
+
PM corrects: "I'll have Research investigate..."
|
|
347
|
+
↓
|
|
348
|
+
PM delegates: Task(agent="research", task="Investigate login bug")
|
|
202
349
|
```
|
|
203
350
|
|
|
351
|
+
#### Read Limit Enforcement (CORRECT)
|
|
352
|
+
|
|
353
|
+
```
|
|
354
|
+
User: "Check auth and session code"
|
|
355
|
+
↓
|
|
356
|
+
PM detects: "check" + multiple components
|
|
357
|
+
↓
|
|
358
|
+
PM reasoning: "Would need to read auth.js AND session.js (>1 file)"
|
|
359
|
+
↓
|
|
360
|
+
PM blocks: Read tool (BEFORE first read)
|
|
361
|
+
↓
|
|
362
|
+
PM delegates: Task(agent="research", task="Analyze auth and session code")
|
|
363
|
+
↓
|
|
364
|
+
Read count: 0 (zero reads by PM)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
#### Violation Examples (BLOCKED)
|
|
368
|
+
|
|
369
|
+
```
|
|
370
|
+
❌ PM: Read("src/auth.js") then Read("src/session.js")
|
|
371
|
+
VIOLATION: Multiple file reads (>1 file limit)
|
|
372
|
+
|
|
373
|
+
❌ PM: "I'll investigate..." then uses Read tool
|
|
374
|
+
VIOLATION: Investigation language detected, proceeded anyway
|
|
375
|
+
|
|
376
|
+
❌ PM: Grep(pattern="authentication")
|
|
377
|
+
VIOLATION: Investigation tool usage (Grep always forbidden)
|
|
378
|
+
|
|
379
|
+
❌ PM: User says "investigate", PM uses Read
|
|
380
|
+
VIOLATION: Investigation keyword ignored, proceeded with tools
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Success Metrics
|
|
384
|
+
|
|
385
|
+
**Target Effectiveness**: 95% compliance
|
|
386
|
+
|
|
387
|
+
**Measurement Criteria**:
|
|
388
|
+
1. **Trigger Word Detection**: 90%+ of investigation keywords detected
|
|
389
|
+
2. **Self-Awareness**: 85%+ of PM investigation statements self-corrected
|
|
390
|
+
3. **Pre-Action Blocking**: 95%+ of blocks occur BEFORE tool use
|
|
391
|
+
4. **Read Limit Compliance**: 98%+ tasks follow one-file maximum rule
|
|
392
|
+
5. **Overall Violation Rate**: <10% sessions with Circuit Breaker #2 violations
|
|
393
|
+
|
|
394
|
+
**Test Validation**: All 5 test cases in `tests/one-shot/pm-investigation-violations/` must pass
|
|
395
|
+
|
|
204
396
|
---
|
|
205
397
|
|
|
206
398
|
## Circuit Breaker #3: Unverified Assertion Detection
|
|
@@ -558,11 +750,68 @@ PM: "All test files tracked in git"
|
|
|
558
750
|
|
|
559
751
|
**CRITICAL**: PM MUST NEVER use ticketing tools directly - ALWAYS delegate to ticketing.
|
|
560
752
|
|
|
561
|
-
#### Ticketing Tool Direct Usage
|
|
753
|
+
#### Ticketing Tool Direct Usage (BLOCKING)
|
|
562
754
|
- PM uses any mcp-ticketer tools (`mcp__mcp-ticketer__*`)
|
|
563
755
|
- PM runs aitrackdown CLI commands (`aitrackdown create`, `aitrackdown show`, etc.)
|
|
564
756
|
- PM accesses Linear/GitHub/JIRA APIs directly
|
|
565
757
|
- PM reads/writes ticket data without delegating
|
|
758
|
+
- PM uses WebFetch on ticket URLs (Linear, GitHub, JIRA)
|
|
759
|
+
|
|
760
|
+
#### Pre-Action Enforcement Hook
|
|
761
|
+
|
|
762
|
+
**BEFORE PM uses ANY tool, check:**
|
|
763
|
+
|
|
764
|
+
```python
|
|
765
|
+
# Forbidden tool patterns for PM
|
|
766
|
+
FORBIDDEN_TICKETING_TOOLS = [
|
|
767
|
+
"mcp__mcp-ticketer__", # All mcp-ticketer tools
|
|
768
|
+
"aitrackdown", # CLI commands
|
|
769
|
+
"linear.app", # Linear URLs in WebFetch
|
|
770
|
+
"github.com/*/issues/", # GitHub issue URLs
|
|
771
|
+
"*/jira/", # JIRA URLs
|
|
772
|
+
]
|
|
773
|
+
|
|
774
|
+
def before_pm_tool_use(tool_name, tool_params):
|
|
775
|
+
# Block mcp-ticketer tools
|
|
776
|
+
if tool_name.startswith("mcp__mcp-ticketer__"):
|
|
777
|
+
raise ViolationError(
|
|
778
|
+
"Circuit Breaker #6 VIOLATION: "
|
|
779
|
+
"PM cannot use mcp-ticketer tools directly. "
|
|
780
|
+
"MUST delegate to ticketing agent. "
|
|
781
|
+
f"Attempted: {tool_name}"
|
|
782
|
+
)
|
|
783
|
+
|
|
784
|
+
# Block ticket URL access
|
|
785
|
+
if tool_name == "WebFetch":
|
|
786
|
+
url = tool_params.get("url", "")
|
|
787
|
+
for forbidden in ["linear.app", "github.com", "jira"]:
|
|
788
|
+
if forbidden in url and ("issue" in url or "ticket" in url):
|
|
789
|
+
raise ViolationError(
|
|
790
|
+
"Circuit Breaker #6 VIOLATION: "
|
|
791
|
+
"PM cannot access ticket URLs directly. "
|
|
792
|
+
"MUST delegate to ticketing agent. "
|
|
793
|
+
f"URL: {url}"
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
# Block Bash commands for ticketing CLIs
|
|
797
|
+
if tool_name == "Bash":
|
|
798
|
+
command = tool_params.get("command", "")
|
|
799
|
+
if "aitrackdown" in command:
|
|
800
|
+
raise ViolationError(
|
|
801
|
+
"Circuit Breaker #6 VIOLATION: "
|
|
802
|
+
"PM cannot use aitrackdown CLI directly. "
|
|
803
|
+
"MUST delegate to ticketing agent. "
|
|
804
|
+
f"Command: {command}"
|
|
805
|
+
)
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
#### Tool Usage Detection Patterns
|
|
809
|
+
|
|
810
|
+
**Ticket URL Detection** (triggers delegation):
|
|
811
|
+
- `https://linear.app/*/issue/*` → Delegate to ticketing
|
|
812
|
+
- `https://github.com/*/issues/*` → Delegate to ticketing
|
|
813
|
+
- `https://*/jira/browse/*` → Delegate to ticketing
|
|
814
|
+
- Any URL containing both "ticket" and platform name → Delegate to ticketing
|
|
566
815
|
|
|
567
816
|
### Why This Matters
|
|
568
817
|
|
claude_mpm/cli/__init__.py
CHANGED
|
@@ -19,6 +19,7 @@ from .info import show_info
|
|
|
19
19
|
from .mcp import manage_mcp
|
|
20
20
|
from .memory import manage_memory
|
|
21
21
|
from .monitor import manage_monitor
|
|
22
|
+
from .postmortem import run_postmortem
|
|
22
23
|
from .run import run_session
|
|
23
24
|
from .skills import manage_skills
|
|
24
25
|
from .tickets import list_tickets, manage_tickets
|
|
@@ -41,6 +42,7 @@ __all__ = [
|
|
|
41
42
|
# 'run_guarded_session', # Excluded from default exports (experimental)
|
|
42
43
|
"manage_tickets",
|
|
43
44
|
"run_doctor",
|
|
45
|
+
"run_postmortem",
|
|
44
46
|
"run_session",
|
|
45
47
|
"show_info",
|
|
46
48
|
]
|
|
@@ -169,18 +169,25 @@ class SimpleAgentManager:
|
|
|
169
169
|
# Normalize agent ID (remove -agent suffix if present, replace underscores)
|
|
170
170
|
normalized_id = agent_id.replace("-agent", "").replace("_", "-")
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
)
|
|
172
|
+
# Check if this agent is deployed
|
|
173
|
+
is_deployed = self._is_agent_deployed(normalized_id)
|
|
174
|
+
|
|
175
|
+
agent_config = AgentConfig(
|
|
176
|
+
name=normalized_id,
|
|
177
|
+
description=(
|
|
178
|
+
description[:80] + "..."
|
|
179
|
+
if len(description) > 80
|
|
180
|
+
else description
|
|
181
|
+
),
|
|
182
|
+
dependencies=display_tools,
|
|
182
183
|
)
|
|
183
184
|
|
|
185
|
+
# Set deployment status
|
|
186
|
+
agent_config.is_deployed = is_deployed
|
|
187
|
+
agent_config.source_type = "local"
|
|
188
|
+
|
|
189
|
+
agents.append(agent_config)
|
|
190
|
+
|
|
184
191
|
except (json.JSONDecodeError, KeyError) as e:
|
|
185
192
|
# Log malformed templates but continue
|
|
186
193
|
self.logger.debug(
|
|
@@ -256,7 +263,7 @@ class SimpleAgentManager:
|
|
|
256
263
|
return []
|
|
257
264
|
|
|
258
265
|
def _is_agent_deployed(self, agent_id: str) -> bool:
|
|
259
|
-
"""Check if agent is deployed
|
|
266
|
+
"""Check if agent is deployed (virtual deployment or physical files).
|
|
260
267
|
|
|
261
268
|
Args:
|
|
262
269
|
agent_id: Full agent ID (may include hierarchy like engineer/backend/python-engineer)
|
|
@@ -264,6 +271,46 @@ class SimpleAgentManager:
|
|
|
264
271
|
Returns:
|
|
265
272
|
True if agent is deployed, False otherwise
|
|
266
273
|
"""
|
|
274
|
+
# Check virtual deployment state (primary method)
|
|
275
|
+
deployment_state_paths = [
|
|
276
|
+
Path.cwd() / ".claude" / "agents" / ".mpm_deployment_state",
|
|
277
|
+
Path.home() / ".claude" / "agents" / ".mpm_deployment_state",
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
for state_path in deployment_state_paths:
|
|
281
|
+
if state_path.exists():
|
|
282
|
+
try:
|
|
283
|
+
with state_path.open() as f:
|
|
284
|
+
state = json.load(f)
|
|
285
|
+
|
|
286
|
+
# Check if agent is in deployment state
|
|
287
|
+
agents = state.get("last_check_results", {}).get("agents", {})
|
|
288
|
+
|
|
289
|
+
# Check full agent_id
|
|
290
|
+
if agent_id in agents:
|
|
291
|
+
self.logger.debug(
|
|
292
|
+
f"Agent {agent_id} found in virtual deployment state"
|
|
293
|
+
)
|
|
294
|
+
return True
|
|
295
|
+
|
|
296
|
+
# Check leaf name for hierarchical IDs
|
|
297
|
+
if "/" in agent_id:
|
|
298
|
+
leaf_name = agent_id.split("/")[-1]
|
|
299
|
+
if leaf_name in agents:
|
|
300
|
+
self.logger.debug(
|
|
301
|
+
f"Agent {agent_id} (leaf: {leaf_name}) found in virtual deployment state"
|
|
302
|
+
)
|
|
303
|
+
return True
|
|
304
|
+
except (json.JSONDecodeError, KeyError) as e:
|
|
305
|
+
self.logger.debug(
|
|
306
|
+
f"Failed to read deployment state from {state_path}: {e}"
|
|
307
|
+
)
|
|
308
|
+
continue
|
|
309
|
+
except Exception as e:
|
|
310
|
+
self.logger.debug(f"Unexpected error reading deployment state: {e}")
|
|
311
|
+
continue
|
|
312
|
+
|
|
313
|
+
# Fallback to physical file checks (legacy support)
|
|
267
314
|
# For hierarchical IDs, check both full ID and leaf name
|
|
268
315
|
agent_file_names = [f"{agent_id}.md"]
|
|
269
316
|
|
|
@@ -278,6 +325,9 @@ class SimpleAgentManager:
|
|
|
278
325
|
for agent_file_name in agent_file_names:
|
|
279
326
|
agent_file = project_agents_dir / agent_file_name
|
|
280
327
|
if agent_file.exists():
|
|
328
|
+
self.logger.debug(
|
|
329
|
+
f"Agent {agent_id} found as physical file: {agent_file}"
|
|
330
|
+
)
|
|
281
331
|
return True
|
|
282
332
|
|
|
283
333
|
# Check ~/.claude/agents/ directory (user level)
|
|
@@ -286,6 +336,9 @@ class SimpleAgentManager:
|
|
|
286
336
|
for agent_file_name in agent_file_names:
|
|
287
337
|
agent_file = user_agents_dir / agent_file_name
|
|
288
338
|
if agent_file.exists():
|
|
339
|
+
self.logger.debug(
|
|
340
|
+
f"Agent {agent_id} found as physical file: {agent_file}"
|
|
341
|
+
)
|
|
289
342
|
return True
|
|
290
343
|
|
|
291
344
|
return False
|