claude-mpm 5.1.8__py3-none-any.whl → 5.4.22__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 (191) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/{PM_INSTRUCTIONS_TEACH.md → CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md} +721 -41
  4. claude_mpm/agents/PM_INSTRUCTIONS.md +290 -34
  5. claude_mpm/agents/agent_loader.py +13 -44
  6. claude_mpm/agents/frontmatter_validator.py +68 -0
  7. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  8. claude_mpm/cli/__main__.py +4 -0
  9. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  10. claude_mpm/cli/commands/agent_state_manager.py +8 -17
  11. claude_mpm/cli/commands/agents.py +169 -31
  12. claude_mpm/cli/commands/auto_configure.py +210 -25
  13. claude_mpm/cli/commands/config.py +88 -2
  14. claude_mpm/cli/commands/configure.py +1111 -161
  15. claude_mpm/cli/commands/configure_agent_display.py +15 -6
  16. claude_mpm/cli/commands/mpm_init/core.py +160 -46
  17. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  18. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  19. claude_mpm/cli/commands/skills.py +214 -189
  20. claude_mpm/cli/commands/summarize.py +413 -0
  21. claude_mpm/cli/executor.py +11 -3
  22. claude_mpm/cli/parsers/agents_parser.py +54 -9
  23. claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
  24. claude_mpm/cli/parsers/base_parser.py +5 -0
  25. claude_mpm/cli/parsers/config_parser.py +153 -83
  26. claude_mpm/cli/parsers/skills_parser.py +3 -2
  27. claude_mpm/cli/startup.py +550 -94
  28. claude_mpm/commands/mpm-config.md +265 -0
  29. claude_mpm/commands/mpm-help.md +14 -95
  30. claude_mpm/commands/mpm-organize.md +500 -0
  31. claude_mpm/config/agent_sources.py +27 -0
  32. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  33. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  34. claude_mpm/core/framework_loader.py +4 -2
  35. claude_mpm/core/logger.py +13 -0
  36. claude_mpm/core/output_style_manager.py +173 -43
  37. claude_mpm/core/socketio_pool.py +3 -3
  38. claude_mpm/core/unified_agent_registry.py +134 -16
  39. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  40. claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
  41. claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
  42. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  43. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  44. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  45. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  46. claude_mpm/hooks/memory_integration_hook.py +46 -1
  47. claude_mpm/init.py +0 -19
  48. claude_mpm/models/agent_definition.py +7 -0
  49. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  50. claude_mpm/scripts/launch_monitor.py +93 -13
  51. claude_mpm/scripts/start_activity_logging.py +0 -0
  52. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  53. claude_mpm/services/agents/agent_review_service.py +280 -0
  54. claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
  55. claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
  56. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +188 -12
  57. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +531 -55
  58. claude_mpm/services/agents/git_source_manager.py +34 -0
  59. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  60. claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
  61. claude_mpm/services/agents/toolchain_detector.py +10 -6
  62. claude_mpm/services/analysis/__init__.py +11 -1
  63. claude_mpm/services/analysis/clone_detector.py +1030 -0
  64. claude_mpm/services/command_deployment_service.py +81 -10
  65. claude_mpm/services/event_bus/config.py +3 -1
  66. claude_mpm/services/git/git_operations_service.py +93 -8
  67. claude_mpm/services/monitor/daemon.py +9 -2
  68. claude_mpm/services/monitor/daemon_manager.py +39 -3
  69. claude_mpm/services/monitor/server.py +225 -19
  70. claude_mpm/services/self_upgrade_service.py +120 -12
  71. claude_mpm/services/skills/__init__.py +3 -0
  72. claude_mpm/services/skills/git_skill_source_manager.py +32 -2
  73. claude_mpm/services/skills/selective_skill_deployer.py +704 -0
  74. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  75. claude_mpm/services/skills_deployer.py +126 -9
  76. claude_mpm/services/socketio/event_normalizer.py +15 -1
  77. claude_mpm/services/socketio/server/core.py +160 -21
  78. claude_mpm/services/version_control/git_operations.py +103 -0
  79. claude_mpm/utils/agent_filters.py +17 -44
  80. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/METADATA +47 -84
  81. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/RECORD +86 -176
  82. claude_mpm-5.4.22.dist-info/entry_points.txt +5 -0
  83. claude_mpm-5.4.22.dist-info/licenses/LICENSE +94 -0
  84. claude_mpm-5.4.22.dist-info/licenses/LICENSE-FAQ.md +153 -0
  85. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  86. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  87. claude_mpm/agents/BASE_ENGINEER.md +0 -658
  88. claude_mpm/agents/BASE_OPS.md +0 -219
  89. claude_mpm/agents/BASE_PM.md +0 -480
  90. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  91. claude_mpm/agents/BASE_QA.md +0 -167
  92. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  93. claude_mpm/agents/base_agent.json +0 -31
  94. claude_mpm/agents/base_agent_loader.py +0 -601
  95. claude_mpm/cli/commands/agents_detect.py +0 -380
  96. claude_mpm/cli/commands/agents_recommend.py +0 -309
  97. claude_mpm/cli/ticket_cli.py +0 -35
  98. claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
  99. claude_mpm/commands/mpm-agents-detect.md +0 -177
  100. claude_mpm/commands/mpm-agents-list.md +0 -131
  101. claude_mpm/commands/mpm-agents-recommend.md +0 -223
  102. claude_mpm/commands/mpm-config-view.md +0 -150
  103. claude_mpm/commands/mpm-ticket-organize.md +0 -304
  104. claude_mpm/dashboard/analysis_runner.py +0 -455
  105. claude_mpm/dashboard/index.html +0 -13
  106. claude_mpm/dashboard/open_dashboard.py +0 -66
  107. claude_mpm/dashboard/static/css/activity.css +0 -1958
  108. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  109. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  110. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  111. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  112. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  113. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  114. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  115. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  116. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  117. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  118. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  119. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  120. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  121. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  122. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  123. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  124. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  125. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  126. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  127. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  128. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  129. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  130. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  131. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  132. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  133. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  134. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  135. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  136. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  137. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  138. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  139. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  140. claude_mpm/dashboard/templates/code_simple.html +0 -153
  141. claude_mpm/dashboard/templates/index.html +0 -606
  142. claude_mpm/dashboard/test_dashboard.html +0 -372
  143. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  144. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  145. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  146. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  147. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  148. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  149. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  150. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  151. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  152. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  153. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  154. claude_mpm/scripts/mcp_server.py +0 -75
  155. claude_mpm/scripts/mcp_wrapper.py +0 -39
  156. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  157. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  158. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  159. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  160. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  161. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  162. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  163. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  164. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  165. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  166. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  167. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  168. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  169. claude_mpm/services/mcp_gateway/main.py +0 -589
  170. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  171. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  172. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  173. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  174. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  175. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  176. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  177. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  178. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  179. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  180. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  181. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  182. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  183. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  184. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  185. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  186. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  187. claude_mpm-5.1.8.dist-info/entry_points.txt +0 -10
  188. claude_mpm-5.1.8.dist-info/licenses/LICENSE +0 -21
  189. /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
  190. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/WHEEL +0 -0
  191. {claude_mpm-5.1.8.dist-info → claude_mpm-5.4.22.dist-info}/top_level.txt +0 -0
claude_mpm/cli/startup.py CHANGED
@@ -14,6 +14,52 @@ import warnings
14
14
  from pathlib import Path
15
15
 
16
16
 
17
+ def sync_hooks_on_startup(quiet: bool = False) -> bool:
18
+ """Ensure hooks are up-to-date on startup.
19
+
20
+ WHY: Users can have stale hook configurations in settings.json that cause errors.
21
+ Reinstalling hooks ensures the hook format matches the current code.
22
+
23
+ DESIGN DECISION: Shows brief status message on success for user awareness.
24
+ Failures are logged but don't prevent startup to ensure claude-mpm remains functional.
25
+
26
+ Args:
27
+ quiet: If True, suppress all output (used internally)
28
+
29
+ Returns:
30
+ bool: True if hooks were synced successfully, False otherwise
31
+ """
32
+ try:
33
+ from ..hooks.claude_hooks.installer import HookInstaller
34
+
35
+ installer = HookInstaller()
36
+
37
+ # Show brief status (hooks sync is fast)
38
+ if not quiet:
39
+ print("Syncing Claude Code hooks...", end=" ", flush=True)
40
+
41
+ # Reinstall hooks (force=True ensures update)
42
+ success = installer.install_hooks(force=True)
43
+
44
+ if not quiet:
45
+ if success:
46
+ print("✓")
47
+ else:
48
+ print("(skipped)")
49
+
50
+ return success
51
+
52
+ except Exception as e:
53
+ if not quiet:
54
+ print("(error)")
55
+ # Log but don't fail startup
56
+ from ..core.logger import get_logger
57
+
58
+ logger = get_logger("startup")
59
+ logger.warning(f"Hook sync failed (non-fatal): {e}")
60
+ return False
61
+
62
+
17
63
  def check_legacy_cache() -> None:
18
64
  """Check for legacy cache/agents/ directory and warn user.
19
65
 
@@ -221,78 +267,157 @@ def discover_and_link_runtime_skills():
221
267
 
222
268
  def deploy_output_style_on_startup():
223
269
  """
224
- Deploy claude-mpm output style to Claude Code on CLI startup.
270
+ Deploy claude-mpm output styles to PROJECT-LEVEL directory on CLI startup.
271
+
272
+ WHY: Automatically deploy output styles to ensure consistent, professional
273
+ communication without emojis and exclamation points. Styles are project-specific
274
+ to allow different projects to have different communication styles.
225
275
 
226
- WHY: Automatically deploy and activate the output style to ensure consistent,
227
- professional communication without emojis and exclamation points. This ensures
228
- the style is available even when using Claude Code directly (not via chat command).
276
+ DESIGN DECISION: This is non-blocking and idempotent. Deploys to project-level
277
+ directory (.claude/settings/output-styles/) instead of user-level to maintain
278
+ project isolation.
229
279
 
230
- DESIGN DECISION: This is non-blocking and idempotent. It uses OutputStyleManager
231
- which handles version detection, file deployment, and settings activation.
232
- Only works for Claude Code >= 1.0.83.
280
+ Deploys two styles:
281
+ - claude-mpm-style.md (professional mode)
282
+ - claude-mpm-teacher.md (teaching mode)
233
283
  """
234
284
  try:
285
+ import shutil
235
286
  from pathlib import Path
236
287
 
237
- from ..core.output_style_manager import OutputStyleManager
238
-
239
- # Create OutputStyleManager instance
240
- output_style_manager = OutputStyleManager()
241
-
242
- # Check if Claude Code supports output styles
243
- if not output_style_manager.supports_output_styles():
244
- # Silently skip - version too old or Claude not installed
245
- return
246
-
247
- # Check if already deployed and active
248
- settings_file = Path.home() / ".claude" / "settings.json"
249
- output_style_file = Path.home() / ".claude" / "output-styles" / "claude-mpm.md"
250
-
251
- already_configured = False
252
- if settings_file.exists() and output_style_file.exists():
253
- try:
254
- import json
255
-
256
- # Check if file has content (bug fix: was skipping empty files)
257
- if output_style_file.stat().st_size == 0:
258
- # File is empty, need to redeploy with content
259
- pass # Fall through to deployment below
260
- else:
261
- # File has content, check if already active
262
- settings = json.loads(settings_file.read_text())
263
- if settings.get("activeOutputStyle") == "claude-mpm":
264
- # Already deployed and active with content
265
- already_configured = True
266
- except Exception:
267
- pass # Continue with deployment if we can't read settings
288
+ # Source files (in framework package)
289
+ package_dir = Path(__file__).parent.parent / "agents"
290
+ professional_source = package_dir / "CLAUDE_MPM_OUTPUT_STYLE.md"
291
+ teacher_source = package_dir / "CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md"
292
+
293
+ # Target directory (PROJECT-LEVEL, not user-level)
294
+ project_dir = Path.cwd()
295
+ output_styles_dir = project_dir / ".claude" / "settings" / "output-styles"
296
+ professional_target = output_styles_dir / "claude-mpm-style.md"
297
+ teacher_target = output_styles_dir / "claude-mpm-teacher.md"
298
+
299
+ # Create directory if it doesn't exist
300
+ output_styles_dir.mkdir(parents=True, exist_ok=True)
301
+
302
+ # Check if already deployed (both files exist and have content)
303
+ already_deployed = (
304
+ professional_target.exists()
305
+ and teacher_target.exists()
306
+ and professional_target.stat().st_size > 0
307
+ and teacher_target.stat().st_size > 0
308
+ )
268
309
 
269
- if already_configured:
270
- # Show feedback that output style is ready
271
- print("✓ Output style configured", flush=True)
310
+ if already_deployed:
311
+ # Show feedback that output styles are ready
312
+ print("✓ Output styles ready", flush=True)
272
313
  return
273
314
 
274
- # Read OUTPUT_STYLE.md content
275
- output_style_path = Path(__file__).parent.parent / "agents" / "OUTPUT_STYLE.md"
315
+ # Deploy both styles
316
+ deployed_count = 0
317
+ if professional_source.exists():
318
+ shutil.copy2(professional_source, professional_target)
319
+ deployed_count += 1
276
320
 
277
- if not output_style_path.exists():
278
- # No output style file to deploy
279
- return
321
+ if teacher_source.exists():
322
+ shutil.copy2(teacher_source, teacher_target)
323
+ deployed_count += 1
280
324
 
281
- output_style_content = output_style_path.read_text()
325
+ if deployed_count > 0:
326
+ print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
327
+ else:
328
+ # Source files missing - log but don't fail
329
+ from ..core.logger import get_logger
282
330
 
283
- # Deploy the output style (deploys file and activates it)
284
- output_style_manager.deploy_output_style(output_style_content)
285
- print("✓ Output style configured", flush=True)
331
+ logger = get_logger("cli")
332
+ logger.debug("Output style source files not found")
286
333
 
287
334
  except Exception as e:
288
335
  # Non-critical - log but don't fail startup
289
336
  from ..core.logger import get_logger
290
337
 
291
338
  logger = get_logger("cli")
292
- logger.debug(f"Failed to deploy output style: {e}")
339
+ logger.debug(f"Failed to deploy output styles: {e}")
293
340
  # Continue execution - output style deployment shouldn't block startup
294
341
 
295
342
 
343
+ def _cleanup_orphaned_agents(deploy_target: Path, deployed_agents: list[str]) -> int:
344
+ """Remove agents that are managed by claude-mpm but no longer deployed.
345
+
346
+ WHY: When agent configurations change, old agents should be removed to avoid
347
+ confusion and stale agent references. Only removes claude-mpm managed agents,
348
+ leaving user-created agents untouched.
349
+
350
+ SAFETY: Only removes files with claude-mpm ownership markers in frontmatter.
351
+ Files without frontmatter or without ownership indicators are preserved.
352
+
353
+ Args:
354
+ deploy_target: Path to .claude/agents/ directory
355
+ deployed_agents: List of agent filenames that should remain
356
+
357
+ Returns:
358
+ Number of agents removed
359
+ """
360
+ import re
361
+
362
+ import yaml
363
+
364
+ from ..core.logger import get_logger
365
+
366
+ logger = get_logger("cli")
367
+ removed_count = 0
368
+ deployed_set = set(deployed_agents)
369
+
370
+ if not deploy_target.exists():
371
+ return 0
372
+
373
+ # Scan all .md files in agents directory
374
+ for agent_file in deploy_target.glob("*.md"):
375
+ # Skip hidden files
376
+ if agent_file.name.startswith("."):
377
+ continue
378
+
379
+ # Skip if this agent should remain deployed
380
+ if agent_file.name in deployed_set:
381
+ continue
382
+
383
+ # Check if this is a claude-mpm managed agent
384
+ try:
385
+ content = agent_file.read_text(encoding="utf-8")
386
+
387
+ # Parse YAML frontmatter
388
+ if content.startswith("---"):
389
+ match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
390
+ if match:
391
+ frontmatter = yaml.safe_load(match.group(1))
392
+
393
+ # Check ownership indicators
394
+ is_ours = False
395
+ if frontmatter:
396
+ author = frontmatter.get("author", "")
397
+ source = frontmatter.get("source", "")
398
+ agent_id = frontmatter.get("agent_id", "")
399
+
400
+ # It's ours if it has any of these markers
401
+ if (
402
+ "Claude MPM" in str(author)
403
+ or source == "remote"
404
+ or agent_id
405
+ ):
406
+ is_ours = True
407
+
408
+ if is_ours:
409
+ # Safe to remove - it's our agent but not deployed
410
+ agent_file.unlink()
411
+ removed_count += 1
412
+ logger.info(f"Removed orphaned agent: {agent_file.name}")
413
+
414
+ except Exception as e:
415
+ logger.debug(f"Could not check agent {agent_file.name}: {e}")
416
+ # Don't remove if we can't verify ownership
417
+
418
+ return removed_count
419
+
420
+
296
421
  def sync_remote_agents_on_startup():
297
422
  """
298
423
  Synchronize agent templates from remote sources on startup.
@@ -308,7 +433,8 @@ def sync_remote_agents_on_startup():
308
433
  Workflow:
309
434
  1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
310
435
  2. Deploy agents to ~/.claude/agents/ - Phase 2 progress bar
311
- 3. Log deployment results
436
+ 3. Cleanup orphaned agents (ours but no longer deployed) - Phase 3
437
+ 4. Log deployment results
312
438
  """
313
439
  # Check for legacy cache and warn user if found
314
440
  check_legacy_cache()
@@ -386,6 +512,7 @@ def sync_remote_agents_on_startup():
386
512
  # 1. Must have "/agents/" in path (from git repos)
387
513
  # 2. Must not be in PM templates or doc files
388
514
  # 3. Exclude BASE-AGENT.md which is not a deployable agent
515
+ # 4. Exclude build artifacts (dist/, build/, .cache/) to prevent double-counting
389
516
  agent_files = [
390
517
  f
391
518
  for f in all_md_files
@@ -396,44 +523,98 @@ def sync_remote_agents_on_startup():
396
523
  and f.name.lower() not in pm_templates
397
524
  and f.name.lower() not in doc_files
398
525
  and f.name.lower() != "base-agent.md"
526
+ # Exclude build artifacts (prevents double-counting source + built files)
527
+ and not any(
528
+ part in str(f).split("/")
529
+ for part in ["dist", "build", ".cache"]
530
+ )
399
531
  )
400
532
  ]
401
533
  agent_count = len(agent_files)
402
534
 
403
535
  if agent_count > 0:
404
- # Create progress bar for deployment phase
405
- deploy_progress = ProgressBar(
406
- total=agent_count,
407
- prefix="Deploying agents",
408
- show_percentage=True,
409
- show_counter=True,
410
- )
411
-
412
- # Deploy agents with progress callback
413
- deploy_target = Path.home() / ".claude" / "agents"
536
+ # Deploy agents to project-level directory where Claude Code expects them
537
+ deploy_target = Path.cwd() / ".claude" / "agents"
414
538
  deployment_result = deployment_service.deploy_agents(
415
539
  target_dir=deploy_target,
416
540
  force_rebuild=False, # Only deploy if versions differ
417
541
  deployment_mode="update", # Version-aware updates
418
542
  )
419
543
 
420
- # Update progress bar (single increment since deploy_agents is batch)
421
- deploy_progress.update(agent_count)
422
-
423
- # Finish deployment progress bar
544
+ # Get actual counts from deployment result (reflects configured agents)
424
545
  deployed = len(deployment_result.get("deployed", []))
425
546
  updated = len(deployment_result.get("updated", []))
426
547
  skipped = len(deployment_result.get("skipped", []))
427
- total_available = deployed + updated + skipped
548
+ total_configured = deployed + updated + skipped
549
+
550
+ # FALLBACK: If deployment result doesn't track skipped agents (async path),
551
+ # count existing agents in target directory as "already deployed"
552
+ # This ensures accurate reporting when agents are already up-to-date
553
+ if total_configured == 0 and deploy_target.exists():
554
+ existing_agents = list(deploy_target.glob("*.md"))
555
+ # Filter out non-agent files (e.g., README.md, INSTRUCTIONS.md)
556
+ agent_count_in_target = len(
557
+ [
558
+ f
559
+ for f in existing_agents
560
+ if not f.name.startswith(("README", "INSTRUCTIONS"))
561
+ ]
562
+ )
563
+ if agent_count_in_target > 0:
564
+ # All agents already deployed - count them as skipped
565
+ skipped = agent_count_in_target
566
+ total_configured = agent_count_in_target
567
+
568
+ # Create progress bar with actual configured agent count (not raw file count)
569
+ deploy_progress = ProgressBar(
570
+ total=total_configured if total_configured > 0 else 1,
571
+ prefix="Deploying agents",
572
+ show_percentage=True,
573
+ show_counter=True,
574
+ )
428
575
 
429
- # Show total available agents (deployed + updated + already existing)
576
+ # Update progress bar to completion
577
+ deploy_progress.update(
578
+ total_configured if total_configured > 0 else 1
579
+ )
580
+
581
+ # Cleanup orphaned agents (ours but no longer deployed)
582
+ # Get list of deployed agent filenames (what should remain)
583
+ deployed_filenames = []
584
+ for agent_name in deployment_result.get("deployed", []):
585
+ deployed_filenames.append(f"{agent_name}.md")
586
+ for agent_name in deployment_result.get("updated", []):
587
+ deployed_filenames.append(f"{agent_name}.md")
588
+ for agent_name in deployment_result.get("skipped", []):
589
+ deployed_filenames.append(f"{agent_name}.md")
590
+
591
+ # Run cleanup and get count of removed agents
592
+ removed = _cleanup_orphaned_agents(
593
+ deploy_target, deployed_filenames
594
+ )
595
+
596
+ # Show total configured agents (deployed + updated + already existing)
597
+ # Include repo count for context and removed count if any
430
598
  if deployed > 0 or updated > 0:
599
+ if removed > 0:
600
+ deploy_progress.finish(
601
+ f"Complete: {deployed} new, {updated} updated, {skipped} unchanged, "
602
+ f"{removed} removed ({total_configured} configured from {agent_count} in repo)"
603
+ )
604
+ else:
605
+ deploy_progress.finish(
606
+ f"Complete: {deployed} new, {updated} updated, {skipped} unchanged "
607
+ f"({total_configured} configured from {agent_count} in repo)"
608
+ )
609
+ elif removed > 0:
431
610
  deploy_progress.finish(
432
- f"Complete: {deployed} deployed, {updated} updated, {skipped} already present ({total_available} total)"
611
+ f"Complete: {total_configured} agents ready - all unchanged, "
612
+ f"{removed} removed ({agent_count} available in repo)"
433
613
  )
434
614
  else:
435
615
  deploy_progress.finish(
436
- f"Complete: {total_available} agents ready (all up-to-date)"
616
+ f"Complete: {total_configured} agents ready - all unchanged "
617
+ f"({agent_count} available in repo)"
437
618
  )
438
619
 
439
620
  # Display deployment errors to user (not just logs)
@@ -496,14 +677,21 @@ def sync_remote_skills_on_startup():
496
677
 
497
678
  Workflow:
498
679
  1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
499
- 2. Deploy skills to ~/.claude/skills/ with flat structure - Phase 2 progress bar
500
- 3. Log deployment results
680
+ 2. Scan deployed agents for skill requirements save to configuration.yaml
681
+ 3. Resolve which skills to deploy (user_defined vs agent_referenced)
682
+ 4. Deploy resolved skills to ~/.claude/skills/ - Phase 2 progress bar
683
+ 5. Log deployment results with source indication
501
684
  """
502
685
  try:
503
686
  from pathlib import Path
504
687
 
505
688
  from ..config.skill_sources import SkillSourceConfiguration
506
689
  from ..services.skills.git_skill_source_manager import GitSkillSourceManager
690
+ from ..services.skills.selective_skill_deployer import (
691
+ get_required_skills_from_agents,
692
+ get_skills_to_deploy,
693
+ save_agent_skills_to_config,
694
+ )
507
695
  from ..utils.progress import ProgressBar
508
696
 
509
697
  config = SkillSourceConfiguration()
@@ -523,6 +711,8 @@ def sync_remote_skills_on_startup():
523
711
 
524
712
  # Discover total file count across all sources
525
713
  total_file_count = 0
714
+ total_skill_dirs = 0 # Count actual skill directories (folders with SKILL.md)
715
+
526
716
  for source in enabled_sources:
527
717
  try:
528
718
  # Parse GitHub URL
@@ -546,15 +736,26 @@ def sync_remote_skills_on_startup():
546
736
  ]
547
737
  total_file_count += len(relevant_files)
548
738
 
739
+ # Count skill directories (unique directories containing SKILL.md)
740
+ skill_dirs = set()
741
+ for f in all_files:
742
+ if f.endswith("/SKILL.md"):
743
+ # Extract directory path
744
+ skill_dir = "/".join(f.split("/")[:-1])
745
+ skill_dirs.add(skill_dir)
746
+ total_skill_dirs += len(skill_dirs)
747
+
549
748
  except Exception as e:
550
749
  logger.debug(f"Failed to discover files for {source.id}: {e}")
551
750
  # Use estimate if discovery fails
552
751
  total_file_count += 150
752
+ total_skill_dirs += 50 # Estimate ~50 skills
553
753
 
554
754
  # Create progress bar for sync phase with actual file count
755
+ # Note: We sync files (md, json, etc.), but will deploy skill directories
555
756
  sync_progress = ProgressBar(
556
757
  total=total_file_count if total_file_count > 0 else 1,
557
- prefix="Syncing skills",
758
+ prefix="Syncing skill files",
558
759
  show_percentage=True,
559
760
  show_counter=True,
560
761
  )
@@ -571,51 +772,100 @@ def sync_remote_skills_on_startup():
571
772
 
572
773
  if cached > 0:
573
774
  sync_progress.finish(
574
- f"Complete: {downloaded} downloaded, {cached} cached ({total_files} total)"
775
+ f"Complete: {downloaded} downloaded, {cached} cached ({total_files} files, {total_skill_dirs} skills)"
575
776
  )
576
777
  else:
577
778
  # All new downloads (first sync)
578
- sync_progress.finish(f"Complete: {downloaded} files downloaded")
779
+ sync_progress.finish(
780
+ f"Complete: {downloaded} files downloaded ({total_skill_dirs} skills)"
781
+ )
579
782
 
580
- # Phase 2: Deploy skills to ~/.claude/skills/
581
- # This flattens nested Git structure (e.g., collaboration/parallel-agents/SKILL.md)
582
- # into flat deployment (e.g., collaboration-dispatching-parallel-agents/SKILL.md)
783
+ # Phase 2: Scan agents and save to configuration.yaml
784
+ # This step populates configuration.yaml with agent-referenced skills
583
785
  if results["synced_count"] > 0:
584
- # Get all skills to determine deployment count
786
+ agents_dir = Path.cwd() / ".claude" / "agents"
787
+
788
+ # Scan agents for skill requirements
789
+ agent_skills = get_required_skills_from_agents(agents_dir)
790
+
791
+ # Save to project-level configuration.yaml
792
+ project_config_path = Path.cwd() / ".claude-mpm" / "configuration.yaml"
793
+ save_agent_skills_to_config(list(agent_skills), project_config_path)
794
+
795
+ # Phase 3: Resolve which skills to deploy (user_defined or agent_referenced)
796
+ skills_to_deploy, skill_source = get_skills_to_deploy(project_config_path)
797
+
798
+ # Get all skills to determine counts
585
799
  all_skills = manager.get_all_skills()
586
- skill_count = len(all_skills)
800
+ total_skill_count = len(all_skills)
587
801
 
588
- if skill_count > 0:
589
- # Create progress bar for deployment phase
590
- deploy_progress = ProgressBar(
591
- total=skill_count,
592
- prefix="Deploying skill directories",
593
- show_percentage=True,
594
- show_counter=True,
595
- )
802
+ # Determine skill count based on resolution
803
+ skill_count = (
804
+ len(skills_to_deploy) if skills_to_deploy else total_skill_count
805
+ )
596
806
 
597
- # Deploy skills with progress callback
807
+ if skill_count > 0:
808
+ # Deploy skills with resolved filter
598
809
  # Deploy to project directory (like agents), not user directory
599
810
  deployment_result = manager.deploy_skills(
600
811
  target_dir=Path.cwd() / ".claude" / "skills",
601
812
  force=False,
602
- progress_callback=deploy_progress.update,
813
+ skill_filter=set(skills_to_deploy) if skills_to_deploy else None,
603
814
  )
604
815
 
605
- # Finish deployment progress bar
816
+ # Get actual counts from deployment result
606
817
  deployed = deployment_result.get("deployed_count", 0)
607
818
  skipped = deployment_result.get("skipped_count", 0)
819
+ filtered = deployment_result.get("filtered_count", 0)
608
820
  total_available = deployed + skipped
609
821
 
822
+ # Only show progress bar if there are skills to deploy
823
+ if total_available > 0:
824
+ deploy_progress = ProgressBar(
825
+ total=total_available,
826
+ prefix="Deploying skill directories",
827
+ show_percentage=True,
828
+ show_counter=True,
829
+ )
830
+ # Update progress bar to completion
831
+ deploy_progress.update(total_available)
832
+ else:
833
+ # No skills to deploy - create dummy progress for message only
834
+ deploy_progress = ProgressBar(
835
+ total=1,
836
+ prefix="Deploying skill directories",
837
+ show_percentage=False,
838
+ show_counter=False,
839
+ )
840
+ deploy_progress.update(1)
841
+
610
842
  # Show total available skills (deployed + already existing)
611
- # This is more user-friendly than just showing newly deployed count
843
+ # Include source indication (user_defined vs agent_referenced)
844
+ # Note: total_skill_count is from the repo, total_available is what's deployed/needed
845
+ source_label = (
846
+ "user override" if skill_source == "user_defined" else "from agents"
847
+ )
848
+
612
849
  if deployed > 0:
850
+ if filtered > 0:
851
+ deploy_progress.finish(
852
+ f"Complete: {deployed} new, {skipped} unchanged "
853
+ f"({total_available} {source_label}, {filtered} available in repo)"
854
+ )
855
+ else:
856
+ deploy_progress.finish(
857
+ f"Complete: {deployed} new, {skipped} unchanged "
858
+ f"({total_available} skills {source_label} from {total_skill_count} in repo)"
859
+ )
860
+ elif filtered > 0:
861
+ # Skills filtered means agents require fewer skills than available
613
862
  deploy_progress.finish(
614
- f"Complete: {deployed} deployed, {skipped} already present ({total_available} total)"
863
+ f"No skills needed ({source_label}, {total_skill_count} available in repo)"
615
864
  )
616
865
  else:
617
866
  deploy_progress.finish(
618
- f"Complete: {total_available} skills ready (all up-to-date)"
867
+ f"Complete: {total_available} skills {source_label} "
868
+ f"({total_skill_count} available in repo)"
619
869
  )
620
870
 
621
871
  # Log deployment errors if any
@@ -644,6 +894,202 @@ def sync_remote_skills_on_startup():
644
894
  # Continue execution - skill sync failure shouldn't block startup
645
895
 
646
896
 
897
+ def show_agent_summary():
898
+ """
899
+ Display agent availability summary on startup.
900
+
901
+ WHY: Users should see at a glance how many agents are available and installed
902
+ without having to run /mpm-agents list.
903
+
904
+ DESIGN DECISION: Fast, non-blocking check that counts agents from the deployment
905
+ directory. Shows simple "X installed / Y available" format. Failures are silent
906
+ to avoid blocking startup.
907
+ """
908
+ try:
909
+ from pathlib import Path
910
+
911
+ # Count deployed agents (installed)
912
+ deploy_target = Path.cwd() / ".claude" / "agents"
913
+ installed_count = 0
914
+ if deploy_target.exists():
915
+ # Count .md files, excluding README and other docs
916
+ agent_files = [
917
+ f
918
+ for f in deploy_target.glob("*.md")
919
+ if not f.name.startswith(("README", "INSTRUCTIONS", "."))
920
+ ]
921
+ installed_count = len(agent_files)
922
+
923
+ # Count available agents in cache (from remote sources)
924
+ cache_dir = Path.home() / ".claude-mpm" / "cache" / "remote-agents"
925
+ available_count = 0
926
+ if cache_dir.exists():
927
+ # Use same filtering logic as agent deployment (lines 486-533 in startup.py)
928
+ pm_templates = {
929
+ "base-agent.md",
930
+ "circuit_breakers.md",
931
+ "pm_examples.md",
932
+ "pm_red_flags.md",
933
+ "research_gate_examples.md",
934
+ "response_format.md",
935
+ "ticket_completeness_examples.md",
936
+ "validation_templates.md",
937
+ "git_file_tracking.md",
938
+ }
939
+ doc_files = {
940
+ "readme.md",
941
+ "changelog.md",
942
+ "contributing.md",
943
+ "implementation-summary.md",
944
+ "reorganization-plan.md",
945
+ "auto-deploy-index.md",
946
+ }
947
+
948
+ # Find all markdown files in agents/ directories
949
+ all_md_files = list(cache_dir.rglob("*.md"))
950
+ agent_files = [
951
+ f
952
+ for f in all_md_files
953
+ if (
954
+ "/agents/" in str(f)
955
+ and f.name.lower() not in pm_templates
956
+ and f.name.lower() not in doc_files
957
+ and f.name.lower() != "base-agent.md"
958
+ and not any(
959
+ part in str(f).split("/")
960
+ for part in ["dist", "build", ".cache"]
961
+ )
962
+ )
963
+ ]
964
+ available_count = len(agent_files)
965
+
966
+ # Display summary if we have agents
967
+ if installed_count > 0 or available_count > 0:
968
+ print(
969
+ f"✓ Agents: {installed_count} installed / {available_count} available",
970
+ flush=True,
971
+ )
972
+
973
+ except Exception as e:
974
+ # Silent failure - agent summary is informational only
975
+ from ..core.logger import get_logger
976
+
977
+ logger = get_logger("cli")
978
+ logger.debug(f"Failed to generate agent summary: {e}")
979
+
980
+
981
+ def show_skill_summary():
982
+ """
983
+ Display skill availability summary on startup.
984
+
985
+ WHY: Users should see at a glance how many skills are deployed and available
986
+ from collections, similar to the agent summary.
987
+
988
+ DESIGN DECISION: Fast, non-blocking check that counts skills from deployment
989
+ directory and collection repos. Shows "X installed (Y available)" format.
990
+ Failures are silent to avoid blocking startup.
991
+ """
992
+ try:
993
+ from pathlib import Path
994
+
995
+ # Count deployed skills (installed)
996
+ skills_dir = Path.home() / ".claude" / "skills"
997
+ installed_count = 0
998
+ if skills_dir.exists():
999
+ # Count directories with SKILL.md (excludes collection repos)
1000
+ # Exclude collection directories (obra-superpowers, etc.)
1001
+ skill_dirs = [
1002
+ d
1003
+ for d in skills_dir.iterdir()
1004
+ if d.is_dir()
1005
+ and (d / "SKILL.md").exists()
1006
+ and not (d / ".git").exists() # Exclude collection repos
1007
+ ]
1008
+ installed_count = len(skill_dirs)
1009
+
1010
+ # Count available skills in collections
1011
+ available_count = 0
1012
+ if skills_dir.exists():
1013
+ # Scan all collection directories (those with .git)
1014
+ for collection_dir in skills_dir.iterdir():
1015
+ if (
1016
+ not collection_dir.is_dir()
1017
+ or not (collection_dir / ".git").exists()
1018
+ ):
1019
+ continue
1020
+
1021
+ # Count skill directories in this collection
1022
+ # Skills can be nested in: skills/category/skill-name/SKILL.md
1023
+ # or in flat structure: skill-name/SKILL.md
1024
+ for root, dirs, files in os.walk(collection_dir):
1025
+ if "SKILL.md" in files:
1026
+ # Exclude build artifacts and hidden directories (within the collection)
1027
+ # Get relative path from collection_dir to avoid excluding based on .claude parent
1028
+ root_path = Path(root)
1029
+ relative_parts = root_path.relative_to(collection_dir).parts
1030
+ if not any(
1031
+ part.startswith(".")
1032
+ or part in ["dist", "build", "__pycache__"]
1033
+ for part in relative_parts
1034
+ ):
1035
+ available_count += 1
1036
+
1037
+ # Display summary if we have skills
1038
+ if installed_count > 0 or available_count > 0:
1039
+ print(
1040
+ f"✓ Skills: {installed_count} installed ({available_count} available)",
1041
+ flush=True,
1042
+ )
1043
+
1044
+ except Exception as e:
1045
+ # Silent failure - skill summary is informational only
1046
+ from ..core.logger import get_logger
1047
+
1048
+ logger = get_logger("cli")
1049
+ logger.debug(f"Failed to generate skill summary: {e}")
1050
+
1051
+
1052
+ def auto_install_chrome_devtools_on_startup():
1053
+ """
1054
+ Automatically install chrome-devtools-mcp on startup if enabled.
1055
+
1056
+ WHY: Browser automation capabilities should be available out-of-the-box without
1057
+ manual MCP server configuration. chrome-devtools-mcp provides powerful browser
1058
+ interaction tools for Claude Code.
1059
+
1060
+ DESIGN DECISION: Non-blocking installation that doesn't prevent startup if it fails.
1061
+ Respects user configuration setting (enabled by default). Only installs if not
1062
+ already configured in Claude.
1063
+ """
1064
+ try:
1065
+ # Check if auto-install is disabled in config
1066
+ from ..config.config_loader import ConfigLoader
1067
+
1068
+ config_loader = ConfigLoader()
1069
+ try:
1070
+ config = config_loader.load_main_config()
1071
+ chrome_devtools_config = config.get("chrome_devtools", {})
1072
+ if not chrome_devtools_config.get("auto_install", True):
1073
+ # Auto-install disabled, skip silently
1074
+ return
1075
+ except Exception:
1076
+ # If config loading fails, assume auto-install is enabled (default)
1077
+ pass
1078
+
1079
+ # Import and run chrome-devtools installation
1080
+ from ..cli.chrome_devtools_installer import auto_install_chrome_devtools
1081
+
1082
+ auto_install_chrome_devtools(quiet=False)
1083
+
1084
+ except Exception as e:
1085
+ # Import logger here to avoid circular imports
1086
+ from ..core.logger import get_logger
1087
+
1088
+ logger = get_logger("cli")
1089
+ logger.debug(f"Failed to auto-install chrome-devtools-mcp: {e}")
1090
+ # Continue execution - chrome-devtools installation failure shouldn't block startup
1091
+
1092
+
647
1093
  def run_background_services():
648
1094
  """
649
1095
  Initialize all background services on startup.
@@ -656,11 +1102,17 @@ def run_background_services():
656
1102
  file creation in project .claude/ directories.
657
1103
  See: SystemInstructionsDeployer and agent_deployment.py line 504-509
658
1104
  """
1105
+ # Sync hooks early to ensure up-to-date configuration
1106
+ # RATIONALE: Hooks should be synced before other services to fix stale configs
1107
+ # This is fast (<100ms) and non-blocking, so it doesn't delay startup
1108
+ sync_hooks_on_startup() # Shows "Syncing Claude Code hooks... ✓"
1109
+
659
1110
  initialize_project_registry()
660
1111
  check_mcp_auto_configuration()
661
1112
  verify_mcp_gateway_startup()
662
1113
  check_for_updates_async()
663
1114
  sync_remote_agents_on_startup() # Sync agents from remote sources
1115
+ show_agent_summary() # Display agent counts after deployment
664
1116
 
665
1117
  # Skills deployment order (precedence: remote > bundled)
666
1118
  # 1. Deploy bundled skills first (base layer from package)
@@ -670,9 +1122,13 @@ def run_background_services():
670
1122
  deploy_bundled_skills() # Base layer: package-bundled skills
671
1123
  sync_remote_skills_on_startup() # Override layer: Git-based skills (takes precedence)
672
1124
  discover_and_link_runtime_skills() # Discovery: user-added skills
1125
+ show_skill_summary() # Display skill counts after deployment
673
1126
 
674
1127
  deploy_output_style_on_startup()
675
1128
 
1129
+ # Auto-install chrome-devtools-mcp for browser automation
1130
+ auto_install_chrome_devtools_on_startup()
1131
+
676
1132
 
677
1133
  def setup_mcp_server_logging(args):
678
1134
  """