claude-mpm 5.4.96__py3-none-any.whl → 5.6.17__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md → CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
- claude_mpm/agents/PM_INSTRUCTIONS.md +44 -10
- claude_mpm/agents/WORKFLOW.md +2 -0
- claude_mpm/agents/templates/circuit-breakers.md +26 -17
- claude_mpm/cli/commands/autotodos.py +45 -5
- claude_mpm/cli/commands/commander.py +216 -0
- claude_mpm/cli/commands/hook_errors.py +60 -60
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +5 -3
- claude_mpm/cli/executor.py +32 -17
- claude_mpm/cli/parsers/base_parser.py +17 -0
- claude_mpm/cli/parsers/commander_parser.py +116 -0
- claude_mpm/cli/parsers/run_parser.py +10 -0
- claude_mpm/cli/parsers/skill_source_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +5 -0
- claude_mpm/cli/startup.py +124 -3
- claude_mpm/cli/startup_display.py +2 -1
- claude_mpm/cli/utils.py +7 -3
- claude_mpm/commander/__init__.py +78 -0
- claude_mpm/commander/adapters/__init__.py +60 -0
- claude_mpm/commander/adapters/auggie.py +260 -0
- claude_mpm/commander/adapters/base.py +288 -0
- claude_mpm/commander/adapters/claude_code.py +392 -0
- claude_mpm/commander/adapters/codex.py +237 -0
- claude_mpm/commander/adapters/communication.py +366 -0
- claude_mpm/commander/adapters/example_usage.py +310 -0
- claude_mpm/commander/adapters/mpm.py +389 -0
- claude_mpm/commander/adapters/registry.py +204 -0
- claude_mpm/commander/api/__init__.py +16 -0
- claude_mpm/commander/api/app.py +121 -0
- claude_mpm/commander/api/errors.py +133 -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 +226 -0
- claude_mpm/commander/api/routes/work.py +296 -0
- claude_mpm/commander/api/schemas.py +186 -0
- claude_mpm/commander/chat/__init__.py +7 -0
- claude_mpm/commander/chat/cli.py +111 -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/core/__init__.py +10 -0
- claude_mpm/commander/core/block_manager.py +325 -0
- claude_mpm/commander/core/response_manager.py +323 -0
- claude_mpm/commander/daemon.py +594 -0
- claude_mpm/commander/env_loader.py +59 -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/memory/__init__.py +45 -0
- claude_mpm/commander/memory/compression.py +347 -0
- claude_mpm/commander/memory/embeddings.py +230 -0
- claude_mpm/commander/memory/entities.py +310 -0
- claude_mpm/commander/memory/example_usage.py +290 -0
- claude_mpm/commander/memory/integration.py +325 -0
- claude_mpm/commander/memory/search.py +381 -0
- claude_mpm/commander/memory/store.py +657 -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 +410 -0
- claude_mpm/commander/runtime/__init__.py +10 -0
- claude_mpm/commander/runtime/executor.py +191 -0
- claude_mpm/commander/runtime/monitor.py +346 -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 +207 -0
- claude_mpm/commander/work/queue.py +405 -0
- claude_mpm/commander/workflow/__init__.py +27 -0
- claude_mpm/commander/workflow/event_handler.py +241 -0
- claude_mpm/commander/workflow/notifier.py +146 -0
- claude_mpm/commands/mpm-config.md +8 -0
- claude_mpm/commands/mpm-doctor.md +8 -0
- claude_mpm/commands/mpm-help.md +8 -0
- claude_mpm/commands/mpm-init.md +8 -0
- claude_mpm/commands/mpm-monitor.md +8 -0
- claude_mpm/commands/mpm-organize.md +8 -0
- claude_mpm/commands/mpm-postmortem.md +8 -0
- claude_mpm/commands/mpm-session-resume.md +8 -0
- claude_mpm/commands/mpm-status.md +8 -0
- claude_mpm/commands/mpm-ticket-view.md +8 -0
- claude_mpm/commands/mpm-version.md +8 -0
- claude_mpm/commands/mpm.md +8 -0
- claude_mpm/config/agent_presets.py +8 -7
- claude_mpm/config/skill_sources.py +16 -0
- claude_mpm/core/claude_runner.py +143 -0
- claude_mpm/core/config.py +32 -19
- claude_mpm/core/logger.py +26 -9
- claude_mpm/core/logging_utils.py +35 -11
- claude_mpm/core/output_style_manager.py +49 -12
- claude_mpm/core/unified_config.py +10 -6
- claude_mpm/core/unified_paths.py +68 -80
- claude_mpm/experimental/cli_enhancements.py +2 -1
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +29 -30
- claude_mpm/hooks/claude_hooks/event_handlers.py +112 -99
- claude_mpm/hooks/claude_hooks/hook_handler.py +81 -88
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
- claude_mpm/hooks/claude_hooks/installer.py +116 -8
- claude_mpm/hooks/claude_hooks/memory_integration.py +51 -31
- claude_mpm/hooks/claude_hooks/response_tracking.py +39 -58
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-314.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__/connection_manager_http.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +23 -28
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +36 -103
- claude_mpm/hooks/claude_hooks/services/state_manager.py +23 -36
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +47 -73
- claude_mpm/hooks/session_resume_hook.py +22 -18
- claude_mpm/hooks/templates/pre_tool_use_template.py +10 -2
- claude_mpm/scripts/claude-hook-handler.sh +43 -16
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/agent_recommendation_service.py +8 -8
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
- claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
- claude_mpm/services/event_log.py +8 -0
- claude_mpm/services/pm_skills_deployer.py +84 -6
- claude_mpm/services/skills/git_skill_source_manager.py +130 -10
- claude_mpm/services/skills/selective_skill_deployer.py +28 -0
- claude_mpm/services/skills/skill_discovery_service.py +74 -4
- claude_mpm/services/skills_deployer.py +31 -5
- claude_mpm/skills/__init__.py +2 -1
- claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
- claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
- claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
- claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
- claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
- claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
- claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
- claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
- claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
- claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
- claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
- claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
- claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
- claude_mpm/skills/registry.py +295 -90
- {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.17.dist-info}/METADATA +22 -6
- {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.17.dist-info}/RECORD +213 -83
- {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.17.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.17.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.17.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.17.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.17.dist-info}/top_level.txt +0 -0
claude_mpm/cli/startup.py
CHANGED
|
@@ -161,7 +161,25 @@ def setup_early_environment(argv):
|
|
|
161
161
|
# CRITICAL: Suppress ALL logging by default
|
|
162
162
|
# This catches all loggers (claude_mpm.*, service.*, framework_loader, etc.)
|
|
163
163
|
# This will be overridden by setup_mcp_server_logging() based on user preference
|
|
164
|
-
logging.getLogger()
|
|
164
|
+
root_logger = logging.getLogger()
|
|
165
|
+
root_logger.setLevel(logging.CRITICAL + 1) # Root logger catches everything
|
|
166
|
+
root_logger.handlers = [] # Remove any handlers
|
|
167
|
+
|
|
168
|
+
# Also suppress common module loggers explicitly to prevent handler leakage
|
|
169
|
+
for logger_name in [
|
|
170
|
+
"claude_mpm",
|
|
171
|
+
"path_resolver",
|
|
172
|
+
"file_loader",
|
|
173
|
+
"framework_loader",
|
|
174
|
+
"service",
|
|
175
|
+
"instruction_loader",
|
|
176
|
+
"agent_loader",
|
|
177
|
+
"startup",
|
|
178
|
+
]:
|
|
179
|
+
module_logger = logging.getLogger(logger_name)
|
|
180
|
+
module_logger.setLevel(logging.CRITICAL + 1)
|
|
181
|
+
module_logger.handlers = []
|
|
182
|
+
module_logger.propagate = False
|
|
165
183
|
|
|
166
184
|
# Process argv
|
|
167
185
|
if argv is None:
|
|
@@ -192,7 +210,16 @@ def should_skip_background_services(args, processed_argv):
|
|
|
192
210
|
return any(cmd in (processed_argv or sys.argv[1:]) for cmd in skip_commands) or (
|
|
193
211
|
hasattr(args, "command")
|
|
194
212
|
and args.command
|
|
195
|
-
in [
|
|
213
|
+
in [
|
|
214
|
+
"info",
|
|
215
|
+
"doctor",
|
|
216
|
+
"config",
|
|
217
|
+
"mcp",
|
|
218
|
+
"configure",
|
|
219
|
+
"hook-errors",
|
|
220
|
+
"autotodos",
|
|
221
|
+
"commander",
|
|
222
|
+
]
|
|
196
223
|
)
|
|
197
224
|
|
|
198
225
|
|
|
@@ -316,7 +343,7 @@ def deploy_output_style_on_startup():
|
|
|
316
343
|
Deploys all styles:
|
|
317
344
|
- claude-mpm.md (professional mode)
|
|
318
345
|
- claude-mpm-teacher.md (teaching mode)
|
|
319
|
-
- claude-mpm-
|
|
346
|
+
- claude-mpm-research.md (research mode - for codebase analysis)
|
|
320
347
|
"""
|
|
321
348
|
try:
|
|
322
349
|
from ..core.output_style_manager import OutputStyleManager
|
|
@@ -451,6 +478,94 @@ def _cleanup_orphaned_agents(deploy_target: Path, deployed_agents: list[str]) ->
|
|
|
451
478
|
return removed_count
|
|
452
479
|
|
|
453
480
|
|
|
481
|
+
def _save_deployment_state_after_reconciliation(
|
|
482
|
+
agent_result, project_path: Path
|
|
483
|
+
) -> None:
|
|
484
|
+
"""Save deployment state after reconciliation to prevent duplicate deployment.
|
|
485
|
+
|
|
486
|
+
WHY: After perform_startup_reconciliation() deploys agents to .claude/agents/,
|
|
487
|
+
we need to save a deployment state file so that ClaudeRunner.setup_agents()
|
|
488
|
+
can detect agents are already deployed and skip redundant deployment.
|
|
489
|
+
|
|
490
|
+
This prevents the "✓ Deployed 31 native agents" duplicate deployment that
|
|
491
|
+
occurs when setup_agents() doesn't know reconciliation already ran.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
agent_result: DeploymentResult from perform_startup_reconciliation()
|
|
495
|
+
project_path: Project root directory
|
|
496
|
+
|
|
497
|
+
DESIGN DECISION: Use same state file format as ClaudeRunner._save_deployment_state()
|
|
498
|
+
Located at: .claude-mpm/cache/deployment_state.json
|
|
499
|
+
|
|
500
|
+
State file format:
|
|
501
|
+
{
|
|
502
|
+
"version": "5.6.13",
|
|
503
|
+
"agent_count": 15,
|
|
504
|
+
"deployment_hash": "sha256:...",
|
|
505
|
+
"deployed_at": 1234567890.123
|
|
506
|
+
}
|
|
507
|
+
"""
|
|
508
|
+
import hashlib
|
|
509
|
+
import json
|
|
510
|
+
import time
|
|
511
|
+
|
|
512
|
+
from ..core.logger import get_logger
|
|
513
|
+
|
|
514
|
+
logger = get_logger("cli")
|
|
515
|
+
|
|
516
|
+
try:
|
|
517
|
+
# Get version from package
|
|
518
|
+
from claude_mpm import __version__
|
|
519
|
+
|
|
520
|
+
# Path to state file (matches ClaudeRunner._get_deployment_state_path())
|
|
521
|
+
state_file = project_path / ".claude-mpm" / "cache" / "deployment_state.json"
|
|
522
|
+
agents_dir = project_path / ".claude" / "agents"
|
|
523
|
+
|
|
524
|
+
# Count deployed agents
|
|
525
|
+
if agents_dir.exists():
|
|
526
|
+
agent_count = len(list(agents_dir.glob("*.md")))
|
|
527
|
+
else:
|
|
528
|
+
agent_count = 0
|
|
529
|
+
|
|
530
|
+
# Calculate deployment hash (matches ClaudeRunner._calculate_deployment_hash())
|
|
531
|
+
# CRITICAL: Must match exact hash algorithm used in ClaudeRunner
|
|
532
|
+
# Hashes filename + file content (not mtime) for consistency
|
|
533
|
+
deployment_hash = ""
|
|
534
|
+
if agents_dir.exists():
|
|
535
|
+
agent_files = sorted(agents_dir.glob("*.md"))
|
|
536
|
+
hash_obj = hashlib.sha256()
|
|
537
|
+
for agent_file in agent_files:
|
|
538
|
+
# Include filename and content in hash (matches ClaudeRunner)
|
|
539
|
+
hash_obj.update(agent_file.name.encode())
|
|
540
|
+
try:
|
|
541
|
+
hash_obj.update(agent_file.read_bytes())
|
|
542
|
+
except Exception as e:
|
|
543
|
+
logger.debug(f"Error reading {agent_file} for hash: {e}")
|
|
544
|
+
|
|
545
|
+
deployment_hash = hash_obj.hexdigest()
|
|
546
|
+
|
|
547
|
+
# Create state data
|
|
548
|
+
state_data = {
|
|
549
|
+
"version": __version__,
|
|
550
|
+
"agent_count": agent_count,
|
|
551
|
+
"deployment_hash": deployment_hash,
|
|
552
|
+
"deployed_at": time.time(),
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
# Ensure directory exists
|
|
556
|
+
state_file.parent.mkdir(parents=True, exist_ok=True)
|
|
557
|
+
|
|
558
|
+
# Write state file
|
|
559
|
+
state_file.write_text(json.dumps(state_data, indent=2))
|
|
560
|
+
logger.debug(
|
|
561
|
+
f"Saved deployment state after reconciliation: {agent_count} agents"
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
except Exception as e:
|
|
565
|
+
# Non-critical error - log but don't fail startup
|
|
566
|
+
logger.debug(f"Failed to save deployment state: {e}")
|
|
567
|
+
|
|
568
|
+
|
|
454
569
|
def sync_remote_agents_on_startup(force_sync: bool = False):
|
|
455
570
|
"""
|
|
456
571
|
Synchronize agent templates from remote sources on startup.
|
|
@@ -613,6 +728,12 @@ def sync_remote_agents_on_startup(force_sync: bool = False):
|
|
|
613
728
|
)
|
|
614
729
|
print(" Run with --verbose for detailed error information.\n")
|
|
615
730
|
|
|
731
|
+
# Save deployment state to prevent duplicate deployment in ClaudeRunner
|
|
732
|
+
# This ensures setup_agents() skips deployment since we already reconciled
|
|
733
|
+
_save_deployment_state_after_reconciliation(
|
|
734
|
+
agent_result=agent_result, project_path=project_path
|
|
735
|
+
)
|
|
736
|
+
|
|
616
737
|
except Exception as e:
|
|
617
738
|
# Deployment failure shouldn't block startup
|
|
618
739
|
from ..core.logger import get_logger
|
|
@@ -540,7 +540,8 @@ def should_show_banner(args) -> bool:
|
|
|
540
540
|
return False
|
|
541
541
|
|
|
542
542
|
# Check for commands that should skip banner
|
|
543
|
-
|
|
543
|
+
# Commander has its own banner, so skip the main MPM banner
|
|
544
|
+
skip_commands = {"info", "doctor", "config", "configure", "commander"}
|
|
544
545
|
if hasattr(args, "command") and args.command in skip_commands:
|
|
545
546
|
return False
|
|
546
547
|
|
claude_mpm/cli/utils.py
CHANGED
|
@@ -107,7 +107,7 @@ def get_agent_versions_display() -> Optional[str]:
|
|
|
107
107
|
base_version_tuple
|
|
108
108
|
)
|
|
109
109
|
output_lines.append(f"\n Base Agent Version: {base_version_str}")
|
|
110
|
-
except Exception:
|
|
110
|
+
except Exception: # nosec B110 - intentional: version display is optional
|
|
111
111
|
pass
|
|
112
112
|
|
|
113
113
|
# Check for agents needing migration
|
|
@@ -173,8 +173,12 @@ def setup_logging(args) -> object:
|
|
|
173
173
|
if not hasattr(args, "logging") or args.logging is None:
|
|
174
174
|
args.logging = LogLevel.OFF.value
|
|
175
175
|
|
|
176
|
+
# Handle deprecated --verbose flag
|
|
177
|
+
if hasattr(args, "verbose") and args.verbose and args.logging == LogLevel.OFF.value:
|
|
178
|
+
args.logging = LogLevel.INFO.value
|
|
179
|
+
|
|
176
180
|
# Handle deprecated --debug flag
|
|
177
|
-
if hasattr(args, "debug") and args.debug
|
|
181
|
+
if hasattr(args, "debug") and args.debug:
|
|
178
182
|
args.logging = LogLevel.DEBUG.value
|
|
179
183
|
|
|
180
184
|
# Only setup logging if not OFF
|
|
@@ -204,7 +208,7 @@ def ensure_directories() -> None:
|
|
|
204
208
|
from ..init import ensure_directories as init_ensure_directories
|
|
205
209
|
|
|
206
210
|
init_ensure_directories()
|
|
207
|
-
except Exception:
|
|
211
|
+
except Exception: # nosec B110
|
|
208
212
|
# Continue even if initialization fails
|
|
209
213
|
# The individual commands will handle missing directories as needed
|
|
210
214
|
pass
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""MPM Commander - Multi-Project Orchestration.
|
|
2
|
+
|
|
3
|
+
This module provides the core infrastructure for managing multiple projects
|
|
4
|
+
with isolated state, work queues, and tool sessions.
|
|
5
|
+
|
|
6
|
+
Key Components:
|
|
7
|
+
- ProjectRegistry: Thread-safe project management
|
|
8
|
+
- Project models: Data structures for state and sessions
|
|
9
|
+
- TmuxOrchestrator: Tmux session and pane management
|
|
10
|
+
- Config loading: .claude-mpm/ directory configuration
|
|
11
|
+
- CommanderDaemon: Main daemon process for orchestration
|
|
12
|
+
- ProjectSession: Per-project lifecycle management
|
|
13
|
+
- InstanceManager: Framework selection and instance lifecycle
|
|
14
|
+
- Frameworks: Claude Code, MPM framework abstractions
|
|
15
|
+
- Memory: Conversation storage, semantic search, context compression
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
>>> from claude_mpm.commander import ProjectRegistry
|
|
19
|
+
>>> registry = ProjectRegistry()
|
|
20
|
+
>>> project = registry.register("/path/to/project")
|
|
21
|
+
>>> registry.update_state(project.id, ProjectState.WORKING)
|
|
22
|
+
|
|
23
|
+
>>> # Memory integration
|
|
24
|
+
>>> from claude_mpm.commander.memory import MemoryIntegration
|
|
25
|
+
>>> memory = MemoryIntegration.create()
|
|
26
|
+
>>> await memory.capture_project_conversation(project)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from claude_mpm.commander.config import DaemonConfig
|
|
30
|
+
from claude_mpm.commander.config_loader import load_project_config
|
|
31
|
+
from claude_mpm.commander.daemon import CommanderDaemon
|
|
32
|
+
from claude_mpm.commander.frameworks import (
|
|
33
|
+
BaseFramework,
|
|
34
|
+
ClaudeCodeFramework,
|
|
35
|
+
InstanceInfo,
|
|
36
|
+
MPMFramework,
|
|
37
|
+
)
|
|
38
|
+
from claude_mpm.commander.instance_manager import (
|
|
39
|
+
FrameworkNotFoundError,
|
|
40
|
+
InstanceAlreadyExistsError,
|
|
41
|
+
InstanceManager,
|
|
42
|
+
InstanceNotFoundError,
|
|
43
|
+
)
|
|
44
|
+
from claude_mpm.commander.models import (
|
|
45
|
+
Project,
|
|
46
|
+
ProjectState,
|
|
47
|
+
ThreadMessage,
|
|
48
|
+
ToolSession,
|
|
49
|
+
)
|
|
50
|
+
from claude_mpm.commander.project_session import ProjectSession, SessionState
|
|
51
|
+
from claude_mpm.commander.registry import ProjectRegistry
|
|
52
|
+
from claude_mpm.commander.tmux_orchestrator import (
|
|
53
|
+
TmuxNotFoundError,
|
|
54
|
+
TmuxOrchestrator,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
"BaseFramework",
|
|
59
|
+
"ClaudeCodeFramework",
|
|
60
|
+
"CommanderDaemon",
|
|
61
|
+
"DaemonConfig",
|
|
62
|
+
"FrameworkNotFoundError",
|
|
63
|
+
"InstanceAlreadyExistsError",
|
|
64
|
+
"InstanceInfo",
|
|
65
|
+
"InstanceManager",
|
|
66
|
+
"InstanceNotFoundError",
|
|
67
|
+
"MPMFramework",
|
|
68
|
+
"Project",
|
|
69
|
+
"ProjectRegistry",
|
|
70
|
+
"ProjectSession",
|
|
71
|
+
"ProjectState",
|
|
72
|
+
"SessionState",
|
|
73
|
+
"ThreadMessage",
|
|
74
|
+
"TmuxNotFoundError",
|
|
75
|
+
"TmuxOrchestrator",
|
|
76
|
+
"ToolSession",
|
|
77
|
+
"load_project_config",
|
|
78
|
+
]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Runtime adapters for MPM Commander.
|
|
2
|
+
|
|
3
|
+
This package provides adapters for different AI coding tools, allowing
|
|
4
|
+
the TmuxOrchestrator to interface with various runtimes in a uniform way.
|
|
5
|
+
|
|
6
|
+
Two types of adapters:
|
|
7
|
+
- RuntimeAdapter: Synchronous parsing and state detection
|
|
8
|
+
- CommunicationAdapter: Async I/O and state management
|
|
9
|
+
|
|
10
|
+
Available Runtime Adapters:
|
|
11
|
+
- ClaudeCodeAdapter: Vanilla Claude Code CLI
|
|
12
|
+
- AuggieAdapter: Auggie with MCP support
|
|
13
|
+
- CodexAdapter: Codex (limited features)
|
|
14
|
+
- MPMAdapter: Full MPM with agents, hooks, skills, monitoring
|
|
15
|
+
|
|
16
|
+
Registry:
|
|
17
|
+
- AdapterRegistry: Centralized adapter management with auto-detection
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .auggie import AuggieAdapter
|
|
21
|
+
from .base import (
|
|
22
|
+
Capability,
|
|
23
|
+
ParsedResponse,
|
|
24
|
+
RuntimeAdapter,
|
|
25
|
+
RuntimeCapability,
|
|
26
|
+
RuntimeInfo,
|
|
27
|
+
)
|
|
28
|
+
from .claude_code import ClaudeCodeAdapter
|
|
29
|
+
from .codex import CodexAdapter
|
|
30
|
+
from .communication import (
|
|
31
|
+
AdapterResponse,
|
|
32
|
+
AdapterState,
|
|
33
|
+
BaseCommunicationAdapter,
|
|
34
|
+
ClaudeCodeCommunicationAdapter,
|
|
35
|
+
)
|
|
36
|
+
from .mpm import MPMAdapter
|
|
37
|
+
from .registry import AdapterRegistry
|
|
38
|
+
|
|
39
|
+
# Auto-register all adapters
|
|
40
|
+
AdapterRegistry.register("claude-code", ClaudeCodeAdapter)
|
|
41
|
+
AdapterRegistry.register("auggie", AuggieAdapter)
|
|
42
|
+
AdapterRegistry.register("codex", CodexAdapter)
|
|
43
|
+
AdapterRegistry.register("mpm", MPMAdapter)
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"AdapterRegistry",
|
|
47
|
+
"AdapterResponse",
|
|
48
|
+
"AdapterState",
|
|
49
|
+
"AuggieAdapter",
|
|
50
|
+
"BaseCommunicationAdapter",
|
|
51
|
+
"Capability",
|
|
52
|
+
"ClaudeCodeAdapter",
|
|
53
|
+
"ClaudeCodeCommunicationAdapter",
|
|
54
|
+
"CodexAdapter",
|
|
55
|
+
"MPMAdapter",
|
|
56
|
+
"ParsedResponse",
|
|
57
|
+
"RuntimeAdapter",
|
|
58
|
+
"RuntimeCapability",
|
|
59
|
+
"RuntimeInfo",
|
|
60
|
+
]
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""Auggie CLI runtime adapter.
|
|
2
|
+
|
|
3
|
+
This module implements the RuntimeAdapter interface for Auggie,
|
|
4
|
+
an AI coding assistant with MCP (Model Context Protocol) support.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import re
|
|
9
|
+
import shlex
|
|
10
|
+
from typing import List, Optional, Set
|
|
11
|
+
|
|
12
|
+
from .base import (
|
|
13
|
+
Capability,
|
|
14
|
+
ParsedResponse,
|
|
15
|
+
RuntimeAdapter,
|
|
16
|
+
RuntimeCapability,
|
|
17
|
+
RuntimeInfo,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AuggieAdapter(RuntimeAdapter):
|
|
24
|
+
"""Adapter for Auggie CLI.
|
|
25
|
+
|
|
26
|
+
Auggie is an AI coding assistant with support for MCP servers,
|
|
27
|
+
custom instructions, and various tool capabilities.
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
>>> adapter = AuggieAdapter()
|
|
31
|
+
>>> cmd = adapter.build_launch_command("/home/user/project")
|
|
32
|
+
>>> print(cmd)
|
|
33
|
+
cd '/home/user/project' && auggie
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# Idle detection patterns
|
|
37
|
+
IDLE_PATTERNS = [
|
|
38
|
+
r"^>\s*$", # Simple prompt
|
|
39
|
+
r"auggie>\s*$", # Named prompt
|
|
40
|
+
r"Ready for input",
|
|
41
|
+
r"What would you like",
|
|
42
|
+
r"How can I assist",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
# Error patterns
|
|
46
|
+
ERROR_PATTERNS = [
|
|
47
|
+
r"Error:",
|
|
48
|
+
r"Failed:",
|
|
49
|
+
r"Exception:",
|
|
50
|
+
r"Permission denied",
|
|
51
|
+
r"not found",
|
|
52
|
+
r"Traceback \(most recent call last\)",
|
|
53
|
+
r"FATAL:",
|
|
54
|
+
r"command not found",
|
|
55
|
+
r"cannot access",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
# Question patterns
|
|
59
|
+
QUESTION_PATTERNS = [
|
|
60
|
+
r"Which option",
|
|
61
|
+
r"Should I proceed",
|
|
62
|
+
r"Please choose",
|
|
63
|
+
r"\(y/n\)\?",
|
|
64
|
+
r"Are you sure",
|
|
65
|
+
r"Do you want",
|
|
66
|
+
r"\[Y/n\]",
|
|
67
|
+
r"\[yes/no\]",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
# ANSI escape code pattern
|
|
71
|
+
ANSI_ESCAPE = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def name(self) -> str:
|
|
75
|
+
"""Return the runtime identifier."""
|
|
76
|
+
return "auggie"
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def capabilities(self) -> Set[Capability]:
|
|
80
|
+
"""Return the set of capabilities supported by Auggie."""
|
|
81
|
+
return {
|
|
82
|
+
Capability.TOOL_USE,
|
|
83
|
+
Capability.FILE_EDIT,
|
|
84
|
+
Capability.FILE_CREATE,
|
|
85
|
+
Capability.GIT_OPERATIONS,
|
|
86
|
+
Capability.SHELL_COMMANDS,
|
|
87
|
+
Capability.COMPLEX_REASONING,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def runtime_info(self) -> RuntimeInfo:
|
|
92
|
+
"""Return detailed runtime information."""
|
|
93
|
+
return RuntimeInfo(
|
|
94
|
+
name="auggie",
|
|
95
|
+
version=None, # Version detection could be added
|
|
96
|
+
capabilities={
|
|
97
|
+
RuntimeCapability.FILE_READ,
|
|
98
|
+
RuntimeCapability.FILE_EDIT,
|
|
99
|
+
RuntimeCapability.FILE_CREATE,
|
|
100
|
+
RuntimeCapability.BASH_EXECUTION,
|
|
101
|
+
RuntimeCapability.GIT_OPERATIONS,
|
|
102
|
+
RuntimeCapability.TOOL_USE,
|
|
103
|
+
RuntimeCapability.MCP_TOOLS, # Auggie supports MCP
|
|
104
|
+
RuntimeCapability.INSTRUCTIONS,
|
|
105
|
+
RuntimeCapability.COMPLEX_REASONING,
|
|
106
|
+
RuntimeCapability.AGENT_DELEGATION, # Auggie now supports agents
|
|
107
|
+
},
|
|
108
|
+
command="auggie",
|
|
109
|
+
supports_agents=True, # Auggie now supports agent delegation
|
|
110
|
+
instruction_file=".augment/instructions.md",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
def build_launch_command(
|
|
114
|
+
self, project_path: str, agent_prompt: Optional[str] = None
|
|
115
|
+
) -> str:
|
|
116
|
+
"""Generate shell command to start Auggie.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
project_path: Absolute path to the project directory
|
|
120
|
+
agent_prompt: Optional system prompt to configure Auggie
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Shell command string ready to execute
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
>>> adapter = AuggieAdapter()
|
|
127
|
+
>>> adapter.build_launch_command("/home/user/project")
|
|
128
|
+
"cd '/home/user/project' && auggie"
|
|
129
|
+
"""
|
|
130
|
+
quoted_path = shlex.quote(project_path)
|
|
131
|
+
cmd = f"cd {quoted_path} && auggie"
|
|
132
|
+
|
|
133
|
+
if agent_prompt:
|
|
134
|
+
# Auggie may support --prompt or similar flag
|
|
135
|
+
# Adjust based on actual Auggie CLI options
|
|
136
|
+
quoted_prompt = shlex.quote(agent_prompt)
|
|
137
|
+
cmd += f" --prompt {quoted_prompt}"
|
|
138
|
+
|
|
139
|
+
logger.debug(f"Built Auggie launch command: {cmd}")
|
|
140
|
+
return cmd
|
|
141
|
+
|
|
142
|
+
def format_input(self, message: str) -> str:
|
|
143
|
+
"""Prepare message for Auggie's input format."""
|
|
144
|
+
formatted = message.strip()
|
|
145
|
+
logger.debug(f"Formatted input: {formatted[:100]}...")
|
|
146
|
+
return formatted
|
|
147
|
+
|
|
148
|
+
def strip_ansi(self, text: str) -> str:
|
|
149
|
+
"""Remove ANSI escape codes from text."""
|
|
150
|
+
return self.ANSI_ESCAPE.sub("", text)
|
|
151
|
+
|
|
152
|
+
def detect_idle(self, output: str) -> bool:
|
|
153
|
+
"""Recognize when Auggie is waiting for input."""
|
|
154
|
+
if not output:
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
clean = self.strip_ansi(output)
|
|
158
|
+
lines = clean.strip().split("\n")
|
|
159
|
+
|
|
160
|
+
if not lines:
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
last_line = lines[-1].strip()
|
|
164
|
+
|
|
165
|
+
for pattern in self.IDLE_PATTERNS:
|
|
166
|
+
if re.search(pattern, last_line):
|
|
167
|
+
logger.debug(f"Detected idle state with pattern: {pattern}")
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
def detect_error(self, output: str) -> Optional[str]:
|
|
173
|
+
"""Recognize error states and extract error message."""
|
|
174
|
+
clean = self.strip_ansi(output)
|
|
175
|
+
|
|
176
|
+
for pattern in self.ERROR_PATTERNS:
|
|
177
|
+
match = re.search(pattern, clean, re.IGNORECASE)
|
|
178
|
+
if match:
|
|
179
|
+
for line in clean.split("\n"):
|
|
180
|
+
if re.search(pattern, line, re.IGNORECASE):
|
|
181
|
+
error_msg = line.strip()
|
|
182
|
+
logger.warning(f"Detected error: {error_msg}")
|
|
183
|
+
return error_msg
|
|
184
|
+
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
def detect_question(
|
|
188
|
+
self, output: str
|
|
189
|
+
) -> tuple[bool, Optional[str], Optional[List[str]]]:
|
|
190
|
+
"""Detect if Auggie is asking a question."""
|
|
191
|
+
clean = self.strip_ansi(output)
|
|
192
|
+
|
|
193
|
+
for pattern in self.QUESTION_PATTERNS:
|
|
194
|
+
if re.search(pattern, clean, re.IGNORECASE):
|
|
195
|
+
lines = clean.strip().split("\n")
|
|
196
|
+
question = None
|
|
197
|
+
options = []
|
|
198
|
+
|
|
199
|
+
for line in lines:
|
|
200
|
+
if re.search(pattern, line, re.IGNORECASE):
|
|
201
|
+
question = line.strip()
|
|
202
|
+
|
|
203
|
+
# Look for numbered options
|
|
204
|
+
opt_match = re.match(r"^\s*(\d+)[.):]\s*(.+)$", line)
|
|
205
|
+
if opt_match:
|
|
206
|
+
options.append(opt_match.group(2).strip())
|
|
207
|
+
|
|
208
|
+
logger.debug(
|
|
209
|
+
f"Detected question: {question}, options: {options if options else 'none'}"
|
|
210
|
+
)
|
|
211
|
+
return True, question, options if options else None
|
|
212
|
+
|
|
213
|
+
return False, None, None
|
|
214
|
+
|
|
215
|
+
def parse_response(self, output: str) -> ParsedResponse:
|
|
216
|
+
"""Extract meaningful content from Auggie output."""
|
|
217
|
+
if not output:
|
|
218
|
+
return ParsedResponse(
|
|
219
|
+
content="",
|
|
220
|
+
is_complete=False,
|
|
221
|
+
is_error=False,
|
|
222
|
+
is_question=False,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
clean = self.strip_ansi(output)
|
|
226
|
+
error_msg = self.detect_error(output)
|
|
227
|
+
is_question, question_text, options = self.detect_question(output)
|
|
228
|
+
is_complete = self.detect_idle(output)
|
|
229
|
+
|
|
230
|
+
response = ParsedResponse(
|
|
231
|
+
content=clean,
|
|
232
|
+
is_complete=is_complete,
|
|
233
|
+
is_error=error_msg is not None,
|
|
234
|
+
error_message=error_msg,
|
|
235
|
+
is_question=is_question,
|
|
236
|
+
question_text=question_text,
|
|
237
|
+
options=options,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
logger.debug(
|
|
241
|
+
f"Parsed response: complete={is_complete}, error={error_msg is not None}, "
|
|
242
|
+
f"question={is_question}"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
return response
|
|
246
|
+
|
|
247
|
+
def inject_instructions(self, instructions: str) -> Optional[str]:
|
|
248
|
+
"""Return command to inject custom instructions.
|
|
249
|
+
|
|
250
|
+
Auggie supports .augment/instructions.md file for custom instructions.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
instructions: Instructions text to inject
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Command to write instructions file
|
|
257
|
+
"""
|
|
258
|
+
# Write to .augment/instructions.md
|
|
259
|
+
escaped = instructions.replace("'", "'\\''")
|
|
260
|
+
return f"mkdir -p .augment && echo '{escaped}' > .augment/instructions.md"
|