claude-mpm 5.6.13__py3-none-any.whl → 5.6.15__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 (50) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/commands/commander.py +173 -3
  3. claude_mpm/cli/parsers/commander_parser.py +41 -8
  4. claude_mpm/cli/startup.py +10 -1
  5. claude_mpm/cli/startup_display.py +2 -1
  6. claude_mpm/commander/__init__.py +6 -0
  7. claude_mpm/commander/adapters/__init__.py +32 -3
  8. claude_mpm/commander/adapters/auggie.py +260 -0
  9. claude_mpm/commander/adapters/base.py +98 -1
  10. claude_mpm/commander/adapters/claude_code.py +32 -1
  11. claude_mpm/commander/adapters/codex.py +237 -0
  12. claude_mpm/commander/adapters/example_usage.py +310 -0
  13. claude_mpm/commander/adapters/mpm.py +389 -0
  14. claude_mpm/commander/adapters/registry.py +204 -0
  15. claude_mpm/commander/api/app.py +32 -16
  16. claude_mpm/commander/api/routes/messages.py +11 -11
  17. claude_mpm/commander/api/routes/projects.py +20 -20
  18. claude_mpm/commander/api/routes/sessions.py +19 -21
  19. claude_mpm/commander/api/routes/work.py +86 -50
  20. claude_mpm/commander/api/schemas.py +4 -0
  21. claude_mpm/commander/chat/cli.py +4 -0
  22. claude_mpm/commander/daemon.py +139 -9
  23. claude_mpm/commander/env_loader.py +59 -0
  24. claude_mpm/commander/memory/__init__.py +45 -0
  25. claude_mpm/commander/memory/compression.py +347 -0
  26. claude_mpm/commander/memory/embeddings.py +230 -0
  27. claude_mpm/commander/memory/entities.py +310 -0
  28. claude_mpm/commander/memory/example_usage.py +290 -0
  29. claude_mpm/commander/memory/integration.py +325 -0
  30. claude_mpm/commander/memory/search.py +381 -0
  31. claude_mpm/commander/memory/store.py +657 -0
  32. claude_mpm/commander/registry.py +10 -4
  33. claude_mpm/commander/work/executor.py +22 -12
  34. claude_mpm/core/claude_runner.py +143 -0
  35. claude_mpm/core/output_style_manager.py +34 -7
  36. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +0 -0
  37. claude_mpm/hooks/claude_hooks/event_handlers.py +0 -0
  38. claude_mpm/hooks/claude_hooks/hook_handler.py +0 -0
  39. claude_mpm/hooks/claude_hooks/memory_integration.py +0 -0
  40. claude_mpm/hooks/claude_hooks/response_tracking.py +0 -0
  41. claude_mpm/hooks/templates/pre_tool_use_template.py +0 -0
  42. claude_mpm/scripts/start_activity_logging.py +0 -0
  43. claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
  44. {claude_mpm-5.6.13.dist-info → claude_mpm-5.6.15.dist-info}/METADATA +2 -2
  45. {claude_mpm-5.6.13.dist-info → claude_mpm-5.6.15.dist-info}/RECORD +43 -28
  46. {claude_mpm-5.6.13.dist-info → claude_mpm-5.6.15.dist-info}/WHEEL +0 -0
  47. {claude_mpm-5.6.13.dist-info → claude_mpm-5.6.15.dist-info}/entry_points.txt +0 -0
  48. {claude_mpm-5.6.13.dist-info → claude_mpm-5.6.15.dist-info}/licenses/LICENSE +0 -0
  49. {claude_mpm-5.6.13.dist-info → claude_mpm-5.6.15.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  50. {claude_mpm-5.6.13.dist-info → claude_mpm-5.6.15.dist-info}/top_level.txt +0 -0
@@ -51,16 +51,19 @@ class WorkExecutor:
51
51
 
52
52
  logger.debug(f"Initialized WorkExecutor for project {queue.project_id}")
53
53
 
54
- async def execute_next(self) -> bool:
54
+ async def execute_next(self, pane_target: Optional[str] = None) -> bool:
55
55
  """Execute next available work item.
56
56
 
57
57
  Gets next work from queue, starts it, and executes via RuntimeExecutor.
58
58
 
59
+ Args:
60
+ pane_target: Optional tmux pane target for execution
61
+
59
62
  Returns:
60
63
  True if work was executed, False if queue empty/blocked
61
64
 
62
65
  Example:
63
- >>> executed = await executor.execute_next()
66
+ >>> executed = await executor.execute_next("%5")
64
67
  >>> if not executed:
65
68
  ... print("No work available")
66
69
  """
@@ -71,10 +74,12 @@ class WorkExecutor:
71
74
  return False
72
75
 
73
76
  # Execute the work item
74
- await self.execute(work_item)
77
+ await self.execute(work_item, pane_target)
75
78
  return True
76
79
 
77
- async def execute(self, work_item: WorkItem) -> None:
80
+ async def execute(
81
+ self, work_item: WorkItem, pane_target: Optional[str] = None
82
+ ) -> None:
78
83
  """Execute a specific work item.
79
84
 
80
85
  Marks work as IN_PROGRESS and sends to RuntimeExecutor.
@@ -83,12 +88,13 @@ class WorkExecutor:
83
88
 
84
89
  Args:
85
90
  work_item: WorkItem to execute
91
+ pane_target: Optional tmux pane target for execution
86
92
 
87
93
  Raises:
88
94
  RuntimeError: If execution fails
89
95
 
90
96
  Example:
91
- >>> await executor.execute(work_item)
97
+ >>> await executor.execute(work_item, "%5")
92
98
  """
93
99
  # Mark as in progress
94
100
  if not self.queue.start(work_item.id):
@@ -103,17 +109,21 @@ class WorkExecutor:
103
109
  )
104
110
 
105
111
  try:
106
- # Send work content to runtime
107
- # Note: In actual implementation, this would integrate with
108
- # ProjectSession which manages the pane_target
109
- # For now, we assume runtime has active session
110
- # This will be properly integrated when wiring with ProjectSession
112
+ # Send work content to runtime if pane target provided
113
+ if pane_target:
114
+ await self.runtime.send_message(pane_target, work_item.content)
115
+ logger.info(
116
+ f"Work item {work_item.id} sent to pane {pane_target} for execution"
117
+ )
118
+ else:
119
+ logger.warning(
120
+ f"No pane target provided for work item {work_item.id}, "
121
+ f"work marked as in-progress but not sent to runtime"
122
+ )
111
123
 
112
124
  # Store work item ID in metadata for callback tracking
113
125
  work_item.metadata["execution_started"] = True
114
126
 
115
- logger.info(f"Work item {work_item.id} sent to runtime for execution")
116
-
117
127
  except Exception as e:
118
128
  logger.error(f"Failed to execute work item {work_item.id}: {e}")
119
129
  await self.handle_failure(work_item.id, str(e))
@@ -1,5 +1,7 @@
1
1
  """Claude runner with both exec and subprocess launch methods."""
2
2
 
3
+ import hashlib
4
+ import json
3
5
  import os
4
6
  from pathlib import Path
5
7
  from typing import Optional
@@ -211,9 +213,137 @@ class ClaudeRunner:
211
213
  }
212
214
  )
213
215
 
216
+ def _get_deployment_state_path(self) -> Path:
217
+ """Get path to deployment state file."""
218
+ return Path.cwd() / ".claude" / "agents" / ".deployment-state.json"
219
+
220
+ def _calculate_deployment_hash(self, agents_dir: Path) -> str:
221
+ """Calculate hash of all agent files for change detection.
222
+
223
+ Args:
224
+ agents_dir: Directory containing agent .md files
225
+
226
+ Returns:
227
+ SHA256 hash of agent file contents
228
+ """
229
+ if not agents_dir.exists():
230
+ return ""
231
+
232
+ # Get all .md files sorted for consistent hashing
233
+ agent_files = sorted(agents_dir.glob("*.md"))
234
+
235
+ hash_obj = hashlib.sha256()
236
+ for agent_file in agent_files:
237
+ # Include filename and content in hash
238
+ hash_obj.update(agent_file.name.encode())
239
+ try:
240
+ hash_obj.update(agent_file.read_bytes())
241
+ except Exception as e:
242
+ self.logger.debug(f"Error reading {agent_file} for hash: {e}")
243
+
244
+ return hash_obj.hexdigest()
245
+
246
+ def _check_deployment_state(self) -> bool:
247
+ """Check if agents are already deployed and up-to-date.
248
+
249
+ Returns:
250
+ True if agents are already deployed and match current version, False otherwise
251
+ """
252
+ state_file = self._get_deployment_state_path()
253
+ agents_dir = Path.cwd() / ".claude" / "agents"
254
+
255
+ # If state file doesn't exist, need to deploy
256
+ if not state_file.exists():
257
+ return False
258
+
259
+ # If agents directory doesn't exist, need to deploy
260
+ if not agents_dir.exists():
261
+ return False
262
+
263
+ try:
264
+ # Load deployment state
265
+ state_data = json.loads(state_file.read_text())
266
+
267
+ # Get current version from package
268
+ from claude_mpm import __version__
269
+
270
+ # Check if version matches
271
+ if state_data.get("version") != __version__:
272
+ self.logger.debug(
273
+ f"Version mismatch: {state_data.get('version')} != {__version__}"
274
+ )
275
+ return False
276
+
277
+ # Check if agent count and hash match
278
+ current_hash = self._calculate_deployment_hash(agents_dir)
279
+ stored_hash = state_data.get("deployment_hash", "")
280
+
281
+ if current_hash != stored_hash:
282
+ self.logger.debug("Agent deployment hash mismatch")
283
+ return False
284
+
285
+ # All checks passed - agents are already deployed
286
+ agent_count = state_data.get("agent_count", 0)
287
+ self.logger.debug(
288
+ f"Agents already deployed: {agent_count} agents (v{__version__})"
289
+ )
290
+ return True
291
+
292
+ except Exception as e:
293
+ self.logger.debug(f"Error checking deployment state: {e}")
294
+ return False
295
+
296
+ def _save_deployment_state(self, agent_count: int) -> None:
297
+ """Save current deployment state.
298
+
299
+ Args:
300
+ agent_count: Number of agents deployed
301
+ """
302
+ state_file = self._get_deployment_state_path()
303
+ agents_dir = Path.cwd() / ".claude" / "agents"
304
+
305
+ try:
306
+ import time
307
+
308
+ from claude_mpm import __version__
309
+
310
+ # Calculate deployment hash
311
+ deployment_hash = self._calculate_deployment_hash(agents_dir)
312
+
313
+ # Create state data
314
+ state_data = {
315
+ "version": __version__,
316
+ "agent_count": agent_count,
317
+ "deployment_hash": deployment_hash,
318
+ "deployed_at": time.time(),
319
+ }
320
+
321
+ # Ensure directory exists
322
+ state_file.parent.mkdir(parents=True, exist_ok=True)
323
+
324
+ # Write state file
325
+ state_file.write_text(json.dumps(state_data, indent=2))
326
+ self.logger.debug(f"Saved deployment state: {agent_count} agents")
327
+
328
+ except Exception as e:
329
+ self.logger.debug(f"Error saving deployment state: {e}")
330
+
214
331
  def setup_agents(self) -> bool:
215
332
  """Deploy native agents to .claude/agents/."""
216
333
  try:
334
+ # Check if agents are already deployed and up-to-date
335
+ if self._check_deployment_state():
336
+ agents_dir = Path.cwd() / ".claude" / "agents"
337
+ agent_count = len(list(agents_dir.glob("*.md")))
338
+ print(f"✓ Agents: {agent_count} cached")
339
+ if self.project_logger:
340
+ self.project_logger.log_system(
341
+ f"Agents already deployed: {agent_count} cached",
342
+ level="INFO",
343
+ component="deployment",
344
+ )
345
+ return True
346
+
217
347
  if self.project_logger:
218
348
  self.project_logger.log_system(
219
349
  "Starting agent deployment", level="INFO", component="deployment"
@@ -239,6 +369,12 @@ class ClaudeRunner:
239
369
 
240
370
  # Set Claude environment
241
371
  self.deployment_service.set_claude_environment()
372
+
373
+ # Save deployment state for future runs
374
+ agents_dir = Path.cwd() / ".claude" / "agents"
375
+ total_agents = len(list(agents_dir.glob("*.md")))
376
+ self._save_deployment_state(total_agents)
377
+
242
378
  return True
243
379
  self.logger.info("All agents already up to date")
244
380
  if self.project_logger:
@@ -247,6 +383,13 @@ class ClaudeRunner:
247
383
  level="INFO",
248
384
  component="deployment",
249
385
  )
386
+
387
+ # Save deployment state even if no changes
388
+ agents_dir = Path.cwd() / ".claude" / "agents"
389
+ if agents_dir.exists():
390
+ total_agents = len(list(agents_dir.glob("*.md")))
391
+ self._save_deployment_state(total_agents)
392
+
250
393
  return True
251
394
 
252
395
  except PermissionError as e:
@@ -297,6 +297,9 @@ class OutputStyleManager:
297
297
  target_path = style_config["target"]
298
298
  style_name = style_config["name"]
299
299
 
300
+ # Check if this is a fresh install (file doesn't exist yet)
301
+ is_fresh_install = not target_path.exists()
302
+
300
303
  # If content not provided, read from source
301
304
  if content is None:
302
305
  content = self.extract_output_style_content(style=style)
@@ -310,7 +313,9 @@ class OutputStyleManager:
310
313
 
311
314
  # Activate the style if requested
312
315
  if activate:
313
- self._activate_output_style(style_name)
316
+ self._activate_output_style(
317
+ style_name, is_fresh_install=is_fresh_install
318
+ )
314
319
 
315
320
  return True
316
321
 
@@ -318,12 +323,21 @@ class OutputStyleManager:
318
323
  self.logger.error(f"Failed to deploy {style} style: {e}")
319
324
  return False
320
325
 
321
- def _activate_output_style(self, style_name: str = "Claude MPM") -> bool:
326
+ def _activate_output_style(
327
+ self, style_name: str = "Claude MPM", is_fresh_install: bool = False
328
+ ) -> bool:
322
329
  """
323
330
  Update Claude Code settings to activate a specific output style.
324
331
 
332
+ Only activates the style if:
333
+ 1. No active style is currently set (first deployment), OR
334
+ 2. This is a fresh install (style file didn't exist before deployment)
335
+
336
+ This preserves user preferences if they've manually changed their active style.
337
+
325
338
  Args:
326
339
  style_name: Name of the style to activate (e.g., "Claude MPM", "Claude MPM Teacher")
340
+ is_fresh_install: Whether this is a fresh install (style file didn't exist before)
327
341
 
328
342
  Returns:
329
343
  True if activated successfully, False otherwise
@@ -342,8 +356,12 @@ class OutputStyleManager:
342
356
  # Check current active style
343
357
  current_style = settings.get("activeOutputStyle")
344
358
 
345
- # Update active output style if different
346
- if current_style != style_name:
359
+ # Only set activeOutputStyle if:
360
+ # 1. No active style is set (first deployment), OR
361
+ # 2. This is a fresh install (file didn't exist before deployment)
362
+ should_activate = current_style is None or is_fresh_install
363
+
364
+ if should_activate and current_style != style_name:
347
365
  settings["activeOutputStyle"] = style_name
348
366
 
349
367
  # Ensure settings directory exists
@@ -358,7 +376,10 @@ class OutputStyleManager:
358
376
  f"✅ Activated {style_name} output style (was: {current_style or 'none'})"
359
377
  )
360
378
  else:
361
- self.logger.debug(f"{style_name} output style already active")
379
+ self.logger.debug(
380
+ f"Preserving user preference: {current_style or 'none'} "
381
+ f"(skipping activation of {style_name})"
382
+ )
362
383
 
363
384
  return True
364
385
 
@@ -452,6 +473,10 @@ class OutputStyleManager:
452
473
  """
453
474
  results: Dict[str, bool] = {}
454
475
 
476
+ # Check if professional style exists BEFORE deployment
477
+ # This determines if this is a fresh install
478
+ professional_style_existed = self.styles["professional"]["target"].exists()
479
+
455
480
  for style_type_key in self.styles:
456
481
  # Deploy without activation
457
482
  # Cast is safe because we know self.styles keys are OutputStyleType
@@ -459,9 +484,11 @@ class OutputStyleManager:
459
484
  success = self.deploy_output_style(style=style_type, activate=False)
460
485
  results[style_type] = success
461
486
 
462
- # Activate the default style if requested
487
+ # Activate the default style if requested AND this is first deployment
463
488
  if activate_default and results.get("professional", False):
464
- self._activate_output_style("Claude MPM")
489
+ self._activate_output_style(
490
+ "Claude MPM", is_fresh_install=not professional_style_existed
491
+ )
465
492
 
466
493
  return results
467
494
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,170 @@
1
+ ---
2
+ name: mpm-session-pause
3
+ description: Pause session and save current work state for later resume
4
+ user-invocable: true
5
+ version: "1.0.0"
6
+ category: mpm-command
7
+ tags: [mpm-command, session, pm-recommended]
8
+ ---
9
+
10
+ # /mpm-pause
11
+
12
+ Pause the current session and save all work state for later resume.
13
+
14
+ ## What This Does
15
+
16
+ When invoked, this skill:
17
+ 1. Captures current work state (todos, git status, context summary)
18
+ 2. Creates session file at `.claude-mpm/sessions/session-{timestamp}.md`
19
+ 3. Updates `.claude-mpm/sessions/LATEST-SESSION.txt` pointer
20
+ 4. Optionally commits session state to git
21
+ 5. Shows user the session file path for later resume
22
+
23
+ ## Usage
24
+
25
+ ```
26
+ /mpm-pause [optional message describing current work]
27
+ ```
28
+
29
+ **Examples:**
30
+ ```
31
+ /mpm-pause
32
+ /mpm-pause Working on authentication refactor, about to test login flow
33
+ /mpm-pause Need to context switch to urgent bug fix
34
+ ```
35
+
36
+ ## Implementation
37
+
38
+ **Execute the following Python code to pause the session:**
39
+
40
+ ```python
41
+ from pathlib import Path
42
+ from claude_mpm.services.cli.session_pause_manager import SessionPauseManager
43
+
44
+ # Optional: Get message from user's command
45
+ # If user provided message after /mpm-pause, extract it
46
+ # Otherwise, message = None
47
+
48
+ # Create session pause manager
49
+ manager = SessionPauseManager(project_path=Path.cwd())
50
+
51
+ # Create pause session
52
+ session_id = manager.create_pause_session(
53
+ message=message, # Optional context message
54
+ skip_commit=False, # Will commit to git if in a repo
55
+ export_path=None, # No additional export needed
56
+ )
57
+
58
+ # Report success to user
59
+ print(f"✅ Session paused successfully!")
60
+ print(f"")
61
+ print(f"Session ID: {session_id}")
62
+ print(f"Session files:")
63
+ print(f" - .claude-mpm/sessions/{session_id}.md (human-readable)")
64
+ print(f" - .claude-mpm/sessions/{session_id}.json (machine-readable)")
65
+ print(f" - .claude-mpm/sessions/{session_id}.yaml (config format)")
66
+ print(f"")
67
+ print(f"Quick resume:")
68
+ print(f" /mpm-resume")
69
+ print(f"")
70
+ print(f"View session context:")
71
+ print(f" cat .claude-mpm/sessions/LATEST-SESSION.txt")
72
+ print(f" cat .claude-mpm/sessions/{session_id}.md")
73
+ ```
74
+
75
+ ## What Gets Saved
76
+
77
+ **Session State:**
78
+ - Session ID and timestamp
79
+ - Current working directory
80
+ - Git branch, recent commits, and file status
81
+ - Primary task and current phase
82
+ - Context message (if provided)
83
+
84
+ **Resume Instructions:**
85
+ - Quick-start commands
86
+ - Validation commands
87
+ - Files to review
88
+
89
+ **File Formats:**
90
+ - `.md` - Human-readable markdown (for reading)
91
+ - `.json` - Machine-readable (for tooling)
92
+ - `.yaml` - Human-readable config (for editing)
93
+
94
+ ## Session File Location
95
+
96
+ All session files are stored in:
97
+ ```
98
+ .claude-mpm/sessions/
99
+ ├── LATEST-SESSION.txt # Pointer to most recent session
100
+ ├── session-YYYYMMDD-HHMMSS.md
101
+ ├── session-YYYYMMDD-HHMMSS.json
102
+ └── session-YYYYMMDD-HHMMSS.yaml
103
+ ```
104
+
105
+ ## Token Budget
106
+
107
+ **Token usage:** ~5-10k tokens to execute (2-5% of context budget)
108
+
109
+ **Benefit:** Saves all remaining context for future resume, allowing you to:
110
+ - Context switch to urgent tasks
111
+ - Take a break and resume later
112
+ - Archive current work state before major changes
113
+
114
+ ## Resume Later
115
+
116
+ To resume this session:
117
+ ```
118
+ /mpm-resume
119
+ ```
120
+
121
+ Or manually:
122
+ ```bash
123
+ cat .claude-mpm/sessions/LATEST-SESSION.txt
124
+ cat .claude-mpm/sessions/session-YYYYMMDD-HHMMSS.md
125
+ ```
126
+
127
+ ## Git Integration
128
+
129
+ If in a git repository, the session will be automatically committed with message:
130
+ ```
131
+ session: pause at YYYY-MM-DD HH:MM:SS
132
+
133
+ Session ID: session-YYYYMMDD-HHMMSS
134
+ Context: [your optional message]
135
+ ```
136
+
137
+ ## Use Cases
138
+
139
+ **Context switching:**
140
+ ```
141
+ /mpm-pause Switching to urgent production bug
142
+ ```
143
+
144
+ **End of work session:**
145
+ ```
146
+ /mpm-pause Completed API refactor, ready for testing tomorrow
147
+ ```
148
+
149
+ **Before major changes:**
150
+ ```
151
+ /mpm-pause Saving state before attempting risky refactor
152
+ ```
153
+
154
+ **When approaching context limit:**
155
+ ```
156
+ /mpm-pause Hit 150k tokens, starting fresh session
157
+ ```
158
+
159
+ ## Related Commands
160
+
161
+ - `/mpm-resume` - Resume from most recent paused session
162
+ - `/mpm-init resume` - Alternative resume command
163
+ - See `docs/features/session-auto-resume.md` for auto-pause behavior
164
+
165
+ ## Notes
166
+
167
+ - Session files are project-local (not synced across machines)
168
+ - Git commit is optional (automatically skipped if not a repo)
169
+ - LATEST-SESSION.txt always points to most recent session
170
+ - Session format compatible with auto-pause feature (70% context trigger)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-mpm
3
- Version: 5.6.13
3
+ Version: 5.6.15
4
4
  Summary: Claude Multi-Agent Project Manager - Orchestrate Claude with agent delegation and ticket tracking
5
5
  Author-email: Bob Matsuoka <bob@matsuoka.com>
6
6
  Maintainer: Claude MPM Team
@@ -23,7 +23,7 @@ License-File: LICENSE
23
23
  License-File: LICENSE-FAQ.md
24
24
  Requires-Dist: ai-trackdown-pytools>=1.4.0
25
25
  Requires-Dist: pyyaml>=6.0
26
- Requires-Dist: python-dotenv>=0.19.0
26
+ Requires-Dist: python-dotenv>=1.0.0
27
27
  Requires-Dist: click>=8.0.0
28
28
  Requires-Dist: pexpect>=4.8.0
29
29
  Requires-Dist: psutil>=5.9.0