claude-mpm 5.5.0__py3-none-any.whl → 5.6.2__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.
Files changed (112) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/commands/commander.py +46 -0
  3. claude_mpm/cli/commands/run.py +35 -3
  4. claude_mpm/cli/executor.py +9 -0
  5. claude_mpm/cli/parsers/base_parser.py +17 -0
  6. claude_mpm/cli/parsers/commander_parser.py +83 -0
  7. claude_mpm/cli/parsers/run_parser.py +10 -0
  8. claude_mpm/cli/utils.py +7 -3
  9. claude_mpm/commander/__init__.py +72 -0
  10. claude_mpm/commander/adapters/__init__.py +31 -0
  11. claude_mpm/commander/adapters/base.py +191 -0
  12. claude_mpm/commander/adapters/claude_code.py +361 -0
  13. claude_mpm/commander/adapters/communication.py +366 -0
  14. claude_mpm/commander/api/__init__.py +16 -0
  15. claude_mpm/commander/api/app.py +105 -0
  16. claude_mpm/commander/api/errors.py +112 -0
  17. claude_mpm/commander/api/routes/__init__.py +8 -0
  18. claude_mpm/commander/api/routes/events.py +184 -0
  19. claude_mpm/commander/api/routes/inbox.py +171 -0
  20. claude_mpm/commander/api/routes/messages.py +148 -0
  21. claude_mpm/commander/api/routes/projects.py +271 -0
  22. claude_mpm/commander/api/routes/sessions.py +215 -0
  23. claude_mpm/commander/api/routes/work.py +260 -0
  24. claude_mpm/commander/api/schemas.py +182 -0
  25. claude_mpm/commander/chat/__init__.py +7 -0
  26. claude_mpm/commander/chat/cli.py +107 -0
  27. claude_mpm/commander/chat/commands.py +96 -0
  28. claude_mpm/commander/chat/repl.py +310 -0
  29. claude_mpm/commander/config.py +49 -0
  30. claude_mpm/commander/config_loader.py +115 -0
  31. claude_mpm/commander/daemon.py +398 -0
  32. claude_mpm/commander/events/__init__.py +26 -0
  33. claude_mpm/commander/events/manager.py +332 -0
  34. claude_mpm/commander/frameworks/__init__.py +12 -0
  35. claude_mpm/commander/frameworks/base.py +143 -0
  36. claude_mpm/commander/frameworks/claude_code.py +58 -0
  37. claude_mpm/commander/frameworks/mpm.py +62 -0
  38. claude_mpm/commander/inbox/__init__.py +16 -0
  39. claude_mpm/commander/inbox/dedup.py +128 -0
  40. claude_mpm/commander/inbox/inbox.py +224 -0
  41. claude_mpm/commander/inbox/models.py +70 -0
  42. claude_mpm/commander/instance_manager.py +337 -0
  43. claude_mpm/commander/llm/__init__.py +6 -0
  44. claude_mpm/commander/llm/openrouter_client.py +167 -0
  45. claude_mpm/commander/llm/summarizer.py +70 -0
  46. claude_mpm/commander/models/__init__.py +18 -0
  47. claude_mpm/commander/models/events.py +121 -0
  48. claude_mpm/commander/models/project.py +162 -0
  49. claude_mpm/commander/models/work.py +214 -0
  50. claude_mpm/commander/parsing/__init__.py +20 -0
  51. claude_mpm/commander/parsing/extractor.py +132 -0
  52. claude_mpm/commander/parsing/output_parser.py +270 -0
  53. claude_mpm/commander/parsing/patterns.py +100 -0
  54. claude_mpm/commander/persistence/__init__.py +11 -0
  55. claude_mpm/commander/persistence/event_store.py +274 -0
  56. claude_mpm/commander/persistence/state_store.py +309 -0
  57. claude_mpm/commander/persistence/work_store.py +164 -0
  58. claude_mpm/commander/polling/__init__.py +13 -0
  59. claude_mpm/commander/polling/event_detector.py +104 -0
  60. claude_mpm/commander/polling/output_buffer.py +49 -0
  61. claude_mpm/commander/polling/output_poller.py +153 -0
  62. claude_mpm/commander/project_session.py +268 -0
  63. claude_mpm/commander/proxy/__init__.py +12 -0
  64. claude_mpm/commander/proxy/formatter.py +89 -0
  65. claude_mpm/commander/proxy/output_handler.py +191 -0
  66. claude_mpm/commander/proxy/relay.py +155 -0
  67. claude_mpm/commander/registry.py +404 -0
  68. claude_mpm/commander/runtime/__init__.py +10 -0
  69. claude_mpm/commander/runtime/executor.py +191 -0
  70. claude_mpm/commander/runtime/monitor.py +316 -0
  71. claude_mpm/commander/session/__init__.py +6 -0
  72. claude_mpm/commander/session/context.py +81 -0
  73. claude_mpm/commander/session/manager.py +59 -0
  74. claude_mpm/commander/tmux_orchestrator.py +361 -0
  75. claude_mpm/commander/web/__init__.py +1 -0
  76. claude_mpm/commander/work/__init__.py +30 -0
  77. claude_mpm/commander/work/executor.py +189 -0
  78. claude_mpm/commander/work/queue.py +405 -0
  79. claude_mpm/commander/workflow/__init__.py +27 -0
  80. claude_mpm/commander/workflow/event_handler.py +219 -0
  81. claude_mpm/commander/workflow/notifier.py +146 -0
  82. claude_mpm/config/agent_presets.py +2 -1
  83. claude_mpm/core/logger.py +1 -1
  84. claude_mpm/core/logging_utils.py +35 -13
  85. claude_mpm/core/unified_config.py +3 -2
  86. claude_mpm/core/unified_paths.py +68 -80
  87. claude_mpm/hooks/claude_hooks/hook_handler.py +67 -80
  88. claude_mpm/hooks/claude_hooks/installer.py +6 -3
  89. claude_mpm/hooks/claude_hooks/memory_integration.py +22 -11
  90. claude_mpm/services/skills/git_skill_source_manager.py +51 -2
  91. {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/METADATA +13 -1
  92. {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/RECORD +97 -37
  93. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  94. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  95. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  96. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  97. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  98. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  99. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  100. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  101. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  102. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  103. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  104. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  105. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  106. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  107. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  108. {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/WHEEL +0 -0
  109. {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/entry_points.txt +0 -0
  110. {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/licenses/LICENSE +0 -0
  111. {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  112. {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,146 @@
1
+ """Basic notification delivery for events.
2
+
3
+ This module provides Notifier which sends notifications for events.
4
+ Currently supports logging, with extensibility for future channels
5
+ (Slack, email, webhooks).
6
+ """
7
+
8
+ import logging
9
+ from dataclasses import dataclass
10
+
11
+ from ..models.events import Event
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @dataclass
17
+ class NotifierConfig:
18
+ """Configuration for notifier.
19
+
20
+ Attributes:
21
+ log_level: Logging level for notifications (default: INFO)
22
+
23
+ Future attributes:
24
+ slack_webhook: URL for Slack webhook notifications
25
+ email_config: SMTP configuration for email notifications
26
+ webhook_urls: List of webhook URLs for custom integrations
27
+ """
28
+
29
+ log_level: str = "INFO"
30
+ # Future: slack_webhook, email_config, webhook_urls
31
+
32
+
33
+ class Notifier:
34
+ """Sends notifications for events.
35
+
36
+ Currently implements logging-based notifications with configurable
37
+ log levels. Designed for extensibility to support future notification
38
+ channels like Slack, email, and webhooks.
39
+
40
+ Attributes:
41
+ config: Notifier configuration
42
+
43
+ Example:
44
+ >>> config = NotifierConfig(log_level="INFO")
45
+ >>> notifier = Notifier(config)
46
+ >>> await notifier.notify(event)
47
+ >>> await notifier.notify_resolution(event, "User responded")
48
+ """
49
+
50
+ def __init__(self, config: NotifierConfig | None = None) -> None:
51
+ """Initialize notifier.
52
+
53
+ Args:
54
+ config: Optional NotifierConfig (uses defaults if not provided)
55
+ """
56
+ self.config = config or NotifierConfig()
57
+
58
+ # Map log level string to logging level
59
+ level_map = {
60
+ "DEBUG": logging.DEBUG,
61
+ "INFO": logging.INFO,
62
+ "WARNING": logging.WARNING,
63
+ "ERROR": logging.ERROR,
64
+ "CRITICAL": logging.CRITICAL,
65
+ }
66
+ self._log_level = level_map.get(self.config.log_level.upper(), logging.INFO)
67
+
68
+ logger.debug("Notifier initialized with log level: %s", self.config.log_level)
69
+
70
+ async def notify(self, event: Event) -> None:
71
+ """Send notification for an event.
72
+
73
+ Currently logs the event at the configured log level. Future versions
74
+ will support additional notification channels.
75
+
76
+ Args:
77
+ event: Event to notify about
78
+
79
+ Example:
80
+ >>> await notifier.notify(event)
81
+ # Logs: [HIGH] Event evt_123: Choose deployment target
82
+ """
83
+ # Format notification message
84
+ message = self._format_event(event)
85
+
86
+ # Log notification
87
+ logger.log(
88
+ self._log_level,
89
+ "Event notification: %s",
90
+ message,
91
+ )
92
+
93
+ # Future: Send to Slack, email, webhooks
94
+ # if self.config.slack_webhook:
95
+ # await self._send_slack(event)
96
+ # if self.config.email_config:
97
+ # await self._send_email(event)
98
+
99
+ async def notify_resolution(self, event: Event, response: str) -> None:
100
+ """Notify that an event was resolved.
101
+
102
+ Logs the resolution with the user's response. Future versions will
103
+ send resolution notifications to configured channels.
104
+
105
+ Args:
106
+ event: Event that was resolved
107
+ response: User's response to the event
108
+
109
+ Example:
110
+ >>> await notifier.notify_resolution(event, "Deploy to staging")
111
+ # Logs: Event evt_123 resolved: Deploy to staging
112
+ """
113
+ message = f"Event {event.id} resolved: {response[:100]}"
114
+
115
+ logger.log(
116
+ self._log_level,
117
+ "Event resolution: %s",
118
+ message,
119
+ )
120
+
121
+ # Future: Send resolution notifications to channels
122
+
123
+ def _format_event(self, event: Event) -> str:
124
+ """Format event for notification display.
125
+
126
+ Args:
127
+ event: Event to format
128
+
129
+ Returns:
130
+ Formatted notification string
131
+
132
+ Example:
133
+ >>> msg = notifier._format_event(event)
134
+ '[HIGH] evt_123 (proj_456): Choose deployment target'
135
+ """
136
+ parts = [
137
+ f"[{event.priority.value.upper()}]",
138
+ f"{event.id}",
139
+ f"({event.project_id})",
140
+ f"{event.title}",
141
+ ]
142
+
143
+ if event.options:
144
+ parts.append(f"Options: {', '.join(event.options)}")
145
+
146
+ return " ".join(parts)
@@ -25,13 +25,14 @@ from typing import Any, Callable, Dict, List, Union
25
25
  PresetResolver = Union[List[str], Callable[[], List[str]]]
26
26
 
27
27
  # Core agents included in ALL presets (MIN and MAX)
28
- # Standard 6 core agents for essential PM workflow functionality
28
+ # Standard 9 core agents for essential PM workflow functionality
29
29
  CORE_AGENTS = [
30
30
  "claude-mpm/mpm-agent-manager", # Agent lifecycle management
31
31
  "claude-mpm/mpm-skills-manager", # Skills management
32
32
  "engineer/core/engineer", # General-purpose implementation
33
33
  "universal/research", # Codebase exploration and analysis
34
34
  "qa/qa", # Testing and quality assurance
35
+ "qa/web-qa", # Browser-based testing specialist
35
36
  "documentation/documentation", # Documentation generation
36
37
  "ops/core/ops", # Basic deployment operations
37
38
  "documentation/ticketing", # Ticket tracking (essential for PM workflow)
claude_mpm/core/logger.py CHANGED
@@ -229,7 +229,7 @@ def setup_logging(
229
229
  console_handler = logging.StreamHandler(sys.stderr)
230
230
  console_handler.setFormatter(formatter if json_format else simple_formatter)
231
231
 
232
- console_handler.setLevel(logging.INFO)
232
+ console_handler.setLevel(log_level) # Respect the requested log level
233
233
  logger.addHandler(console_handler)
234
234
 
235
235
  # File handler
@@ -103,23 +103,45 @@ class LoggerFactory:
103
103
 
104
104
  # Set up root logger
105
105
  root_logger = logging.getLogger()
106
- root_logger.setLevel(LoggingConfig.LEVELS.get(cls._log_level, logging.INFO))
106
+
107
+ # CRITICAL FIX: Respect existing root logger suppression
108
+ # If root logger is already set to CRITICAL+1 (suppressed by startup.py),
109
+ # don't override it. This prevents logging from appearing during startup
110
+ # before the CLI's setup_logging() runs.
111
+ current_level = root_logger.level
112
+ desired_level = LoggingConfig.LEVELS.get(cls._log_level, logging.INFO)
113
+
114
+ # Only set level if current is unset (0) or lower than desired
115
+ # CRITICAL+1 is 51, so this check preserves suppression
116
+ should_configure_logging = current_level == 0 or (
117
+ current_level < desired_level and current_level <= logging.CRITICAL
118
+ )
119
+
120
+ if should_configure_logging:
121
+ root_logger.setLevel(desired_level)
122
+ # else: root logger is suppressed (CRITICAL+1), keep it suppressed
107
123
 
108
124
  # Remove existing handlers
109
125
  root_logger.handlers = []
110
126
 
111
- # Console handler - MUST use stderr to avoid corrupting hook JSON output
112
- # WHY stderr: Hook handlers output JSON to stdout. Logging to stdout
113
- # corrupts this JSON and causes "hook error" messages from Claude Code.
114
- console_handler = logging.StreamHandler(sys.stderr)
115
- console_handler.setLevel(LoggingConfig.LEVELS.get(cls._log_level, logging.INFO))
116
- console_formatter = logging.Formatter(
117
- log_format or LoggingConfig.DEFAULT_FORMAT,
118
- date_format or LoggingConfig.DATE_FORMAT,
119
- )
120
- console_handler.setFormatter(console_formatter)
121
- root_logger.addHandler(console_handler)
122
- cls._handlers["console"] = console_handler
127
+ # CRITICAL FIX: Don't add handlers if logging is suppressed
128
+ # If root logger is at CRITICAL+1 (startup suppression), don't add any handlers
129
+ # This prevents early imports from logging before CLI setup_logging() runs
130
+ if should_configure_logging:
131
+ # Console handler - MUST use stderr to avoid corrupting hook JSON output
132
+ # WHY stderr: Hook handlers output JSON to stdout. Logging to stdout
133
+ # corrupts this JSON and causes "hook error" messages from Claude Code.
134
+ console_handler = logging.StreamHandler(sys.stderr)
135
+ console_handler.setLevel(
136
+ LoggingConfig.LEVELS.get(cls._log_level, logging.INFO)
137
+ )
138
+ console_formatter = logging.Formatter(
139
+ log_format or LoggingConfig.DEFAULT_FORMAT,
140
+ date_format or LoggingConfig.DATE_FORMAT,
141
+ )
142
+ console_handler.setFormatter(console_formatter)
143
+ root_logger.addHandler(console_handler)
144
+ cls._handlers["console"] = console_handler
123
145
 
124
146
  # File handler (optional)
125
147
  if log_to_file and cls._log_dir:
@@ -73,18 +73,19 @@ class AgentConfig(BaseModel):
73
73
  )
74
74
 
75
75
  # Required agents that are always deployed
76
- # Standard 6 core agents for essential PM workflow functionality
76
+ # Standard 7 core agents for essential PM workflow functionality
77
77
  # These are auto-deployed when no agents are specified in configuration
78
78
  required: List[str] = Field(
79
79
  default_factory=lambda: [
80
80
  "engineer", # General-purpose implementation
81
81
  "research", # Codebase exploration and analysis
82
82
  "qa", # Testing and quality assurance
83
+ "web-qa", # Browser-based testing specialist
83
84
  "documentation", # Documentation generation
84
85
  "ops", # Basic deployment operations
85
86
  "ticketing", # Ticket tracking (essential for PM workflow)
86
87
  ],
87
- description="Agents that are always deployed (standard 6 core agents)",
88
+ description="Agents that are always deployed (standard 7 core agents)",
88
89
  )
89
90
 
90
91
  include_universal: bool = Field(
@@ -76,6 +76,7 @@ class DeploymentContext(Enum):
76
76
  EDITABLE_INSTALL = "editable_install"
77
77
  PIP_INSTALL = "pip_install"
78
78
  PIPX_INSTALL = "pipx_install"
79
+ UV_TOOLS = "uv_tools"
79
80
  SYSTEM_PACKAGE = "system_package"
80
81
 
81
82
 
@@ -190,113 +191,100 @@ class PathContext:
190
191
 
191
192
  Priority order:
192
193
  1. Environment variable override (CLAUDE_MPM_DEV_MODE)
193
- 2. Current working directory is a claude-mpm development project
194
- 3. Editable installation detection
195
- 4. Path-based detection (development, pipx, system, pip)
194
+ 2. Package installation path (uv tools, pipx, site-packages, editable)
195
+ 3. Current working directory (opt-in with CLAUDE_MPM_PREFER_LOCAL_SOURCE)
196
+
197
+ This ensures installed packages use their installation paths rather than
198
+ accidentally picking up development paths from CWD.
196
199
  """
197
- # Check for environment variable override
200
+ # 1. Explicit environment variable override
198
201
  if os.environ.get("CLAUDE_MPM_DEV_MODE", "").lower() in ("1", "true", "yes"):
199
202
  logger.debug(
200
203
  "Development mode forced via CLAUDE_MPM_DEV_MODE environment variable"
201
204
  )
202
205
  return DeploymentContext.DEVELOPMENT
203
206
 
204
- # Check if current working directory is a claude-mpm development project
205
- # This handles the case where pipx claude-mpm is run from within the dev directory
206
- cwd = _safe_cwd()
207
- current = cwd
208
- for _ in range(5): # Check up to 5 levels up from current directory
209
- if (current / "pyproject.toml").exists() and (
210
- current / "src" / "claude_mpm"
211
- ).exists():
212
- # Check if this is the claude-mpm project
213
- try:
214
- pyproject_content = (current / "pyproject.toml").read_text()
215
- if (
216
- 'name = "claude-mpm"' in pyproject_content
217
- or '"claude-mpm"' in pyproject_content
218
- ):
219
- logger.debug(
220
- f"Detected claude-mpm development directory at {current}"
221
- )
222
- logger.debug(
223
- "Using development mode for local source preference"
224
- )
225
- return DeploymentContext.DEVELOPMENT
226
- except Exception: # nosec B110
227
- pass
228
- if current == current.parent:
229
- break
230
- current = current.parent
231
-
207
+ # 2. Check where the actual package is installed
232
208
  try:
233
209
  import claude_mpm
234
210
 
235
211
  module_path = Path(claude_mpm.__file__).parent
212
+ package_str = str(module_path)
236
213
 
237
- # First check if this is an editable install, regardless of path
238
- # This is important for cases where pipx points to a development installation
239
- if PathContext._is_editable_install():
240
- logger.debug("Detected editable/development installation")
241
- # Check if we should use development paths
242
- # This could be because we're in a src/ directory or running from dev directory
243
- if module_path.parent.name == "src":
244
- return DeploymentContext.DEVELOPMENT
245
- if "pipx" in str(module_path):
246
- # Running via pipx but from within a development directory
247
- # Use development mode to prefer local source over pipx installation
248
- cwd = _safe_cwd()
249
- current = cwd
250
- for _ in range(5):
251
- if (current / "src" / "claude_mpm").exists() and (
252
- current / "pyproject.toml"
253
- ).exists():
254
- logger.debug(
255
- "Running pipx from development directory, using development mode"
256
- )
257
- return DeploymentContext.DEVELOPMENT
258
- if current == current.parent:
259
- break
260
- current = current.parent
261
- return DeploymentContext.EDITABLE_INSTALL
262
- return DeploymentContext.EDITABLE_INSTALL
214
+ # UV tools installation (~/.local/share/uv/tools/)
215
+ if "/.local/share/uv/tools/" in package_str:
216
+ logger.debug(f"Detected uv tools installation at {module_path}")
217
+ return DeploymentContext.UV_TOOLS
263
218
 
264
- # Check for development mode based on directory structure
265
- # module_path is typically /path/to/project/src/claude_mpm
266
- if (
267
- module_path.parent.name == "src"
268
- and (module_path.parent.parent / "src" / "claude_mpm").exists()
269
- ):
219
+ # pipx installation (~/.local/pipx/venvs/)
220
+ if "/.local/pipx/venvs/" in package_str or "/pipx/" in package_str:
221
+ logger.debug(f"Detected pipx installation at {module_path}")
222
+ return DeploymentContext.PIPX_INSTALL
223
+
224
+ # site-packages (pip install) - but not editable
225
+ if "/site-packages/" in package_str and "/src/" not in package_str:
226
+ logger.debug(f"Detected pip installation at {module_path}")
227
+ return DeploymentContext.PIP_INSTALL
228
+
229
+ # Editable install (pip install -e) - module in src/
230
+ if module_path.parent.name == "src":
231
+ # Check if this is truly an editable install
232
+ if PathContext._is_editable_install():
233
+ logger.debug(f"Detected editable installation at {module_path}")
234
+ return DeploymentContext.EDITABLE_INSTALL
235
+ # Module in src/ but not editable - development mode
270
236
  logger.debug(
271
237
  f"Detected development mode via directory structure at {module_path}"
272
238
  )
273
239
  return DeploymentContext.DEVELOPMENT
274
240
 
275
- # Check for pipx install
276
- if "pipx" in str(module_path):
277
- logger.debug(f"Detected pipx installation at {module_path}")
278
- return DeploymentContext.PIPX_INSTALL
279
-
280
- # Check for system package
281
- if "dist-packages" in str(module_path):
241
+ # dist-packages (system package manager)
242
+ if "dist-packages" in package_str:
282
243
  logger.debug(f"Detected system package installation at {module_path}")
283
244
  return DeploymentContext.SYSTEM_PACKAGE
284
245
 
285
- # Check for site-packages (could be pip or editable)
286
- if "site-packages" in str(module_path):
287
- # Already checked for editable above, so this is a regular pip install
288
- logger.debug(f"Detected pip installation at {module_path}")
289
- return DeploymentContext.PIP_INSTALL
290
-
291
- # Default to pip install
246
+ # Default to pip install for any other installation
292
247
  logger.debug(f"Defaulting to pip installation for {module_path}")
293
248
  return DeploymentContext.PIP_INSTALL
294
249
 
295
250
  except ImportError:
296
251
  logger.debug(
297
- "ImportError during context detection, defaulting to development"
252
+ "ImportError during module path detection, checking CWD as fallback"
298
253
  )
299
- return DeploymentContext.DEVELOPMENT
254
+
255
+ # 3. CWD-based detection (OPT-IN ONLY for explicit development work)
256
+ # Only use CWD if explicitly requested or no package installation found
257
+ if os.environ.get("CLAUDE_MPM_PREFER_LOCAL_SOURCE", "").lower() in (
258
+ "1",
259
+ "true",
260
+ "yes",
261
+ ):
262
+ cwd = _safe_cwd()
263
+ current = cwd
264
+ for _ in range(5): # Check up to 5 levels up from current directory
265
+ if (current / "pyproject.toml").exists() and (
266
+ current / "src" / "claude_mpm"
267
+ ).exists():
268
+ # Check if this is the claude-mpm project
269
+ try:
270
+ pyproject_content = (current / "pyproject.toml").read_text()
271
+ if (
272
+ 'name = "claude-mpm"' in pyproject_content
273
+ or '"claude-mpm"' in pyproject_content
274
+ ):
275
+ logger.debug(
276
+ f"CLAUDE_MPM_PREFER_LOCAL_SOURCE: Using development directory at {current}"
277
+ )
278
+ return DeploymentContext.DEVELOPMENT
279
+ except Exception: # nosec B110
280
+ pass
281
+ if current == current.parent:
282
+ break
283
+ current = current.parent
284
+
285
+ # Final fallback: assume development mode
286
+ logger.debug("No installation detected, defaulting to development mode")
287
+ return DeploymentContext.DEVELOPMENT
300
288
 
301
289
 
302
290
  class UnifiedPathManager: