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.

Files changed (76) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +1176 -909
  4. claude_mpm/agents/base_agent_loader.py +10 -35
  5. claude_mpm/agents/frontmatter_validator.py +68 -0
  6. claude_mpm/agents/templates/circuit-breakers.md +293 -44
  7. claude_mpm/cli/__init__.py +0 -1
  8. claude_mpm/cli/commands/__init__.py +2 -0
  9. claude_mpm/cli/commands/agent_state_manager.py +64 -11
  10. claude_mpm/cli/commands/agents.py +446 -25
  11. claude_mpm/cli/commands/auto_configure.py +535 -233
  12. claude_mpm/cli/commands/configure.py +545 -89
  13. claude_mpm/cli/commands/postmortem.py +401 -0
  14. claude_mpm/cli/commands/run.py +1 -39
  15. claude_mpm/cli/commands/skills.py +322 -19
  16. claude_mpm/cli/interactive/agent_wizard.py +302 -195
  17. claude_mpm/cli/parsers/agents_parser.py +137 -0
  18. claude_mpm/cli/parsers/auto_configure_parser.py +13 -0
  19. claude_mpm/cli/parsers/base_parser.py +4 -0
  20. claude_mpm/cli/parsers/skills_parser.py +7 -0
  21. claude_mpm/cli/startup.py +73 -32
  22. claude_mpm/commands/mpm-agents-auto-configure.md +2 -2
  23. claude_mpm/commands/mpm-agents-list.md +2 -2
  24. claude_mpm/commands/mpm-config-view.md +2 -2
  25. claude_mpm/commands/mpm-help.md +3 -0
  26. claude_mpm/commands/mpm-postmortem.md +123 -0
  27. claude_mpm/commands/mpm-session-resume.md +2 -2
  28. claude_mpm/commands/mpm-ticket-organize.md +2 -2
  29. claude_mpm/commands/mpm-ticket-view.md +2 -2
  30. claude_mpm/config/agent_presets.py +312 -82
  31. claude_mpm/config/skill_presets.py +392 -0
  32. claude_mpm/constants.py +1 -0
  33. claude_mpm/core/claude_runner.py +2 -25
  34. claude_mpm/core/framework/loaders/file_loader.py +54 -101
  35. claude_mpm/core/interactive_session.py +19 -5
  36. claude_mpm/core/oneshot_session.py +16 -4
  37. claude_mpm/core/output_style_manager.py +173 -43
  38. claude_mpm/core/protocols/__init__.py +23 -0
  39. claude_mpm/core/protocols/runner_protocol.py +103 -0
  40. claude_mpm/core/protocols/session_protocol.py +131 -0
  41. claude_mpm/core/shared/singleton_manager.py +11 -4
  42. claude_mpm/core/system_context.py +38 -0
  43. claude_mpm/core/unified_agent_registry.py +129 -1
  44. claude_mpm/core/unified_config.py +22 -0
  45. claude_mpm/hooks/claude_hooks/memory_integration.py +12 -1
  46. claude_mpm/models/agent_definition.py +7 -0
  47. claude_mpm/services/agents/cache_git_manager.py +621 -0
  48. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +110 -3
  49. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +195 -1
  50. claude_mpm/services/agents/sources/git_source_sync_service.py +37 -5
  51. claude_mpm/services/analysis/__init__.py +25 -0
  52. claude_mpm/services/analysis/postmortem_reporter.py +474 -0
  53. claude_mpm/services/analysis/postmortem_service.py +765 -0
  54. claude_mpm/services/command_deployment_service.py +108 -5
  55. claude_mpm/services/core/base.py +7 -2
  56. claude_mpm/services/diagnostics/checks/mcp_services_check.py +7 -15
  57. claude_mpm/services/git/git_operations_service.py +8 -8
  58. claude_mpm/services/mcp_config_manager.py +75 -145
  59. claude_mpm/services/mcp_gateway/core/process_pool.py +22 -16
  60. claude_mpm/services/mcp_service_verifier.py +6 -3
  61. claude_mpm/services/monitor/daemon.py +28 -8
  62. claude_mpm/services/monitor/daemon_manager.py +96 -19
  63. claude_mpm/services/project/project_organizer.py +4 -0
  64. claude_mpm/services/runner_configuration_service.py +16 -3
  65. claude_mpm/services/session_management_service.py +16 -4
  66. claude_mpm/utils/agent_filters.py +288 -0
  67. claude_mpm/utils/gitignore.py +3 -0
  68. claude_mpm/utils/migration.py +372 -0
  69. claude_mpm/utils/progress.py +5 -1
  70. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/METADATA +69 -8
  71. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/RECORD +76 -62
  72. /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
  73. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/WHEEL +0 -0
  74. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/entry_points.txt +0 -0
  75. {claude_mpm-5.0.2.dist-info → claude_mpm-5.1.9.dist-info}/licenses/LICENSE +0 -0
  76. {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
- lines = content.split("\n")
315
- filtered_lines = []
316
- skip_section = False
317
-
318
- i = 0
319
- while i < len(lines):
320
- line = lines[i]
321
-
322
- # Check if we're entering the test response protocol section
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
- # Replace multiple consecutive newlines with double newlines
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**: Prevent PM from investigating code, analyzing patterns, or researching solutions.
137
+ **Purpose**: Block PM from investigation work through pre-action enforcement
138
138
 
139
- ### Trigger Conditions
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
- **IF PM attempts ANY of the following:**
144
+ ### Core Principle
142
145
 
143
- #### File Reading Investigation
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
- #### Search and Analysis
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
- #### Investigation Activities
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
- ### Violation Response
152
+ #### Step 1: User Request Analysis (BLOCKING)
162
153
 
163
- **→ STOP IMMEDIATELY**
154
+ Before any tool use, PM analyzes user request for investigation triggers:
164
155
 
165
- **→ ERROR**: `"PM VIOLATION - Must delegate investigation to Research"`
156
+ **Investigation Trigger Keywords**:
166
157
 
167
- **→ REQUIRED ACTION**: Delegate to:
168
- - **Research**: For code investigation, documentation reading, web research
169
- - **Code Analyzer**: For code analysis, pattern identification, architecture review
170
- - **Ops**: For log analysis and debugging
171
- - **Version Control**: For git history and code evolution
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
- **→ VIOLATIONS TRACKED AND REPORTED**
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
- ### Allowed Exceptions
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
- **ONE file read** per session is allowed for quick context (e.g., checking a single config file).
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
- **Vector search** (`mcp__mcp-vector-search__*`) is allowed for quick context BEFORE delegation.
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
- #### VIOLATION Examples
323
+ #### Pre-Action Blocking (CORRECT)
184
324
 
185
325
  ```
186
- PM: Read("src/auth.js")
187
- Read("src/middleware.js") # VIOLATION - reading multiple files
188
- PM: Grep(pattern="authentication") # VIOLATION - searching code
189
- PM: Glob(pattern="**/*.js") # VIOLATION - file discovery
190
- PM: WebSearch(query="how to fix CORS") # VIOLATION - researching solutions
191
- PM: Bash("git log src/auth.js") # VIOLATION - investigating history
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
- #### CORRECT Examples
337
+ #### Self-Correction (CORRECT)
195
338
 
196
339
  ```
197
- PM: Task(agent="research", task="Analyze authentication system across all auth-related files")
198
- PM: Task(agent="research", task="Find all JavaScript files using Glob and summarize")
199
- PM: Task(agent="research", task="Research CORS fix solutions for Express.js")
200
- PM: Task(agent="version-control", task="Review git history for auth.js changes")
201
- PM: Read("config.json") # ALLOWED - single file for context
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
 
@@ -91,7 +91,6 @@ def main(argv: Optional[list] = None):
91
91
  )
92
92
 
93
93
  try:
94
- launch_progress.update(10) # Start progress
95
94
  run_background_services()
96
95
  launch_progress.finish(message="Ready")
97
96
 
@@ -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
- agents.append(
173
- AgentConfig(
174
- name=normalized_id,
175
- description=(
176
- description[:80] + "..."
177
- if len(description) > 80
178
- else description
179
- ),
180
- dependencies=display_tools,
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 in current project or user directory.
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