claude-mpm 5.4.21__py3-none-any.whl → 5.4.59__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 (176) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_AGENT.md +164 -0
  3. claude_mpm/agents/BASE_ENGINEER.md +658 -0
  4. claude_mpm/agents/MEMORY.md +1 -1
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +771 -1019
  6. claude_mpm/agents/WORKFLOW.md +5 -254
  7. claude_mpm/agents/agent_loader.py +1 -1
  8. claude_mpm/agents/base_agent.json +31 -0
  9. claude_mpm/agents/frontmatter_validator.py +2 -2
  10. claude_mpm/cli/commands/agent_state_manager.py +10 -10
  11. claude_mpm/cli/commands/agents.py +9 -9
  12. claude_mpm/cli/commands/auto_configure.py +4 -4
  13. claude_mpm/cli/commands/configure.py +1 -1
  14. claude_mpm/cli/commands/configure_agent_display.py +12 -0
  15. claude_mpm/cli/commands/mpm_init/core.py +72 -0
  16. claude_mpm/cli/commands/postmortem.py +1 -1
  17. claude_mpm/cli/commands/profile.py +276 -0
  18. claude_mpm/cli/commands/skills.py +14 -18
  19. claude_mpm/cli/executor.py +10 -0
  20. claude_mpm/cli/interactive/agent_wizard.py +2 -2
  21. claude_mpm/cli/parsers/base_parser.py +7 -0
  22. claude_mpm/cli/parsers/profile_parser.py +147 -0
  23. claude_mpm/cli/parsers/skills_parser.py +0 -6
  24. claude_mpm/cli/startup.py +506 -180
  25. claude_mpm/commands/mpm-config.md +13 -250
  26. claude_mpm/commands/mpm-doctor.md +9 -22
  27. claude_mpm/commands/mpm-help.md +5 -206
  28. claude_mpm/commands/mpm-init.md +81 -507
  29. claude_mpm/commands/mpm-monitor.md +15 -402
  30. claude_mpm/commands/mpm-organize.md +61 -441
  31. claude_mpm/commands/mpm-postmortem.md +6 -108
  32. claude_mpm/commands/mpm-session-resume.md +12 -363
  33. claude_mpm/commands/mpm-status.md +5 -69
  34. claude_mpm/commands/mpm-ticket-view.md +52 -495
  35. claude_mpm/commands/mpm-version.md +5 -107
  36. claude_mpm/core/config.py +2 -4
  37. claude_mpm/core/framework/loaders/agent_loader.py +1 -1
  38. claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
  39. claude_mpm/core/optimized_startup.py +61 -0
  40. claude_mpm/core/shared/config_loader.py +3 -1
  41. claude_mpm/core/unified_agent_registry.py +1 -1
  42. claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
  43. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +1 -0
  44. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +1 -0
  45. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +1 -0
  46. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +24 -0
  47. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B0uc0UOD.js +36 -0
  48. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7RN905-.js +1 -0
  49. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7xVLGWV.js +2 -0
  50. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BIF9m_hv.js +61 -0
  51. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +1 -0
  52. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BPYeabCQ.js +1 -0
  53. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
  54. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BSNlmTZj.js +1 -0
  55. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Be7GpZd6.js +7 -0
  56. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bh0LDWpI.js +145 -0
  57. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BofRWZRR.js +10 -0
  58. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BovzEFCE.js +30 -0
  59. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C30mlcqg.js +165 -0
  60. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4B-KCzX.js +1 -0
  61. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4JcI4KD.js +122 -0
  62. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CBBdVcY8.js +1 -0
  63. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CDuw-vjf.js +1 -0
  64. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C_Usid8X.js +15 -0
  65. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cfqx1Qun.js +10 -0
  66. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CiIAseT4.js +128 -0
  67. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
  68. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CnA0NrzZ.js +1 -0
  69. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cs_tUR18.js +24 -0
  70. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
  71. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CyWMqx4W.js +43 -0
  72. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzZX-COe.js +220 -0
  73. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzeYkLYB.js +65 -0
  74. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D3k0OPJN.js +4 -0
  75. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9lljYKQ.js +1 -0
  76. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DGkLK5U1.js +267 -0
  77. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DI7hHRFL.js +1 -0
  78. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DLVjFsZ3.js +139 -0
  79. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUrLdbGD.js +89 -0
  80. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
  81. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DY1XQ8fi.js +2 -0
  82. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DZX00Y4g.js +1 -0
  83. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +1 -0
  84. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DaimHw_p.js +68 -0
  85. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +323 -0
  86. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dhb8PKl3.js +1 -0
  87. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dle-35c7.js +64 -0
  88. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DmxopI1J.js +1 -0
  89. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DwBR2MJi.js +60 -0
  90. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/GYwsonyD.js +1 -0
  91. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
  92. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/NqQ1dWOy.js +1 -0
  93. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/RJiighC3.js +1 -0
  94. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Vzk33B_K.js +2 -0
  95. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ZGh7QtNv.js +7 -0
  96. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bT1r9zLR.js +1 -0
  97. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bTOqqlTd.js +1 -0
  98. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/eNVUfhuA.js +1 -0
  99. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/iEWssX7S.js +162 -0
  100. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/sQeU3Y1z.js +1 -0
  101. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uuIeMWc-.js +1 -0
  102. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.D6-I5TpK.js +2 -0
  103. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +1 -0
  104. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.m1gL8KXf.js +1 -0
  105. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.CgNOuw-d.js +1 -0
  106. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +1 -0
  107. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
  108. claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
  109. claude_mpm/dashboard/static/svelte-build/index.html +36 -0
  110. claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
  111. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
  112. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
  113. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
  114. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
  115. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  116. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  117. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  118. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  119. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  120. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  121. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  122. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  123. claude_mpm/hooks/claude_hooks/hook_handler.py +149 -1
  124. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  125. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  126. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  127. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  128. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  129. claude_mpm/hooks/claude_hooks/services/connection_manager.py +26 -6
  130. claude_mpm/hooks/kuzu_memory_hook.py +5 -5
  131. claude_mpm/init.py +276 -0
  132. claude_mpm/models/git_repository.py +3 -3
  133. claude_mpm/scripts/start_activity_logging.py +0 -0
  134. claude_mpm/services/agents/agent_builder.py +3 -3
  135. claude_mpm/services/agents/cache_git_manager.py +6 -6
  136. claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
  137. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -2
  138. claude_mpm/services/agents/deployment/agent_format_converter.py +25 -13
  139. claude_mpm/services/agents/deployment/agent_template_builder.py +31 -19
  140. claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
  141. claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
  142. claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
  143. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +169 -26
  144. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +98 -75
  145. claude_mpm/services/agents/git_source_manager.py +23 -4
  146. claude_mpm/services/agents/recommender.py +5 -3
  147. claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
  148. claude_mpm/services/agents/sources/git_source_sync_service.py +121 -10
  149. claude_mpm/services/agents/startup_sync.py +22 -2
  150. claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
  151. claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
  152. claude_mpm/services/git/git_operations_service.py +8 -8
  153. claude_mpm/services/monitor/management/lifecycle.py +7 -1
  154. claude_mpm/services/monitor/server.py +473 -3
  155. claude_mpm/services/pm_skills_deployer.py +711 -0
  156. claude_mpm/services/profile_manager.py +337 -0
  157. claude_mpm/services/skills/git_skill_source_manager.py +148 -11
  158. claude_mpm/services/skills/selective_skill_deployer.py +97 -48
  159. claude_mpm/services/skills_deployer.py +161 -65
  160. claude_mpm/services/socketio/dashboard_server.py +1 -0
  161. claude_mpm/services/socketio/event_normalizer.py +37 -6
  162. claude_mpm/services/socketio/server/core.py +262 -123
  163. claude_mpm/skills/bundled/security-scanning.md +112 -0
  164. claude_mpm/skills/skill_manager.py +98 -3
  165. claude_mpm/templates/.pre-commit-config.yaml +112 -0
  166. claude_mpm/utils/agent_dependency_loader.py +14 -2
  167. claude_mpm/utils/agent_filters.py +1 -1
  168. claude_mpm/utils/migration.py +4 -4
  169. claude_mpm/utils/robust_installer.py +47 -3
  170. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/METADATA +7 -4
  171. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/RECORD +175 -81
  172. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/WHEEL +0 -0
  173. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/entry_points.txt +0 -0
  174. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/licenses/LICENSE +0 -0
  175. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  176. {claude_mpm-5.4.21.dist-info → claude_mpm-5.4.59.dist-info}/top_level.txt +0 -0
claude_mpm/init.py CHANGED
@@ -163,6 +163,15 @@ class ProjectInitializer:
163
163
  f"✓ Found {agent_count} project agent(s) in .claude-mpm/agents/"
164
164
  )
165
165
 
166
+ # Verify and deploy PM skills (non-blocking)
167
+ self._verify_and_deploy_pm_skills(project_root, is_mcp_mode)
168
+
169
+ # Setup security hooks (auto-install pre-commit, detect-secrets)
170
+ self._setup_security_hooks(project_root, is_mcp_mode)
171
+
172
+ # Perform security checks (non-blocking)
173
+ self._check_security_risks(project_root, is_mcp_mode)
174
+
166
175
  return True
167
176
 
168
177
  except Exception as e:
@@ -170,6 +179,68 @@ class ProjectInitializer:
170
179
  print(f"✗ Failed to create .claude-mpm/ directory: {e}")
171
180
  return False
172
181
 
182
+ def _verify_and_deploy_pm_skills(
183
+ self, project_root: Path, is_mcp_mode: bool = False
184
+ ) -> None:
185
+ """Verify PM skills are deployed and auto-deploy if missing.
186
+
187
+ Non-blocking operation that gracefully handles errors.
188
+
189
+ Args:
190
+ project_root: Project root directory
191
+ is_mcp_mode: Whether running in MCP mode (suppress console output)
192
+ """
193
+ try:
194
+ from claude_mpm.services.pm_skills_deployer import PMSkillsDeployerService
195
+
196
+ deployer = PMSkillsDeployerService()
197
+ result = deployer.verify_pm_skills(project_root)
198
+
199
+ if not result.verified:
200
+ # Log warnings
201
+ for warning in result.warnings:
202
+ self.logger.warning(warning)
203
+
204
+ # Auto-deploy PM skills
205
+ self.logger.info("Auto-deploying PM skills...")
206
+ deploy_result = deployer.deploy_pm_skills(project_root)
207
+
208
+ if deploy_result.success:
209
+ self.logger.info(
210
+ f"PM skills deployed: {len(deploy_result.deployed)} deployed, "
211
+ f"{len(deploy_result.skipped)} skipped"
212
+ )
213
+
214
+ # Print to console if not in MCP mode
215
+ if not is_mcp_mode:
216
+ if deploy_result.deployed:
217
+ print(
218
+ f"✓ Deployed {len(deploy_result.deployed)} PM skill(s) "
219
+ f"to .claude-mpm/skills/pm/"
220
+ )
221
+ else:
222
+ self.logger.warning(
223
+ f"PM skills deployment had errors: {len(deploy_result.errors)}"
224
+ )
225
+ if not is_mcp_mode and deploy_result.errors:
226
+ print(
227
+ f"⚠ PM skills deployment had {len(deploy_result.errors)} error(s)"
228
+ )
229
+ else:
230
+ # Skills verified successfully
231
+ registry = deployer._load_registry(project_root)
232
+ skill_count = len(registry.get("skills", []))
233
+ self.logger.debug(f"PM skills verified: {skill_count} skills")
234
+
235
+ if not is_mcp_mode and skill_count > 0:
236
+ print(f"✓ Verified {skill_count} PM skill(s)")
237
+
238
+ except ImportError:
239
+ self.logger.debug("PM skills deployer not available")
240
+ except Exception as e:
241
+ self.logger.warning(f"PM skills verification failed: {e}")
242
+ # Don't print to console - this is a non-critical failure
243
+
173
244
  def _migrate_project_agents(self):
174
245
  """Migrate agents from old subdirectory structure to direct agents directory.
175
246
 
@@ -319,6 +390,211 @@ class ProjectInitializer:
319
390
  if not dst_file.exists():
320
391
  shutil.copy2(template_file, dst_file)
321
392
 
393
+ def _setup_security_hooks(
394
+ self, project_root: Path, is_mcp_mode: bool = False
395
+ ) -> None:
396
+ """Automatically install pre-commit hooks for secret scanning.
397
+
398
+ This method:
399
+ 1. Installs pre-commit and detect-secrets if missing
400
+ 2. Copies .pre-commit-config.yaml to project root
401
+ 3. Runs pre-commit install to set up git hooks
402
+ 4. Creates .secrets.baseline for detect-secrets
403
+
404
+ Args:
405
+ project_root: Project root directory
406
+ is_mcp_mode: Whether running in MCP mode (suppress console output)
407
+ """
408
+ try:
409
+ import subprocess
410
+
411
+ # Only set up hooks if this is a git repository
412
+ if not (project_root / ".git").exists():
413
+ self.logger.debug("Not a git repository, skipping security hooks setup")
414
+ return
415
+
416
+ # Check/install pre-commit
417
+ try:
418
+ subprocess.run(
419
+ ["pre-commit", "--version"],
420
+ capture_output=True,
421
+ text=True,
422
+ timeout=2,
423
+ check=True,
424
+ )
425
+ except (
426
+ subprocess.CalledProcessError,
427
+ subprocess.TimeoutExpired,
428
+ FileNotFoundError,
429
+ ):
430
+ self.logger.info("Installing pre-commit...")
431
+ try:
432
+ subprocess.run(
433
+ [sys.executable, "-m", "pip", "install", "pre-commit"],
434
+ capture_output=True,
435
+ text=True,
436
+ timeout=60,
437
+ check=True,
438
+ )
439
+ self.logger.info("pre-commit installed successfully")
440
+ except subprocess.CalledProcessError as e:
441
+ self.logger.warning(f"Failed to install pre-commit: {e}")
442
+ return
443
+
444
+ # Check/install detect-secrets
445
+ try:
446
+ subprocess.run(
447
+ ["detect-secrets", "--version"],
448
+ capture_output=True,
449
+ text=True,
450
+ timeout=2,
451
+ check=True,
452
+ )
453
+ except (
454
+ subprocess.CalledProcessError,
455
+ subprocess.TimeoutExpired,
456
+ FileNotFoundError,
457
+ ):
458
+ self.logger.info("Installing detect-secrets...")
459
+ try:
460
+ subprocess.run(
461
+ [sys.executable, "-m", "pip", "install", "detect-secrets"],
462
+ capture_output=True,
463
+ text=True,
464
+ timeout=60,
465
+ check=True,
466
+ )
467
+ self.logger.info("detect-secrets installed successfully")
468
+ except subprocess.CalledProcessError as e:
469
+ self.logger.warning(f"Failed to install detect-secrets: {e}")
470
+ return
471
+
472
+ # Copy .pre-commit-config.yaml to project root if it doesn't exist
473
+ precommit_config = project_root / ".pre-commit-config.yaml"
474
+ if not precommit_config.exists():
475
+ template_dir = Path(__file__).parent / "templates"
476
+ template_config = template_dir / ".pre-commit-config.yaml"
477
+
478
+ if template_config.exists():
479
+ shutil.copy2(template_config, precommit_config)
480
+ self.logger.info("Copied .pre-commit-config.yaml to project root")
481
+ else:
482
+ self.logger.warning("Template .pre-commit-config.yaml not found")
483
+ return
484
+
485
+ # Create .secrets.baseline if it doesn't exist
486
+ secrets_baseline = project_root / ".secrets.baseline"
487
+ if not secrets_baseline.exists():
488
+ try:
489
+ subprocess.run(
490
+ ["detect-secrets", "scan", "--baseline", ".secrets.baseline"],
491
+ cwd=str(project_root),
492
+ capture_output=True,
493
+ text=True,
494
+ timeout=30,
495
+ check=True,
496
+ )
497
+ self.logger.info("Created .secrets.baseline")
498
+ except subprocess.CalledProcessError as e:
499
+ self.logger.warning(f"Failed to create .secrets.baseline: {e}")
500
+
501
+ # Install git hooks
502
+ try:
503
+ subprocess.run(
504
+ ["pre-commit", "install"],
505
+ cwd=str(project_root),
506
+ capture_output=True,
507
+ text=True,
508
+ timeout=30,
509
+ check=True,
510
+ )
511
+ self.logger.info("Pre-commit hooks installed in git repository")
512
+
513
+ if not is_mcp_mode:
514
+ print("✓ Security hooks installed (pre-commit + detect-secrets)")
515
+
516
+ except subprocess.CalledProcessError as e:
517
+ self.logger.warning(f"Failed to install pre-commit hooks: {e}")
518
+
519
+ except Exception as e:
520
+ self.logger.debug(f"Security hooks setup failed: {e}")
521
+ # Don't print to console - this is a non-critical failure
522
+
523
+ def _check_security_risks(
524
+ self, project_root: Path, is_mcp_mode: bool = False
525
+ ) -> None:
526
+ """Check for potential security risks like exposed config files.
527
+
528
+ Non-blocking operation that warns about security issues.
529
+
530
+ Args:
531
+ project_root: Project root directory
532
+ is_mcp_mode: Whether running in MCP mode (suppress console output)
533
+ """
534
+ try:
535
+ import subprocess
536
+
537
+ security_issues = []
538
+
539
+ # Common secret file patterns to check
540
+ secret_patterns = [
541
+ ".mcp-vector-search/config.json",
542
+ ".mcp/config.json",
543
+ "openrouter.json",
544
+ "anthropic-config.json",
545
+ "credentials.json",
546
+ "secrets.json",
547
+ "api-keys.json",
548
+ ]
549
+
550
+ for pattern in secret_patterns:
551
+ file_path = project_root / pattern
552
+ if file_path.exists():
553
+ # Check if file is tracked by git
554
+ try:
555
+ result = subprocess.run(
556
+ ["git", "ls-files", str(file_path)],
557
+ check=False,
558
+ cwd=str(project_root),
559
+ capture_output=True,
560
+ text=True,
561
+ timeout=2,
562
+ )
563
+ if result.stdout.strip():
564
+ security_issues.append(
565
+ f"⚠️ SECURITY: {pattern} is tracked by git (may contain secrets)"
566
+ )
567
+ except (subprocess.TimeoutExpired, FileNotFoundError):
568
+ pass
569
+
570
+ # Check if file is ignored by .gitignore
571
+ try:
572
+ result = subprocess.run(
573
+ ["git", "check-ignore", str(file_path)],
574
+ check=False,
575
+ cwd=str(project_root),
576
+ capture_output=True,
577
+ text=True,
578
+ timeout=2,
579
+ )
580
+ if result.returncode != 0: # File NOT ignored
581
+ security_issues.append(
582
+ f"⚠️ WARNING: {pattern} exists but not in .gitignore"
583
+ )
584
+ except (subprocess.TimeoutExpired, FileNotFoundError):
585
+ pass
586
+
587
+ # Print security warnings if not in MCP mode
588
+ if security_issues and not is_mcp_mode:
589
+ print("\n🔒 Security Check:")
590
+ for issue in security_issues:
591
+ print(f" {issue}")
592
+ print()
593
+
594
+ except Exception as e:
595
+ self.logger.debug(f"Security check failed: {e}")
596
+ # Don't print to console - this is a non-critical failure
597
+
322
598
  def validate_dependencies(self) -> Dict[str, bool]:
323
599
  """Validate that all required dependencies are available."""
324
600
  dependencies = {}
@@ -34,7 +34,7 @@ class GitRepository:
34
34
  def cache_path(self) -> Path:
35
35
  """Return cache directory path for this repository.
36
36
 
37
- Cache structure: ~/.claude-mpm/cache/remote-agents/{owner}/{repo}/{subdirectory}/
37
+ Cache structure: ~/.claude-mpm/cache/agents/{owner}/{repo}/{subdirectory}/
38
38
 
39
39
  Returns:
40
40
  Absolute path to cache directory for this repository
@@ -45,10 +45,10 @@ class GitRepository:
45
45
  ... subdirectory="agents"
46
46
  ... )
47
47
  >>> repo.cache_path
48
- Path('/Users/user/.claude-mpm/cache/remote-agents/bobmatnyc/claude-mpm-agents/agents')
48
+ Path('/Users/user/.claude-mpm/cache/agents/bobmatnyc/claude-mpm-agents/agents')
49
49
  """
50
50
  home = Path.home()
51
- base_cache = home / ".claude-mpm" / "cache" / "remote-agents"
51
+ base_cache = home / ".claude-mpm" / "cache" / "agents"
52
52
 
53
53
  # Extract owner and repo from URL
54
54
  owner, repo = self._parse_github_url(self.url)
File without changes
@@ -206,8 +206,8 @@ class AgentBuilderService:
206
206
  """
207
207
  errors = []
208
208
 
209
- # Required fields
210
- required_fields = ["id", "name", "prompt", "model"]
209
+ # Required fields (model is optional - defaults to sonnet if not specified)
210
+ required_fields = ["id", "name", "prompt"]
211
211
  for field in required_fields:
212
212
  if field not in config:
213
213
  errors.append(f"Missing required field: {field}")
@@ -219,7 +219,7 @@ class AgentBuilderService:
219
219
  except AgentDeploymentError as e:
220
220
  errors.append(str(e))
221
221
 
222
- # Validate model
222
+ # Validate model (only if present)
223
223
  if "model" in config:
224
224
  try:
225
225
  self._validate_model(config["model"])
@@ -29,7 +29,7 @@ Error Handling:
29
29
 
30
30
  Example:
31
31
  >>> from pathlib import Path
32
- >>> manager = CacheGitManager(Path.home() / ".claude-mpm/cache/remote-agents")
32
+ >>> manager = CacheGitManager(Path.home() / ".claude-mpm/cache/agents")
33
33
  >>> if manager.is_git_repo():
34
34
  ... status = manager.get_status()
35
35
  ... print(f"Branch: {status['branch']}, Uncommitted: {len(status['uncommitted'])}")
@@ -76,7 +76,7 @@ class CacheGitManager:
76
76
  timeout: Git command timeout in seconds (default: 30)
77
77
 
78
78
  Example:
79
- >>> cache_dir = Path.home() / ".claude-mpm/cache/remote-agents"
79
+ >>> cache_dir = Path.home() / ".claude-mpm/cache/agents"
80
80
  >>> manager = CacheGitManager(cache_dir)
81
81
  """
82
82
  self.cache_path = Path(cache_path)
@@ -105,12 +105,12 @@ class CacheGitManager:
105
105
 
106
106
  Example:
107
107
  >>> # Case 1: cache_path inside repo (searches upward)
108
- >>> # cache_path: ~/.claude-mpm/cache/remote-agents/bobmatnyc/claude-mpm-agents/agents
109
- >>> # Found at: ~/.claude-mpm/cache/remote-agents/bobmatnyc/claude-mpm-agents
108
+ >>> # cache_path: ~/.claude-mpm/cache/agents/bobmatnyc/claude-mpm-agents/agents
109
+ >>> # Found at: ~/.claude-mpm/cache/agents/bobmatnyc/claude-mpm-agents
110
110
 
111
111
  >>> # Case 2: repo nested in cache_path (searches downward)
112
- >>> # cache_path: ~/.claude-mpm/cache/remote-agents
113
- >>> # Found at: ~/.claude-mpm/cache/remote-agents/bobmatnyc/claude-mpm-agents
112
+ >>> # cache_path: ~/.claude-mpm/cache/agents
113
+ >>> # Found at: ~/.claude-mpm/cache/agents/bobmatnyc/claude-mpm-agents
114
114
  """
115
115
  # Strategy 1: Search upward (cache_path is inside repo)
116
116
  current = self.cache_path
@@ -876,13 +876,13 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
876
876
  user_agents_dir = potential_user_dir
877
877
  self.logger.info(f"Found user agents at: {user_agents_dir}")
878
878
 
879
- # Check for remote agents (cached from GitHub)
880
- remote_agents_dir = None
879
+ # Check for agents cache (from Git sources)
880
+ agents_cache_dir = None
881
881
  cache_dir = user_home / ".claude-mpm" / "cache"
882
- potential_remote_dir = cache_dir / "remote-agents"
883
- if potential_remote_dir.exists():
884
- remote_agents_dir = potential_remote_dir
885
- self.logger.info(f"Found remote agents cache at: {remote_agents_dir}")
882
+ potential_cache_dir = cache_dir / "agents"
883
+ if potential_cache_dir.exists():
884
+ agents_cache_dir = potential_cache_dir
885
+ self.logger.info(f"Found agents cache at: {agents_cache_dir}")
886
886
 
887
887
  # Get agents with version comparison and cleanup (4-tier discovery)
888
888
  agents_to_deploy, agent_sources, cleanup_results = (
@@ -890,7 +890,7 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
890
890
  system_templates_dir=system_templates_dir,
891
891
  project_agents_dir=project_agents_dir,
892
892
  user_agents_dir=user_agents_dir,
893
- remote_agents_dir=remote_agents_dir, # NEW: 4th tier
893
+ agents_cache_dir=agents_cache_dir, # NEW: 4th tier
894
894
  working_directory=self.working_directory,
895
895
  excluded_agents=excluded_agents,
896
896
  config=config,
@@ -898,6 +898,9 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
898
898
  )
899
899
  )
900
900
 
901
+ # Keep track of all enabled agents before filtering (for cleanup)
902
+ all_enabled_agents = agents_to_deploy.copy()
903
+
901
904
  # Compare with deployed versions if agents directory exists
902
905
  if agents_dir.exists():
903
906
  comparison_results = self.multi_source_service.compare_deployed_versions(
@@ -954,6 +957,25 @@ class AgentDeploymentService(ConfigServiceBase, AgentDeploymentInterface):
954
957
  f"All {len(comparison_results.get('up_to_date', []))} agents are up to date"
955
958
  )
956
959
 
960
+ # Cleanup excluded agents (remove agents not in deployment list)
961
+ # CRITICAL: Use all_enabled_agents (before filtering for updates) to preserve up-to-date agents
962
+ # Bug fix (1M-XXX): Previously used filtered agents_to_deploy which could be empty,
963
+ # causing all agents to be removed when everything was up-to-date
964
+ exclusion_cleanup_results = self.multi_source_service.cleanup_excluded_agents(
965
+ deployed_agents_dir=agents_dir,
966
+ agents_to_deploy=all_enabled_agents,
967
+ )
968
+
969
+ # Add exclusion cleanup results to main cleanup results
970
+ if exclusion_cleanup_results.get("removed"):
971
+ cleanup_results.setdefault("excluded_removed", []).extend(
972
+ exclusion_cleanup_results["removed"]
973
+ )
974
+ self.logger.info(
975
+ f"Removed {len(exclusion_cleanup_results['removed'])} excluded agents: "
976
+ f"{', '.join(exclusion_cleanup_results['removed'])}"
977
+ )
978
+
957
979
  # Convert to list of Path objects
958
980
  template_files = list(agents_to_deploy.values())
959
981
 
@@ -248,7 +248,9 @@ class AgentDiscoveryService:
248
248
  return agent_info
249
249
 
250
250
  except yaml.YAMLError as e:
251
- self.logger.error(f"Invalid YAML frontmatter in {template_file.name}: {e}")
251
+ self.logger.warning(
252
+ f"Invalid YAML frontmatter in {template_file.name}: {e}"
253
+ )
252
254
  return None
253
255
  except Exception as e:
254
256
  self.logger.error(
@@ -431,7 +433,7 @@ class AgentDiscoveryService:
431
433
  return True
432
434
 
433
435
  except yaml.YAMLError:
434
- self.logger.error(
436
+ self.logger.warning(
435
437
  f"Invalid YAML frontmatter in template: {template_file.name}"
436
438
  )
437
439
  return False
@@ -137,8 +137,8 @@ class AgentFormatConverter:
137
137
  else:
138
138
  pass
139
139
 
140
- # Extract additional fields
141
- model = self.extract_yaml_field(yaml_content, "model") or "sonnet"
140
+ # Extract additional fields - model is optional (Claude Code uses conversation model if not set)
141
+ model = self.extract_yaml_field(yaml_content, "model") # None if not specified
142
142
  author = (
143
143
  self.extract_yaml_field(yaml_content, "author")
144
144
  or "claude-mpm@anthropic.com"
@@ -147,7 +147,7 @@ class AgentFormatConverter:
147
147
  # Extract instructions from YAML content
148
148
  instructions = self._extract_instructions_from_yaml(yaml_content, agent_name)
149
149
 
150
- # Map model names to Claude Code format
150
+ # Map model names to Claude Code format (only if model is specified)
151
151
  model_map = {
152
152
  "claude-3-5-sonnet-20241022": "sonnet",
153
153
  "claude-3-5-sonnet": "sonnet",
@@ -159,7 +159,8 @@ class AgentFormatConverter:
159
159
  "opus": "opus",
160
160
  }
161
161
 
162
- mapped_model = model_map.get(model, "sonnet")
162
+ # Only map model if it's not None (preserve None for agents without model field)
163
+ mapped_model = model_map.get(model, model) if model is not None else None
163
164
 
164
165
  # Create multiline description with example (Claude Code format)
165
166
  multiline_description = f"""{description}
@@ -172,16 +173,27 @@ assistant: "I'll use the {name} agent to provide specialized assistance."
172
173
 
173
174
  # Build new YAML frontmatter - Claude Code compatible format
174
175
  # NOTE: Removed tags field and other non-essential fields for Claude Code compatibility
175
- new_frontmatter = f"""---
176
- name: {name}
177
- description: |
178
- {self._indent_text(multiline_description, 2)}
179
- model: {mapped_model}
180
- version: "{version}"
181
- author: "{author}"
182
- ---
176
+ frontmatter_lines = [
177
+ "---",
178
+ f"name: {name}",
179
+ "description: |",
180
+ f" {self._indent_text(multiline_description, 2)}",
181
+ ]
183
182
 
184
- """
183
+ # Only include model field if explicitly set in source
184
+ if mapped_model is not None:
185
+ frontmatter_lines.append(f"model: {mapped_model}")
186
+
187
+ frontmatter_lines.extend(
188
+ [
189
+ f'version: "{version}"',
190
+ f'author: "{author}"',
191
+ "---",
192
+ "",
193
+ ]
194
+ )
195
+
196
+ new_frontmatter = "\n".join(frontmatter_lines)
185
197
 
186
198
  return new_frontmatter + instructions
187
199
 
@@ -135,8 +135,8 @@ class AgentTemplateBuilder:
135
135
  break
136
136
 
137
137
  # Stop at common repository root indicators (check AFTER finding BASE-AGENT.md)
138
- # This ensures we check the 'agents' directory before stopping at 'remote-agents'
139
- if current_dir.name in [".claude-mpm", "remote-agents", "cache"]:
138
+ # Stop at cache root or .claude-mpm directory
139
+ if current_dir.name in [".claude-mpm", "cache"]:
140
140
  self.logger.debug(
141
141
  f"Reached repository root indicator at: {current_dir}"
142
142
  )
@@ -419,7 +419,7 @@ class AgentTemplateBuilder:
419
419
  if non_standard:
420
420
  self.logger.info(f"Using non-standard tools: {non_standard}")
421
421
 
422
- # Extract model from template with fallback
422
+ # Extract model from template (no fallback - preserve None if not specified)
423
423
  capabilities_model = (
424
424
  capabilities.get("model") if isinstance(capabilities, dict) else None
425
425
  )
@@ -428,7 +428,7 @@ class AgentTemplateBuilder:
428
428
  template_data.get("model")
429
429
  or capabilities_model
430
430
  or template_data.get("configuration_fields", {}).get("model")
431
- or "sonnet" # Default fallback
431
+ # No default fallback - preserve None if not set
432
432
  )
433
433
 
434
434
  # Convert tools list to comma-separated string (without spaces for compatibility)
@@ -448,11 +448,11 @@ class AgentTemplateBuilder:
448
448
  "opus": "opus",
449
449
  }
450
450
 
451
- if model in model_map:
452
- model = model_map[model]
453
- else:
454
- # Default to sonnet if model not found in map
455
- model = "sonnet"
451
+ # Only map model if it's not None
452
+ if model is not None:
453
+ if model in model_map:
454
+ model = model_map[model]
455
+ # If model is specified but not in map, keep as-is (no default)
456
456
 
457
457
  # Get response format from template or use base agent default
458
458
  template_data.get("response", {}).get("format", "structured")
@@ -559,8 +559,9 @@ class AgentTemplateBuilder:
559
559
  f"description: {self._format_description_for_yaml(description)}"
560
560
  )
561
561
 
562
- # Add model field (required for Claude Code)
563
- frontmatter_lines.append(f"model: {model}")
562
+ # Add model field only if explicitly set (not required for Claude Code)
563
+ if model is not None:
564
+ frontmatter_lines.append(f"model: {model}")
564
565
 
565
566
  # Add type field (important for agent categorization)
566
567
  if agent_type and agent_type != "general":
@@ -718,21 +719,32 @@ Only include memories that are:
718
719
  "description", f"{name} agent for specialized tasks"
719
720
  )
720
721
 
721
- # Get tools and model with fallbacks
722
+ # Get tools and model (no fallback for model)
722
723
  raw_tools = merged_config.get("tools")
723
724
  tools = self.normalize_tools_input(raw_tools)
724
- model = merged_config.get("model", "sonnet")
725
+ model = merged_config.get("model") # No default - preserve None
725
726
 
726
727
  # Format tools as YAML list
727
728
  tools_yaml = self.format_yaml_list(tools, 2)
728
729
 
729
730
  # Build YAML content with only essential fields
730
- return f"""name: {name}
731
- description: {description}
732
- model: {model}
733
- tools:
734
- {tools_yaml}
735
- """
731
+ yaml_lines = [
732
+ f"name: {name}",
733
+ f"description: {description}",
734
+ ]
735
+
736
+ # Only include model if explicitly set
737
+ if model is not None:
738
+ yaml_lines.append(f"model: {model}")
739
+
740
+ yaml_lines.extend(
741
+ [
742
+ "tools:",
743
+ tools_yaml,
744
+ ]
745
+ )
746
+
747
+ return "\n".join(yaml_lines) + "\n"
736
748
 
737
749
  def merge_narrative_fields(self, base_data: dict, template_data: dict) -> dict:
738
750
  """
@@ -8,7 +8,7 @@ DEPLOYMENT ARCHITECTURE:
8
8
 
9
9
  Agent Source Locations (Discovery):
10
10
  -----------------------------------
11
- 1. System Agents: ~/.claude-mpm/cache/remote-agents/bobmatnyc/claude-mpm-agents/
11
+ 1. System Agents: ~/.claude-mpm/cache/agents/bobmatnyc/claude-mpm-agents/
12
12
  - Synced from GitHub repository
13
13
  - Read-only (managed by git pull)
14
14
  - 44+ agents organized by category
@@ -39,7 +39,7 @@ Why Project-Level Deployment?
39
39
  Example Flow:
40
40
  -------------
41
41
  1. User runs: claude-mpm agents deploy
42
- 2. Agents synced from GitHub → ~/.claude-mpm/cache/remote-agents/
42
+ 2. Agents synced from GitHub → ~/.claude-mpm/cache/agents/
43
43
  3. Agents deployed FROM cache → .claude/agents/
44
44
  4. Claude Code discovers agents FROM .claude/agents/
45
45