claude-mpm 5.1.9__py3-none-any.whl → 5.4.48__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 (248) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/BASE_AGENT.md +164 -0
  4. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +1 -1
  5. claude_mpm/agents/MEMORY.md +1 -1
  6. claude_mpm/agents/PM_INSTRUCTIONS.md +843 -900
  7. claude_mpm/agents/WORKFLOW.md +5 -254
  8. claude_mpm/agents/agent_loader.py +13 -44
  9. claude_mpm/agents/base_agent.json +1 -1
  10. claude_mpm/agents/frontmatter_validator.py +2 -2
  11. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  12. claude_mpm/cli/__main__.py +4 -0
  13. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  14. claude_mpm/cli/commands/agent_state_manager.py +18 -27
  15. claude_mpm/cli/commands/agents.py +9 -40
  16. claude_mpm/cli/commands/auto_configure.py +210 -25
  17. claude_mpm/cli/commands/config.py +88 -2
  18. claude_mpm/cli/commands/configure.py +1098 -159
  19. claude_mpm/cli/commands/configure_agent_display.py +25 -6
  20. claude_mpm/cli/commands/mpm_init/core.py +225 -46
  21. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  22. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  23. claude_mpm/cli/commands/postmortem.py +1 -1
  24. claude_mpm/cli/commands/profile.py +277 -0
  25. claude_mpm/cli/commands/skills.py +218 -197
  26. claude_mpm/cli/commands/summarize.py +413 -0
  27. claude_mpm/cli/executor.py +21 -3
  28. claude_mpm/cli/interactive/agent_wizard.py +2 -2
  29. claude_mpm/cli/parsers/agents_parser.py +0 -9
  30. claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
  31. claude_mpm/cli/parsers/base_parser.py +12 -0
  32. claude_mpm/cli/parsers/config_parser.py +153 -83
  33. claude_mpm/cli/parsers/profile_parser.py +148 -0
  34. claude_mpm/cli/parsers/skills_parser.py +0 -5
  35. claude_mpm/cli/startup.py +876 -149
  36. claude_mpm/commands/mpm-config.md +28 -0
  37. claude_mpm/commands/mpm-doctor.md +9 -22
  38. claude_mpm/commands/mpm-help.md +5 -287
  39. claude_mpm/commands/mpm-init.md +81 -507
  40. claude_mpm/commands/mpm-monitor.md +15 -402
  41. claude_mpm/commands/mpm-organize.md +120 -0
  42. claude_mpm/commands/mpm-postmortem.md +6 -108
  43. claude_mpm/commands/mpm-session-resume.md +12 -363
  44. claude_mpm/commands/mpm-status.md +5 -69
  45. claude_mpm/commands/mpm-ticket-view.md +52 -495
  46. claude_mpm/commands/mpm-version.md +5 -107
  47. claude_mpm/config/agent_sources.py +27 -0
  48. claude_mpm/core/config.py +2 -4
  49. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  50. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  51. claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
  52. claude_mpm/core/framework_loader.py +4 -2
  53. claude_mpm/core/logger.py +13 -0
  54. claude_mpm/core/optimized_startup.py +59 -0
  55. claude_mpm/core/shared/config_loader.py +1 -1
  56. claude_mpm/core/socketio_pool.py +3 -3
  57. claude_mpm/core/unified_agent_registry.py +5 -15
  58. claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
  59. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +1 -0
  60. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +1 -0
  61. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +1 -0
  62. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +1 -0
  63. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +1 -0
  64. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +2 -0
  65. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DjhvlsAc.js +1 -0
  66. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/N4qtv3Hx.js +2 -0
  67. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uj46x2Wr.js +1 -0
  68. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +2 -0
  69. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +1 -0
  70. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.CAGBuiOw.js +1 -0
  71. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +1 -0
  72. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +10 -0
  73. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
  74. claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
  75. claude_mpm/dashboard/static/svelte-build/index.html +36 -0
  76. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  77. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  78. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  79. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  80. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  81. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  82. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  83. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  84. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  85. claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
  86. claude_mpm/hooks/claude_hooks/hook_handler.py +155 -1
  87. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  88. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  89. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  90. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  91. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  92. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  93. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  94. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  95. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  96. claude_mpm/hooks/claude_hooks/services/connection_manager.py +30 -6
  97. claude_mpm/hooks/kuzu_memory_hook.py +5 -5
  98. claude_mpm/hooks/memory_integration_hook.py +46 -1
  99. claude_mpm/init.py +63 -19
  100. claude_mpm/models/git_repository.py +3 -3
  101. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  102. claude_mpm/scripts/launch_monitor.py +93 -13
  103. claude_mpm/services/agents/agent_builder.py +3 -3
  104. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  105. claude_mpm/services/agents/agent_review_service.py +280 -0
  106. claude_mpm/services/agents/cache_git_manager.py +6 -6
  107. claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
  108. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -5
  109. claude_mpm/services/agents/deployment/agent_format_converter.py +23 -13
  110. claude_mpm/services/agents/deployment/agent_template_builder.py +32 -20
  111. claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
  112. claude_mpm/services/agents/deployment/async_agent_deployment.py +31 -27
  113. claude_mpm/services/agents/deployment/local_template_deployment.py +3 -1
  114. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +247 -35
  115. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +392 -87
  116. claude_mpm/services/agents/git_source_manager.py +53 -4
  117. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  118. claude_mpm/services/agents/recommender.py +5 -3
  119. claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
  120. claude_mpm/services/agents/sources/git_source_sync_service.py +120 -7
  121. claude_mpm/services/agents/startup_sync.py +22 -2
  122. claude_mpm/services/agents/toolchain_detector.py +10 -6
  123. claude_mpm/services/analysis/__init__.py +11 -1
  124. claude_mpm/services/analysis/clone_detector.py +1030 -0
  125. claude_mpm/services/command_deployment_service.py +81 -10
  126. claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
  127. claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
  128. claude_mpm/services/event_bus/config.py +3 -1
  129. claude_mpm/services/git/git_operations_service.py +101 -16
  130. claude_mpm/services/monitor/daemon.py +9 -2
  131. claude_mpm/services/monitor/daemon_manager.py +39 -3
  132. claude_mpm/services/monitor/management/lifecycle.py +8 -1
  133. claude_mpm/services/monitor/server.py +698 -22
  134. claude_mpm/services/pm_skills_deployer.py +711 -0
  135. claude_mpm/services/profile_manager.py +331 -0
  136. claude_mpm/services/self_upgrade_service.py +120 -12
  137. claude_mpm/services/skills/__init__.py +3 -0
  138. claude_mpm/services/skills/git_skill_source_manager.py +130 -2
  139. claude_mpm/services/skills/selective_skill_deployer.py +704 -0
  140. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  141. claude_mpm/services/skills_deployer.py +127 -9
  142. claude_mpm/services/socketio/dashboard_server.py +1 -0
  143. claude_mpm/services/socketio/event_normalizer.py +51 -6
  144. claude_mpm/services/socketio/server/core.py +386 -108
  145. claude_mpm/services/version_control/git_operations.py +103 -0
  146. claude_mpm/skills/skill_manager.py +92 -3
  147. claude_mpm/utils/agent_dependency_loader.py +14 -2
  148. claude_mpm/utils/agent_filters.py +17 -44
  149. claude_mpm/utils/migration.py +4 -4
  150. claude_mpm/utils/robust_installer.py +47 -3
  151. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/METADATA +53 -87
  152. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/RECORD +157 -197
  153. claude_mpm-5.4.48.dist-info/entry_points.txt +5 -0
  154. claude_mpm-5.4.48.dist-info/licenses/LICENSE +94 -0
  155. claude_mpm-5.4.48.dist-info/licenses/LICENSE-FAQ.md +153 -0
  156. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  157. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  158. claude_mpm/agents/BASE_OPS.md +0 -219
  159. claude_mpm/agents/BASE_PM.md +0 -480
  160. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  161. claude_mpm/agents/BASE_QA.md +0 -167
  162. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  163. claude_mpm/agents/base_agent_loader.py +0 -601
  164. claude_mpm/cli/commands/agents_detect.py +0 -380
  165. claude_mpm/cli/commands/agents_recommend.py +0 -309
  166. claude_mpm/cli/ticket_cli.py +0 -35
  167. claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
  168. claude_mpm/commands/mpm-agents-detect.md +0 -177
  169. claude_mpm/commands/mpm-agents-list.md +0 -131
  170. claude_mpm/commands/mpm-agents-recommend.md +0 -223
  171. claude_mpm/commands/mpm-config-view.md +0 -150
  172. claude_mpm/commands/mpm-ticket-organize.md +0 -304
  173. claude_mpm/dashboard/analysis_runner.py +0 -455
  174. claude_mpm/dashboard/index.html +0 -13
  175. claude_mpm/dashboard/open_dashboard.py +0 -66
  176. claude_mpm/dashboard/static/css/activity.css +0 -1958
  177. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  178. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  179. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  180. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  181. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  182. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  183. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  184. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  185. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  186. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  187. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  188. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  189. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  190. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  191. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  192. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  193. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  194. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  195. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  196. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  197. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  198. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  199. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  200. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  201. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  202. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  203. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  204. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  205. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  206. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  207. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  208. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  209. claude_mpm/dashboard/templates/code_simple.html +0 -153
  210. claude_mpm/dashboard/templates/index.html +0 -606
  211. claude_mpm/dashboard/test_dashboard.html +0 -372
  212. claude_mpm/scripts/mcp_server.py +0 -75
  213. claude_mpm/scripts/mcp_wrapper.py +0 -39
  214. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  215. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  216. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  217. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  218. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  219. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  220. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  221. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  222. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  223. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  224. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  225. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  226. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  227. claude_mpm/services/mcp_gateway/main.py +0 -589
  228. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  229. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  230. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  231. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  232. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  233. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  234. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  235. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  236. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  237. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  238. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  239. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  240. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  241. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  242. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  243. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  244. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  245. claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
  246. claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
  247. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/WHEEL +0 -0
  248. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.48.dist-info}/top_level.txt +0 -0
@@ -18,6 +18,11 @@ from dataclasses import dataclass, field
18
18
  from datetime import datetime, timezone
19
19
  from typing import Any, Dict, List, Optional
20
20
 
21
+ # Privileged users who can push directly to main branch
22
+ # All other users must use feature branches and PRs
23
+ PRIVILEGED_GIT_USERS = ["bobmatnyc@users.noreply.github.com"]
24
+ PROTECTED_BRANCHES = ["main", "master"]
25
+
21
26
 
22
27
  @dataclass
23
28
  class GitBranchInfo:
@@ -101,6 +106,94 @@ class GitOperationsManager:
101
106
  if not self._is_git_repository():
102
107
  raise GitOperationError(f"Not a Git repository: {project_root}")
103
108
 
109
+ def _get_current_git_user(self) -> str:
110
+ """
111
+ Get the current Git user email.
112
+
113
+ Returns:
114
+ Git user email configured in repository or globally
115
+
116
+ Raises:
117
+ GitOperationError: If git user.email is not configured
118
+ """
119
+ try:
120
+ result = self._run_git_command(["config", "user.email"])
121
+ email = result.stdout.strip()
122
+ if not email:
123
+ raise GitOperationError(
124
+ "Git user.email is not configured. "
125
+ "Please configure it with: git config user.email 'your@email.com'"
126
+ )
127
+ return email
128
+ except GitOperationError as e:
129
+ raise GitOperationError(
130
+ "Git user.email is not configured. "
131
+ "Please configure it with: git config user.email 'your@email.com'"
132
+ ) from e
133
+
134
+ def _is_privileged_user(self) -> bool:
135
+ """
136
+ Check if the current Git user is privileged to push to protected branches.
137
+
138
+ Returns:
139
+ True if user email is in PRIVILEGED_GIT_USERS, False otherwise
140
+ """
141
+ try:
142
+ current_user = self._get_current_git_user()
143
+ return current_user in PRIVILEGED_GIT_USERS
144
+ except GitOperationError:
145
+ # If we can't determine user, assume not privileged
146
+ return False
147
+
148
+ def _enforce_branch_protection(
149
+ self, target_branch: str, operation: str
150
+ ) -> Optional[GitOperationResult]:
151
+ """
152
+ Enforce branch protection rules for protected branches.
153
+
154
+ Args:
155
+ target_branch: Branch being operated on
156
+ operation: Operation being performed (e.g., "push", "merge")
157
+
158
+ Returns:
159
+ GitOperationResult with error if protection violated, None if allowed
160
+ """
161
+ # Check if target branch is protected
162
+ if target_branch not in PROTECTED_BRANCHES:
163
+ return None
164
+
165
+ # Check if user is privileged
166
+ if self._is_privileged_user():
167
+ return None
168
+
169
+ # Get current user for error message
170
+ try:
171
+ current_user = self._get_current_git_user()
172
+ except GitOperationError:
173
+ current_user = "unknown"
174
+
175
+ # Build helpful error message
176
+ error_message = (
177
+ f"Direct {operation} to '{target_branch}' branch is restricted.\n"
178
+ f"Only {', '.join(PRIVILEGED_GIT_USERS)} can {operation} directly to protected branches.\n"
179
+ f"Current user: {current_user}\n\n"
180
+ f"Please use the feature branch workflow:\n"
181
+ f" 1. git checkout -b feature/your-feature-name\n"
182
+ f" 2. Make your changes and commit\n"
183
+ f" 3. git push -u origin feature/your-feature-name\n"
184
+ f" 4. Create a Pull Request on GitHub for review"
185
+ )
186
+
187
+ return GitOperationResult(
188
+ success=False,
189
+ operation=f"{operation}_branch_protection",
190
+ message=f"Branch protection: {operation} to '{target_branch}' denied",
191
+ error=error_message,
192
+ branch_before=self.get_current_branch(),
193
+ branch_after=self.get_current_branch(),
194
+ execution_time=0.0,
195
+ )
196
+
104
197
  def _is_git_repository(self) -> bool:
105
198
  """Check if the directory is a Git repository."""
106
199
  return self.git_dir.exists() and self.git_dir.is_dir()
@@ -503,6 +596,11 @@ class GitOperationsManager:
503
596
  start_time = datetime.now(timezone.utc)
504
597
  current_branch = self.get_current_branch()
505
598
 
599
+ # Enforce branch protection for target branch
600
+ protection_result = self._enforce_branch_protection(target_branch, "merge")
601
+ if protection_result:
602
+ return protection_result
603
+
506
604
  try:
507
605
  # Switch to target branch
508
606
  if current_branch != target_branch:
@@ -659,6 +757,11 @@ class GitOperationsManager:
659
757
  if not branch_name:
660
758
  branch_name = current_branch
661
759
 
760
+ # Enforce branch protection
761
+ protection_result = self._enforce_branch_protection(branch_name, "push")
762
+ if protection_result:
763
+ return protection_result
764
+
662
765
  try:
663
766
  # Build push command
664
767
  push_args = ["push"]
@@ -2,7 +2,9 @@
2
2
 
3
3
  import json
4
4
  from pathlib import Path
5
- from typing import Dict, List, Optional
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ import yaml
6
8
 
7
9
  from claude_mpm.core.logging_utils import get_logger
8
10
 
@@ -56,12 +58,79 @@ class SkillManager:
56
58
  if mapping_count > 0:
57
59
  logger.info(f"Loaded skill mappings for {mapping_count} agents")
58
60
 
61
+ def _get_pm_skills(self, project_dir: Optional[Path] = None) -> List[Dict[str, Any]]:
62
+ """Load PM skills from project's .claude-mpm/skills/pm/ directory.
63
+
64
+ PM skills are special required skills deployed per-project,
65
+ NOT fetched from the skills repository.
66
+
67
+ Args:
68
+ project_dir: Project directory. Defaults to current working directory.
69
+
70
+ Returns:
71
+ List of PM skill dictionaries with metadata
72
+ """
73
+ if project_dir is None:
74
+ project_dir = Path.cwd()
75
+
76
+ pm_skills_dir = project_dir / ".claude-mpm" / "skills" / "pm"
77
+
78
+ if not pm_skills_dir.exists():
79
+ logger.debug("PM skills directory not found")
80
+ return []
81
+
82
+ skills = []
83
+ for skill_dir in pm_skills_dir.iterdir():
84
+ if skill_dir.is_dir():
85
+ skill_file = skill_dir / "SKILL.md"
86
+ if skill_file.exists():
87
+ skill = self._load_pm_skill(skill_file)
88
+ if skill:
89
+ skills.append(skill)
90
+
91
+ if skills:
92
+ logger.debug(f"Loaded {len(skills)} PM skills from {pm_skills_dir}")
93
+
94
+ return skills
95
+
96
+ def _load_pm_skill(self, skill_file: Path) -> Optional[Dict[str, Any]]:
97
+ """Load a single PM skill from SKILL.md file.
98
+
99
+ Args:
100
+ skill_file: Path to SKILL.md file
101
+
102
+ Returns:
103
+ Dictionary with skill metadata and content, or None if failed
104
+ """
105
+ try:
106
+ content = skill_file.read_text(encoding='utf-8')
107
+
108
+ # Parse YAML frontmatter
109
+ if content.startswith('---'):
110
+ parts = content.split('---', 2)
111
+ if len(parts) >= 3:
112
+ metadata = yaml.safe_load(parts[1])
113
+ body = parts[2].strip()
114
+
115
+ return {
116
+ 'name': metadata.get('name', skill_file.parent.name),
117
+ 'version': metadata.get('version', '1.0.0'),
118
+ 'description': metadata.get('description', ''),
119
+ 'when_to_use': metadata.get('when_to_use', ''),
120
+ 'content': body,
121
+ 'is_pm_skill': True
122
+ }
123
+ except Exception as e:
124
+ logger.warning(f"Failed to load PM skill {skill_file}: {e}")
125
+
126
+ return None
127
+
59
128
  def get_agent_skills(self, agent_type: str) -> List[Skill]:
60
129
  """
61
- Get all skills for an agent (bundled + discovered).
130
+ Get all skills for an agent (bundled + discovered + PM skills if PM agent).
62
131
 
63
132
  Args:
64
- agent_type: Agent type/ID (e.g., 'engineer', 'python_engineer')
133
+ agent_type: Agent type/ID (e.g., 'engineer', 'python_engineer', 'pm')
65
134
 
66
135
  Returns:
67
136
  List of Skill objects for this agent
@@ -86,6 +155,26 @@ class SkillManager:
86
155
  if skill not in skills:
87
156
  skills.append(skill)
88
157
 
158
+ # Add PM skills for PM agent only
159
+ if agent_type.lower() in ('pm', 'project-manager', 'project_manager'):
160
+ pm_skill_dicts = self._get_pm_skills()
161
+ for pm_skill_dict in pm_skill_dicts:
162
+ # Convert PM skill dict to Skill object
163
+ pm_skill = Skill(
164
+ name=pm_skill_dict['name'],
165
+ path=Path.cwd() / ".claude-mpm" / "skills" / "pm" / pm_skill_dict['name'],
166
+ content=pm_skill_dict['content'],
167
+ source='pm', # Special source type for PM skills
168
+ version=pm_skill_dict['version'],
169
+ skill_id=pm_skill_dict['name'],
170
+ description=pm_skill_dict['description'],
171
+ agent_types=['pm', 'project-manager', 'project_manager']
172
+ )
173
+ skills.append(pm_skill)
174
+
175
+ if pm_skill_dicts:
176
+ logger.debug(f"Added {len(pm_skill_dicts)} PM skills for PM agent")
177
+
89
178
  return skills
90
179
 
91
180
  def enhance_agent_prompt(
@@ -658,12 +658,24 @@ class AgentDependencyLoader:
658
658
  "Robust installer not available, falling back to simple installation"
659
659
  )
660
660
  try:
661
- cmd = [sys.executable, "-m", "pip", "install"]
662
-
663
661
  # Check environment and add appropriate flags
664
662
  import os
665
663
  import sysconfig
666
664
 
665
+ # Check if in UV tool environment (no pip available)
666
+ uv_tool_dir = os.environ.get("UV_TOOL_DIR", "")
667
+ is_uv_tool = (
668
+ (uv_tool_dir and "claude-mpm" in uv_tool_dir)
669
+ or ".local/share/uv/tools/" in sys.executable
670
+ or "/uv/tools/" in sys.executable
671
+ )
672
+
673
+ if is_uv_tool:
674
+ cmd = ["uv", "pip", "install"]
675
+ logger.debug("Using 'uv pip install' for UV tool environment")
676
+ else:
677
+ cmd = [sys.executable, "-m", "pip", "install"]
678
+
667
679
  # Check if in virtualenv
668
680
  in_virtualenv = (
669
681
  (hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix)
@@ -4,10 +4,14 @@ Agent filtering utilities for claude-mpm.
4
4
  WHY: This module provides centralized filtering logic to remove non-deployable
5
5
  agents (BASE_AGENT) and already-deployed agents from user-facing displays.
6
6
 
7
+ ARCHITECTURE:
8
+ - SOURCE: ~/.claude-mpm/cache/agents/ (git repository cache)
9
+ - DEPLOYMENT: .claude/agents/ (project-level deployment location)
10
+
7
11
  DESIGN DECISIONS:
8
12
  - BASE_AGENT is a build tool, not a deployable agent - filter everywhere
9
- - Deployed agent detection supports both new (.claude-mpm/agents/) and
10
- legacy (.claude/agents/)
13
+ - Deployed agent detection checks .claude/agents/ for all deployed agents
14
+ - Supports both virtual (.mpm_deployment_state) and physical (.md files) detection
11
15
  - Case-insensitive BASE_AGENT detection for robustness
12
16
  - Pure functions for easy testing and reuse
13
17
 
@@ -102,10 +106,11 @@ def get_deployed_agent_ids(project_dir: Optional[Path] = None) -> Set[str]:
102
106
 
103
107
  Design Rationale:
104
108
  - Primary detection: Virtual deployment state (.mpm_deployment_state)
105
- - Fallback detection: Physical .md files (.claude-mpm/agents/, .claude/agents/)
109
+ - Fallback detection: Physical .md files in .claude/agents/
106
110
  - Returns leaf names for consistent comparison with agent_id formats
107
111
  - Combines both detection methods for complete coverage
108
112
  - Graceful error handling for malformed or missing state files
113
+ - Only checks project-level deployment (simplified architecture)
109
114
 
110
115
  Related:
111
116
  - Fixes checkbox interface showing all agents as "○ [Available]" instead of "● [Installed]"
@@ -115,24 +120,17 @@ def get_deployed_agent_ids(project_dir: Optional[Path] = None) -> Set[str]:
115
120
  deployed = set()
116
121
 
117
122
  # Track if project_dir was explicitly provided
118
- explicit_project_dir = project_dir is not None
119
123
 
120
124
  if project_dir is None:
121
125
  project_dir = Path.cwd()
122
126
 
123
127
  # NEW: Check virtual deployment state (primary method)
124
128
  # This is the current deployment model used by Claude Code
129
+ # Only checking project-level deployment in simplified architecture
125
130
  deployment_state_paths = [
126
131
  project_dir / ".claude" / "agents" / ".mpm_deployment_state",
127
132
  ]
128
133
 
129
- # Only check user-level state if using default project directory
130
- # This prevents test isolation issues when explicit project_dir is provided
131
- if not explicit_project_dir:
132
- deployment_state_paths.append(
133
- Path.home() / ".claude" / "agents" / ".mpm_deployment_state"
134
- )
135
-
136
134
  for state_path in deployment_state_paths:
137
135
  if state_path.exists():
138
136
  try:
@@ -162,42 +160,17 @@ def get_deployed_agent_ids(project_dir: Optional[Path] = None) -> Set[str]:
162
160
  continue
163
161
 
164
162
  # EXISTING: Check physical .md files (fallback for backward compatibility)
165
- # Check new architecture
166
- new_agents_dir = project_dir / ".claude-mpm" / "agents"
167
- if new_agents_dir.exists():
168
- for file in new_agents_dir.glob("*.md"):
169
- if file.stem not in {"BASE-AGENT", ".DS_Store"}:
170
- deployed.add(file.stem)
171
-
172
- # Check legacy architecture
173
- legacy_agents_dir = project_dir / ".claude" / "agents"
174
- if legacy_agents_dir.exists():
175
- for file in legacy_agents_dir.glob("*.md"):
163
+ # Check project deployment location (.claude/agents/)
164
+ agents_dir = project_dir / ".claude" / "agents"
165
+ if agents_dir.exists():
166
+ for file in agents_dir.glob("*.md"):
176
167
  if file.stem not in {"BASE-AGENT", ".DS_Store"}:
177
168
  deployed.add(file.stem)
178
169
 
179
- # Check .claude/templates/ directory (where agents are actually deployed)
180
- templates_dir = project_dir / ".claude" / "templates"
181
- if templates_dir.exists():
182
- for file in templates_dir.glob("*.md"):
183
- if file.stem not in {
184
- "BASE-AGENT",
185
- ".DS_Store",
186
- "README",
187
- "circuit-breakers",
188
- }:
189
- # Skip template/example files
190
- if not any(x in file.stem for x in ["example", "template", "pm-"]):
191
- deployed.add(file.stem)
192
-
193
- # Check user-level directory only if using default project directory
194
- # This prevents test isolation issues when explicit project_dir is provided
195
- if not explicit_project_dir:
196
- user_agents_dir = Path.home() / ".claude" / "agents"
197
- if user_agents_dir.exists():
198
- for file in user_agents_dir.glob("*.md"):
199
- if file.stem not in {"BASE-AGENT", ".DS_Store"}:
200
- deployed.add(file.stem)
170
+ # NOTE: .claude/templates/ contains PM instruction templates, NOT deployed agents
171
+ # It should NOT be checked here. Agents are deployed to:
172
+ # - .mpm_deployment_state (virtual deployment)
173
+ # - .claude/agents/*.md (project deployment)
201
174
 
202
175
  return deployed
203
176
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  WHY: Phase 3 of 1M-486 requires migrating from old single-tier deployment
4
4
  (~/.claude/agents/, ~/.claude/skills/) to new two-phase architecture:
5
- - Cache: ~/.claude-mpm/cache/remote-agents/, ~/.claude-mpm/cache/skills/
5
+ - Cache: ~/.claude-mpm/cache/agents/, ~/.claude-mpm/cache/skills/
6
6
  - Deployment: .claude-mpm/agents/, .claude-mpm/skills/
7
7
 
8
8
  DESIGN DECISIONS:
@@ -41,7 +41,7 @@ class MigrationUtility:
41
41
  self.old_agent_dir = Path.home() / ".claude" / "agents"
42
42
  self.old_skill_dir = Path.home() / ".claude" / "skills"
43
43
 
44
- self.new_agent_cache = Path.home() / ".claude-mpm" / "cache" / "remote-agents"
44
+ self.new_agent_cache = Path.home() / ".claude-mpm" / "cache" / "agents"
45
45
  self.new_skill_cache = Path.home() / ".claude-mpm" / "cache" / "skills"
46
46
 
47
47
  def detect_old_locations(self) -> Dict[str, bool]:
@@ -80,7 +80,7 @@ class MigrationUtility:
80
80
  def migrate_agents(
81
81
  self, dry_run: bool = False, auto_confirm: bool = False
82
82
  ) -> Dict[str, any]:
83
- """Migrate agents from ~/.claude/agents/ to ~/.claude-mpm/cache/remote-agents/.
83
+ """Migrate agents from ~/.claude/agents/ to ~/.claude-mpm/cache/agents/.
84
84
 
85
85
  Args:
86
86
  dry_run: Preview migration without making changes
@@ -329,7 +329,7 @@ class MigrationUtility:
329
329
 
330
330
  warning += "\nThe deployment architecture has changed:\n"
331
331
  warning += " OLD: ~/.claude/agents/ (single-tier, global)\n"
332
- warning += " NEW: ~/.claude-mpm/cache/remote-agents/ → .claude-mpm/agents/ (two-phase, per-project)\n\n"
332
+ warning += " NEW: ~/.claude-mpm/cache/agents/ → .claude-mpm/agents/ (two-phase, per-project)\n\n"
333
333
 
334
334
  warning += "To migrate:\n"
335
335
  warning += " claude-mpm migrate\n\n"
@@ -82,6 +82,7 @@ class RobustPackageInstaller:
82
82
  self.attempts: List[InstallAttempt] = []
83
83
  self.success_cache: Dict[str, bool] = {}
84
84
  self.in_virtualenv = self._check_virtualenv()
85
+ self.is_uv_tool = self._check_uv_tool_installation()
85
86
  self.is_pep668_managed = self._check_pep668_managed()
86
87
  self.pep668_warning_shown = False
87
88
 
@@ -261,6 +262,36 @@ class RobustPackageInstaller:
261
262
 
262
263
  return False
263
264
 
265
+ def _check_uv_tool_installation(self) -> bool:
266
+ """
267
+ Check if running in UV tool environment (no pip available).
268
+
269
+ WHY: UV tool environments don't have pip installed. The executable
270
+ path typically contains ".local/share/uv/tools/" and the UV_TOOL_DIR
271
+ environment variable is set. In such environments, we need to use
272
+ 'uv pip' instead of 'python -m pip'.
273
+
274
+ Returns:
275
+ True if UV tool environment, False otherwise
276
+ """
277
+ import os
278
+
279
+ # Check UV_TOOL_DIR environment variable
280
+ uv_tool_dir = os.environ.get("UV_TOOL_DIR", "")
281
+ if uv_tool_dir and "claude-mpm" in uv_tool_dir:
282
+ logger.debug(f"UV tool environment detected via UV_TOOL_DIR: {uv_tool_dir}")
283
+ return True
284
+
285
+ # Check executable path for UV tool patterns
286
+ executable = sys.executable
287
+ if ".local/share/uv/tools/" in executable or "/uv/tools/" in executable:
288
+ logger.debug(
289
+ f"UV tool environment detected via executable path: {executable}"
290
+ )
291
+ return True
292
+
293
+ return False
294
+
264
295
  def _show_pep668_warning(self) -> None:
265
296
  """
266
297
  Show warning about PEP 668 managed environment.
@@ -301,7 +332,12 @@ class RobustPackageInstaller:
301
332
  Returns:
302
333
  Command as list of arguments
303
334
  """
304
- base_cmd = [sys.executable, "-m", "pip", "install"]
335
+ # UV tool environments don't have pip; use uv pip instead
336
+ if self.is_uv_tool:
337
+ base_cmd = ["uv", "pip", "install"]
338
+ logger.debug("Using 'uv pip install' for UV tool environment")
339
+ else:
340
+ base_cmd = [sys.executable, "-m", "pip", "install"]
305
341
 
306
342
  # Determine appropriate flags based on environment
307
343
  if self.in_virtualenv:
@@ -651,7 +687,12 @@ class RobustPackageInstaller:
651
687
  Tuple of (success, error_message)
652
688
  """
653
689
  try:
654
- cmd = [sys.executable, "-m", "pip", "install"]
690
+ # UV tool environments don't have pip; use uv pip instead
691
+ if self.is_uv_tool:
692
+ cmd = ["uv", "pip", "install"]
693
+ logger.debug("Using 'uv pip install' for batch installation")
694
+ else:
695
+ cmd = [sys.executable, "-m", "pip", "install"]
655
696
 
656
697
  # Add appropriate flags based on environment
657
698
  if self.in_virtualenv:
@@ -702,7 +743,10 @@ class RobustPackageInstaller:
702
743
 
703
744
  # Add environment status
704
745
  lines.append("")
705
- if self.in_virtualenv:
746
+ if self.is_uv_tool:
747
+ lines.append("✓ Environment: UV Tool Environment")
748
+ lines.append(" Using 'uv pip' command (pip not available)")
749
+ elif self.in_virtualenv:
706
750
  lines.append("✓ Environment: Virtual Environment (isolated)")
707
751
  lines.append(" No special pip flags needed")
708
752
  elif self.is_pep668_managed: