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.
- claude_mpm/VERSION +1 -1
- claude_mpm/cli/commands/commander.py +46 -0
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/executor.py +9 -0
- claude_mpm/cli/parsers/base_parser.py +17 -0
- claude_mpm/cli/parsers/commander_parser.py +83 -0
- claude_mpm/cli/parsers/run_parser.py +10 -0
- claude_mpm/cli/utils.py +7 -3
- claude_mpm/commander/__init__.py +72 -0
- claude_mpm/commander/adapters/__init__.py +31 -0
- claude_mpm/commander/adapters/base.py +191 -0
- claude_mpm/commander/adapters/claude_code.py +361 -0
- claude_mpm/commander/adapters/communication.py +366 -0
- claude_mpm/commander/api/__init__.py +16 -0
- claude_mpm/commander/api/app.py +105 -0
- claude_mpm/commander/api/errors.py +112 -0
- claude_mpm/commander/api/routes/__init__.py +8 -0
- claude_mpm/commander/api/routes/events.py +184 -0
- claude_mpm/commander/api/routes/inbox.py +171 -0
- claude_mpm/commander/api/routes/messages.py +148 -0
- claude_mpm/commander/api/routes/projects.py +271 -0
- claude_mpm/commander/api/routes/sessions.py +215 -0
- claude_mpm/commander/api/routes/work.py +260 -0
- claude_mpm/commander/api/schemas.py +182 -0
- claude_mpm/commander/chat/__init__.py +7 -0
- claude_mpm/commander/chat/cli.py +107 -0
- claude_mpm/commander/chat/commands.py +96 -0
- claude_mpm/commander/chat/repl.py +310 -0
- claude_mpm/commander/config.py +49 -0
- claude_mpm/commander/config_loader.py +115 -0
- claude_mpm/commander/daemon.py +398 -0
- claude_mpm/commander/events/__init__.py +26 -0
- claude_mpm/commander/events/manager.py +332 -0
- claude_mpm/commander/frameworks/__init__.py +12 -0
- claude_mpm/commander/frameworks/base.py +143 -0
- claude_mpm/commander/frameworks/claude_code.py +58 -0
- claude_mpm/commander/frameworks/mpm.py +62 -0
- claude_mpm/commander/inbox/__init__.py +16 -0
- claude_mpm/commander/inbox/dedup.py +128 -0
- claude_mpm/commander/inbox/inbox.py +224 -0
- claude_mpm/commander/inbox/models.py +70 -0
- claude_mpm/commander/instance_manager.py +337 -0
- claude_mpm/commander/llm/__init__.py +6 -0
- claude_mpm/commander/llm/openrouter_client.py +167 -0
- claude_mpm/commander/llm/summarizer.py +70 -0
- claude_mpm/commander/models/__init__.py +18 -0
- claude_mpm/commander/models/events.py +121 -0
- claude_mpm/commander/models/project.py +162 -0
- claude_mpm/commander/models/work.py +214 -0
- claude_mpm/commander/parsing/__init__.py +20 -0
- claude_mpm/commander/parsing/extractor.py +132 -0
- claude_mpm/commander/parsing/output_parser.py +270 -0
- claude_mpm/commander/parsing/patterns.py +100 -0
- claude_mpm/commander/persistence/__init__.py +11 -0
- claude_mpm/commander/persistence/event_store.py +274 -0
- claude_mpm/commander/persistence/state_store.py +309 -0
- claude_mpm/commander/persistence/work_store.py +164 -0
- claude_mpm/commander/polling/__init__.py +13 -0
- claude_mpm/commander/polling/event_detector.py +104 -0
- claude_mpm/commander/polling/output_buffer.py +49 -0
- claude_mpm/commander/polling/output_poller.py +153 -0
- claude_mpm/commander/project_session.py +268 -0
- claude_mpm/commander/proxy/__init__.py +12 -0
- claude_mpm/commander/proxy/formatter.py +89 -0
- claude_mpm/commander/proxy/output_handler.py +191 -0
- claude_mpm/commander/proxy/relay.py +155 -0
- claude_mpm/commander/registry.py +404 -0
- claude_mpm/commander/runtime/__init__.py +10 -0
- claude_mpm/commander/runtime/executor.py +191 -0
- claude_mpm/commander/runtime/monitor.py +316 -0
- claude_mpm/commander/session/__init__.py +6 -0
- claude_mpm/commander/session/context.py +81 -0
- claude_mpm/commander/session/manager.py +59 -0
- claude_mpm/commander/tmux_orchestrator.py +361 -0
- claude_mpm/commander/web/__init__.py +1 -0
- claude_mpm/commander/work/__init__.py +30 -0
- claude_mpm/commander/work/executor.py +189 -0
- claude_mpm/commander/work/queue.py +405 -0
- claude_mpm/commander/workflow/__init__.py +27 -0
- claude_mpm/commander/workflow/event_handler.py +219 -0
- claude_mpm/commander/workflow/notifier.py +146 -0
- claude_mpm/config/agent_presets.py +2 -1
- claude_mpm/core/logger.py +1 -1
- claude_mpm/core/logging_utils.py +35 -13
- claude_mpm/core/unified_config.py +3 -2
- claude_mpm/core/unified_paths.py +68 -80
- claude_mpm/hooks/claude_hooks/hook_handler.py +67 -80
- claude_mpm/hooks/claude_hooks/installer.py +6 -3
- claude_mpm/hooks/claude_hooks/memory_integration.py +22 -11
- claude_mpm/services/skills/git_skill_source_manager.py +51 -2
- {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/METADATA +13 -1
- {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/RECORD +97 -37
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/WHEEL +0 -0
- {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.5.0.dist-info → claude_mpm-5.6.2.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {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
|
|
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(
|
|
232
|
+
console_handler.setLevel(log_level) # Respect the requested log level
|
|
233
233
|
logger.addHandler(console_handler)
|
|
234
234
|
|
|
235
235
|
# File handler
|
claude_mpm/core/logging_utils.py
CHANGED
|
@@ -103,23 +103,45 @@ class LoggerFactory:
|
|
|
103
103
|
|
|
104
104
|
# Set up root logger
|
|
105
105
|
root_logger = logging.getLogger()
|
|
106
|
-
|
|
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
|
-
#
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
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
|
|
88
|
+
description="Agents that are always deployed (standard 7 core agents)",
|
|
88
89
|
)
|
|
89
90
|
|
|
90
91
|
include_universal: bool = Field(
|
claude_mpm/core/unified_paths.py
CHANGED
|
@@ -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.
|
|
194
|
-
3.
|
|
195
|
-
|
|
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
|
-
#
|
|
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
|
|
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
|
-
#
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
#
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
#
|
|
276
|
-
if "
|
|
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
|
-
#
|
|
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
|
|
252
|
+
"ImportError during module path detection, checking CWD as fallback"
|
|
298
253
|
)
|
|
299
|
-
|
|
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:
|