claude-mpm 4.7.7__py3-none-any.whl → 4.7.9__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/configure.py +245 -61
- claude_mpm/cli/commands/mpm_init.py +151 -1
- claude_mpm/cli/parsers/mpm_init_parser.py +5 -0
- claude_mpm/commands/mpm-init.md +17 -0
- {claude_mpm-4.7.7.dist-info → claude_mpm-4.7.9.dist-info}/METADATA +1 -1
- {claude_mpm-4.7.7.dist-info → claude_mpm-4.7.9.dist-info}/RECORD +11 -11
- {claude_mpm-4.7.7.dist-info → claude_mpm-4.7.9.dist-info}/WHEEL +0 -0
- {claude_mpm-4.7.7.dist-info → claude_mpm-4.7.9.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.7.7.dist-info → claude_mpm-4.7.9.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.7.7.dist-info → claude_mpm-4.7.9.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
4.7.
|
1
|
+
4.7.9
|
@@ -60,6 +60,8 @@ class SimpleAgentManager:
|
|
60
60
|
import logging
|
61
61
|
|
62
62
|
self.logger = logging.getLogger(__name__)
|
63
|
+
# Track pending changes for batch operations
|
64
|
+
self.deferred_changes: Dict[str, bool] = {}
|
63
65
|
|
64
66
|
def _load_states(self):
|
65
67
|
"""Load agent states from file."""
|
@@ -85,6 +87,33 @@ class SimpleAgentManager:
|
|
85
87
|
self.states[agent_name]["enabled"] = enabled
|
86
88
|
self._save_states()
|
87
89
|
|
90
|
+
def set_agent_enabled_deferred(self, agent_name: str, enabled: bool) -> None:
|
91
|
+
"""Queue agent state change without saving."""
|
92
|
+
self.deferred_changes[agent_name] = enabled
|
93
|
+
|
94
|
+
def commit_deferred_changes(self) -> None:
|
95
|
+
"""Save all deferred changes at once."""
|
96
|
+
for agent_name, enabled in self.deferred_changes.items():
|
97
|
+
if agent_name not in self.states:
|
98
|
+
self.states[agent_name] = {}
|
99
|
+
self.states[agent_name]["enabled"] = enabled
|
100
|
+
self._save_states()
|
101
|
+
self.deferred_changes.clear()
|
102
|
+
|
103
|
+
def discard_deferred_changes(self) -> None:
|
104
|
+
"""Discard all pending changes."""
|
105
|
+
self.deferred_changes.clear()
|
106
|
+
|
107
|
+
def get_pending_state(self, agent_name: str) -> bool:
|
108
|
+
"""Get agent state including pending changes."""
|
109
|
+
if agent_name in self.deferred_changes:
|
110
|
+
return self.deferred_changes[agent_name]
|
111
|
+
return self.states.get(agent_name, {}).get("enabled", True)
|
112
|
+
|
113
|
+
def has_pending_changes(self) -> bool:
|
114
|
+
"""Check if there are unsaved changes."""
|
115
|
+
return len(self.deferred_changes) > 0
|
116
|
+
|
88
117
|
def discover_agents(self) -> List[AgentConfig]:
|
89
118
|
"""Discover available agents from template JSON files."""
|
90
119
|
agents = []
|
@@ -296,6 +325,33 @@ class ConfigureCommand(BaseCommand):
|
|
296
325
|
self._switch_scope()
|
297
326
|
elif choice == "6":
|
298
327
|
self._show_version_info_interactive()
|
328
|
+
elif choice == "l":
|
329
|
+
# Check for pending agent changes
|
330
|
+
if self.agent_manager and self.agent_manager.has_pending_changes():
|
331
|
+
should_save = Confirm.ask(
|
332
|
+
"[yellow]You have unsaved agent changes. Save them before launching?[/yellow]",
|
333
|
+
default=True,
|
334
|
+
)
|
335
|
+
if should_save:
|
336
|
+
self.agent_manager.commit_deferred_changes()
|
337
|
+
self.console.print("[green]✓ Agent changes saved[/green]")
|
338
|
+
else:
|
339
|
+
self.agent_manager.discard_deferred_changes()
|
340
|
+
self.console.print(
|
341
|
+
"[yellow]⚠ Agent changes discarded[/yellow]"
|
342
|
+
)
|
343
|
+
|
344
|
+
# Save all configuration
|
345
|
+
self.console.print("\n[cyan]Saving configuration...[/cyan]")
|
346
|
+
if self._save_all_configuration():
|
347
|
+
# Launch Claude MPM (this will replace the process if successful)
|
348
|
+
self._launch_claude_mpm()
|
349
|
+
# If execvp fails, we'll return here and break
|
350
|
+
break
|
351
|
+
self.console.print(
|
352
|
+
"[red]✗ Failed to save configuration. Not launching.[/red]"
|
353
|
+
)
|
354
|
+
Prompt.ask("\nPress Enter to continue")
|
299
355
|
elif choice == "q":
|
300
356
|
self.console.print(
|
301
357
|
"\n[green]Configuration complete. Goodbye![/green]"
|
@@ -356,16 +412,17 @@ class ConfigureCommand(BaseCommand):
|
|
356
412
|
),
|
357
413
|
("5", "Switch Scope", f"Current: {self.current_scope}"),
|
358
414
|
("6", "Version Info", "Display MPM and Claude versions"),
|
359
|
-
("
|
415
|
+
("l", "Save & Launch", "Save all changes and start Claude MPM"),
|
416
|
+
("q", "Quit", "Exit without launching"),
|
360
417
|
]
|
361
418
|
|
362
419
|
table = Table(show_header=False, box=None, padding=(0, 2))
|
363
|
-
table.add_column("Key", style="cyan", width=
|
364
|
-
table.add_column("Option", style="bold white", width=
|
365
|
-
table.add_column("Description", style="
|
420
|
+
table.add_column("Key", style="cyan bold", width=4) # Bolder shortcuts
|
421
|
+
table.add_column("Option", style="bold white", width=24) # Wider for titles
|
422
|
+
table.add_column("Description", style="white") # Better contrast
|
366
423
|
|
367
424
|
for key, option, desc in menu_items:
|
368
|
-
table.add_row(f"[{key}]", option, desc)
|
425
|
+
table.add_row(f"\\[{key}]", option, desc)
|
369
426
|
|
370
427
|
menu_panel = Panel(
|
371
428
|
table, title="[bold]Main Menu[/bold]", box=ROUNDED, style="green"
|
@@ -392,15 +449,10 @@ class ConfigureCommand(BaseCommand):
|
|
392
449
|
self.console.print("\n[bold]Agent Management Options:[/bold]")
|
393
450
|
|
394
451
|
# Use Text objects to properly display shortcuts with styling
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
self.console.print(
|
399
|
-
|
400
|
-
text_d = Text(" ")
|
401
|
-
text_d.append("[d]", style="cyan bold")
|
402
|
-
text_d.append(" Disable an agent")
|
403
|
-
self.console.print(text_d)
|
452
|
+
text_t = Text(" ")
|
453
|
+
text_t.append("[t]", style="cyan bold")
|
454
|
+
text_t.append(" Toggle agents (enable/disable multiple)")
|
455
|
+
self.console.print(text_t)
|
404
456
|
|
405
457
|
text_c = Text(" ")
|
406
458
|
text_c.append("[c]", style="cyan bold")
|
@@ -428,10 +480,8 @@ class ConfigureCommand(BaseCommand):
|
|
428
480
|
|
429
481
|
if choice == "b":
|
430
482
|
break
|
431
|
-
if choice == "
|
432
|
-
self.
|
433
|
-
elif choice == "d":
|
434
|
-
self._disable_agent_interactive(agents)
|
483
|
+
if choice == "t":
|
484
|
+
self._toggle_agents_interactive(agents)
|
435
485
|
elif choice == "c":
|
436
486
|
self._customize_agent_template(agents)
|
437
487
|
elif choice == "v":
|
@@ -494,55 +544,127 @@ class ConfigureCommand(BaseCommand):
|
|
494
544
|
|
495
545
|
self.console.print(table)
|
496
546
|
|
497
|
-
def
|
498
|
-
"""
|
499
|
-
|
547
|
+
def _display_agents_with_pending_states(self, agents: List[AgentConfig]) -> None:
|
548
|
+
"""Display agents table with pending state indicators."""
|
549
|
+
has_pending = self.agent_manager.has_pending_changes()
|
550
|
+
pending_count = len(self.agent_manager.deferred_changes) if has_pending else 0
|
500
551
|
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
self.agent_manager.set_agent_enabled(agent.name, True)
|
505
|
-
self.console.print("[green]All agents enabled successfully![/green]")
|
506
|
-
else:
|
507
|
-
try:
|
508
|
-
idx = int(agent_id) - 1
|
509
|
-
if 0 <= idx < len(agents):
|
510
|
-
agent = agents[idx]
|
511
|
-
self.agent_manager.set_agent_enabled(agent.name, True)
|
512
|
-
self.console.print(
|
513
|
-
f"[green]Agent '{agent.name}' enabled successfully![/green]"
|
514
|
-
)
|
515
|
-
else:
|
516
|
-
self.console.print("[red]Invalid agent ID.[/red]")
|
517
|
-
except ValueError:
|
518
|
-
self.console.print("[red]Invalid input. Please enter a number.[/red]")
|
552
|
+
title = f"Available Agents ({len(agents)} total)"
|
553
|
+
if has_pending:
|
554
|
+
title += f" [yellow]({pending_count} change{'s' if pending_count != 1 else ''} pending)[/yellow]"
|
519
555
|
|
520
|
-
|
556
|
+
table = Table(title=title, box=ROUNDED, show_lines=True, expand=True)
|
557
|
+
table.add_column("ID", justify="right", style="cyan", width=5)
|
558
|
+
table.add_column("Name", style="bold", width=22)
|
559
|
+
table.add_column("Status", width=20)
|
560
|
+
table.add_column("Description", style="bold cyan", width=45)
|
521
561
|
|
522
|
-
|
523
|
-
|
524
|
-
|
562
|
+
for idx, agent in enumerate(agents, 1):
|
563
|
+
current_state = self.agent_manager.is_agent_enabled(agent.name)
|
564
|
+
pending_state = self.agent_manager.get_pending_state(agent.name)
|
525
565
|
|
526
|
-
|
527
|
-
if
|
528
|
-
|
529
|
-
|
530
|
-
self.console.print("[green]All agents disabled successfully![/green]")
|
531
|
-
else:
|
532
|
-
try:
|
533
|
-
idx = int(agent_id) - 1
|
534
|
-
if 0 <= idx < len(agents):
|
535
|
-
agent = agents[idx]
|
536
|
-
self.agent_manager.set_agent_enabled(agent.name, False)
|
537
|
-
self.console.print(
|
538
|
-
f"[green]Agent '{agent.name}' disabled successfully![/green]"
|
539
|
-
)
|
566
|
+
# Show pending status with arrow
|
567
|
+
if current_state != pending_state:
|
568
|
+
if pending_state:
|
569
|
+
status = "[yellow]✗ Disabled → ✓ Enabled[/yellow]"
|
540
570
|
else:
|
541
|
-
|
542
|
-
|
543
|
-
|
571
|
+
status = "[yellow]✓ Enabled → ✗ Disabled[/yellow]"
|
572
|
+
else:
|
573
|
+
status = (
|
574
|
+
"[green]✓ Enabled[/green]"
|
575
|
+
if current_state
|
576
|
+
else "[dim]✗ Disabled[/dim]"
|
577
|
+
)
|
544
578
|
|
545
|
-
|
579
|
+
desc_display = Text()
|
580
|
+
desc_display.append(
|
581
|
+
(
|
582
|
+
agent.description[:42] + "..."
|
583
|
+
if len(agent.description) > 42
|
584
|
+
else agent.description
|
585
|
+
),
|
586
|
+
style="cyan",
|
587
|
+
)
|
588
|
+
|
589
|
+
table.add_row(str(idx), agent.name, status, desc_display)
|
590
|
+
|
591
|
+
self.console.print(table)
|
592
|
+
|
593
|
+
def _toggle_agents_interactive(self, agents: List[AgentConfig]) -> None:
|
594
|
+
"""Interactive multi-agent enable/disable with batch save."""
|
595
|
+
|
596
|
+
# Initialize pending states from current states
|
597
|
+
for agent in agents:
|
598
|
+
current_state = self.agent_manager.is_agent_enabled(agent.name)
|
599
|
+
self.agent_manager.set_agent_enabled_deferred(agent.name, current_state)
|
600
|
+
|
601
|
+
while True:
|
602
|
+
# Display table with pending states
|
603
|
+
self._display_agents_with_pending_states(agents)
|
604
|
+
|
605
|
+
# Show menu
|
606
|
+
self.console.print("\n[bold]Toggle Agent Status:[/bold]")
|
607
|
+
text_toggle = Text(" ")
|
608
|
+
text_toggle.append("[t]", style="cyan bold")
|
609
|
+
text_toggle.append(" Enter agent IDs to toggle (e.g., '1,3,5' or '1-4')")
|
610
|
+
self.console.print(text_toggle)
|
611
|
+
|
612
|
+
text_all = Text(" ")
|
613
|
+
text_all.append("[a]", style="cyan bold")
|
614
|
+
text_all.append(" Enable all agents")
|
615
|
+
self.console.print(text_all)
|
616
|
+
|
617
|
+
text_none = Text(" ")
|
618
|
+
text_none.append("[n]", style="cyan bold")
|
619
|
+
text_none.append(" Disable all agents")
|
620
|
+
self.console.print(text_none)
|
621
|
+
|
622
|
+
text_save = Text(" ")
|
623
|
+
text_save.append("[s]", style="green bold")
|
624
|
+
text_save.append(" Save changes and return")
|
625
|
+
self.console.print(text_save)
|
626
|
+
|
627
|
+
text_cancel = Text(" ")
|
628
|
+
text_cancel.append("[c]", style="yellow bold")
|
629
|
+
text_cancel.append(" Cancel (discard changes)")
|
630
|
+
self.console.print(text_cancel)
|
631
|
+
|
632
|
+
choice = (
|
633
|
+
Prompt.ask("[bold cyan]Select an option[/bold cyan]", default="s")
|
634
|
+
.strip()
|
635
|
+
.lower()
|
636
|
+
)
|
637
|
+
|
638
|
+
if choice == "s":
|
639
|
+
if self.agent_manager.has_pending_changes():
|
640
|
+
self.agent_manager.commit_deferred_changes()
|
641
|
+
self.console.print("[green]✓ Changes saved successfully![/green]")
|
642
|
+
else:
|
643
|
+
self.console.print("[yellow]No changes to save.[/yellow]")
|
644
|
+
Prompt.ask("Press Enter to continue")
|
645
|
+
break
|
646
|
+
if choice == "c":
|
647
|
+
self.agent_manager.discard_deferred_changes()
|
648
|
+
self.console.print("[yellow]Changes discarded.[/yellow]")
|
649
|
+
Prompt.ask("Press Enter to continue")
|
650
|
+
break
|
651
|
+
if choice == "a":
|
652
|
+
for agent in agents:
|
653
|
+
self.agent_manager.set_agent_enabled_deferred(agent.name, True)
|
654
|
+
elif choice == "n":
|
655
|
+
for agent in agents:
|
656
|
+
self.agent_manager.set_agent_enabled_deferred(agent.name, False)
|
657
|
+
elif choice == "t" or choice.replace(",", "").replace("-", "").isdigit():
|
658
|
+
selected_ids = self._parse_id_selection(
|
659
|
+
choice if choice != "t" else Prompt.ask("Enter IDs"), len(agents)
|
660
|
+
)
|
661
|
+
for idx in selected_ids:
|
662
|
+
if 1 <= idx <= len(agents):
|
663
|
+
agent = agents[idx - 1]
|
664
|
+
current = self.agent_manager.get_pending_state(agent.name)
|
665
|
+
self.agent_manager.set_agent_enabled_deferred(
|
666
|
+
agent.name, not current
|
667
|
+
)
|
546
668
|
|
547
669
|
def _customize_agent_template(self, agents: List[AgentConfig]) -> None:
|
548
670
|
"""Customize agent JSON template."""
|
@@ -1707,6 +1829,68 @@ class ConfigureCommand(BaseCommand):
|
|
1707
1829
|
Prompt.ask("Press Enter to continue")
|
1708
1830
|
return False
|
1709
1831
|
|
1832
|
+
def _save_all_configuration(self) -> bool:
|
1833
|
+
"""Save all configuration changes across all contexts.
|
1834
|
+
|
1835
|
+
Returns:
|
1836
|
+
bool: True if all saves successful, False otherwise
|
1837
|
+
"""
|
1838
|
+
try:
|
1839
|
+
# 1. Save any pending agent changes
|
1840
|
+
if self.agent_manager and self.agent_manager.has_pending_changes():
|
1841
|
+
self.agent_manager.commit_deferred_changes()
|
1842
|
+
self.console.print("[green]✓ Agent changes saved[/green]")
|
1843
|
+
|
1844
|
+
# 2. Save configuration file
|
1845
|
+
config = Config()
|
1846
|
+
|
1847
|
+
# Determine config file path based on scope
|
1848
|
+
if self.current_scope == "project":
|
1849
|
+
config_file = self.project_dir / ".claude-mpm" / "configuration.yaml"
|
1850
|
+
else:
|
1851
|
+
config_file = Path.home() / ".claude-mpm" / "configuration.yaml"
|
1852
|
+
|
1853
|
+
config_file.parent.mkdir(parents=True, exist_ok=True)
|
1854
|
+
|
1855
|
+
# Save with suppressed logging to avoid duplicate messages
|
1856
|
+
import logging
|
1857
|
+
|
1858
|
+
root_logger = logging.getLogger("claude_mpm")
|
1859
|
+
original_level = root_logger.level
|
1860
|
+
root_logger.setLevel(logging.WARNING)
|
1861
|
+
|
1862
|
+
try:
|
1863
|
+
config.save(config_file, format="yaml")
|
1864
|
+
finally:
|
1865
|
+
root_logger.setLevel(original_level)
|
1866
|
+
|
1867
|
+
self.console.print(f"[green]✓ Configuration saved to {config_file}[/green]")
|
1868
|
+
return True
|
1869
|
+
|
1870
|
+
except Exception as e:
|
1871
|
+
self.console.print(f"[red]✗ Error saving configuration: {e}[/red]")
|
1872
|
+
import traceback
|
1873
|
+
|
1874
|
+
traceback.print_exc()
|
1875
|
+
return False
|
1876
|
+
|
1877
|
+
def _launch_claude_mpm(self) -> None:
|
1878
|
+
"""Launch Claude MPM run command, replacing current process."""
|
1879
|
+
self.console.print("\n[bold cyan]═══ Launching Claude MPM ═══[/bold cyan]\n")
|
1880
|
+
|
1881
|
+
try:
|
1882
|
+
# Use execvp to replace the current process with claude-mpm run
|
1883
|
+
# This ensures a clean transition from configurator to Claude MPM
|
1884
|
+
os.execvp("claude-mpm", ["claude-mpm", "run"])
|
1885
|
+
except Exception as e:
|
1886
|
+
self.console.print(
|
1887
|
+
f"[yellow]⚠ Could not launch Claude MPM automatically: {e}[/yellow]"
|
1888
|
+
)
|
1889
|
+
self.console.print(
|
1890
|
+
"[cyan]→ Please run 'claude-mpm run' manually to start.[/cyan]"
|
1891
|
+
)
|
1892
|
+
Prompt.ask("\nPress Enter to exit")
|
1893
|
+
|
1710
1894
|
def _switch_scope(self) -> None:
|
1711
1895
|
"""Switch between project and user scope."""
|
1712
1896
|
self.current_scope = "user" if self.current_scope == "project" else "project"
|
@@ -12,7 +12,7 @@ import contextlib
|
|
12
12
|
import subprocess
|
13
13
|
import sys
|
14
14
|
from pathlib import Path
|
15
|
-
from typing import Dict, List, Optional
|
15
|
+
from typing import Any, Dict, List, Optional
|
16
16
|
|
17
17
|
import click
|
18
18
|
from rich.console import Console
|
@@ -63,6 +63,7 @@ class MPMInitCommand:
|
|
63
63
|
skip_archive: bool = False,
|
64
64
|
dry_run: bool = False,
|
65
65
|
quick_update: bool = False,
|
66
|
+
catchup: bool = False,
|
66
67
|
non_interactive: bool = False,
|
67
68
|
days: int = 30,
|
68
69
|
export: Optional[str] = None,
|
@@ -84,6 +85,7 @@ class MPMInitCommand:
|
|
84
85
|
skip_archive: Skip archiving existing files
|
85
86
|
dry_run: Show what would be done without making changes
|
86
87
|
quick_update: Perform lightweight update based on recent git activity
|
88
|
+
catchup: Show recent commit history from all branches for PM context
|
87
89
|
non_interactive: Non-interactive mode - display report only without prompting
|
88
90
|
days: Number of days for git history analysis (7, 14, 30, 60, or 90)
|
89
91
|
export: Export report to file (path or "auto" for default location)
|
@@ -99,6 +101,11 @@ class MPMInitCommand:
|
|
99
101
|
if review_only:
|
100
102
|
return self._run_review_mode()
|
101
103
|
|
104
|
+
if catchup:
|
105
|
+
data = self._catchup()
|
106
|
+
self._display_catchup(data)
|
107
|
+
return {"status": "success", "mode": "catchup", "catchup_data": data}
|
108
|
+
|
102
109
|
if quick_update:
|
103
110
|
return self._run_quick_update_mode(
|
104
111
|
days=days,
|
@@ -679,6 +686,142 @@ The final CLAUDE.md should be a comprehensive, well-organized guide that any AI
|
|
679
686
|
console.print("\n[yellow]Quick update cancelled[/yellow]")
|
680
687
|
return {"status": "cancelled", "message": "Quick update cancelled"}
|
681
688
|
|
689
|
+
def _catchup(self) -> Dict[str, Any]:
|
690
|
+
"""Get recent commit history for PM context.
|
691
|
+
|
692
|
+
Returns:
|
693
|
+
Dict containing commit history and contributor stats
|
694
|
+
"""
|
695
|
+
from collections import Counter
|
696
|
+
from datetime import datetime
|
697
|
+
from subprocess import run
|
698
|
+
|
699
|
+
try:
|
700
|
+
# Get last 25 commits from all branches with author info
|
701
|
+
result = run(
|
702
|
+
["git", "log", "--all", "--format=%h|%an|%ai|%s", "-25"],
|
703
|
+
capture_output=True,
|
704
|
+
text=True,
|
705
|
+
check=True,
|
706
|
+
cwd=str(self.project_path),
|
707
|
+
)
|
708
|
+
|
709
|
+
commits = []
|
710
|
+
authors = []
|
711
|
+
|
712
|
+
for line in result.stdout.strip().split("\n"):
|
713
|
+
if not line:
|
714
|
+
continue
|
715
|
+
|
716
|
+
parts = line.split("|", 3)
|
717
|
+
if len(parts) == 4:
|
718
|
+
hash_val, author, date_str, message = parts
|
719
|
+
|
720
|
+
# Parse date
|
721
|
+
try:
|
722
|
+
dt = datetime.fromisoformat(date_str.replace(" ", "T", 1))
|
723
|
+
date_display = dt.strftime("%Y-%m-%d %H:%M")
|
724
|
+
except Exception:
|
725
|
+
date_display = date_str[:16]
|
726
|
+
|
727
|
+
commits.append(
|
728
|
+
{
|
729
|
+
"hash": hash_val,
|
730
|
+
"author": author,
|
731
|
+
"date": date_display,
|
732
|
+
"message": message,
|
733
|
+
}
|
734
|
+
)
|
735
|
+
authors.append(author)
|
736
|
+
|
737
|
+
# Calculate contributor stats
|
738
|
+
author_counts = Counter(authors)
|
739
|
+
|
740
|
+
return {
|
741
|
+
"commits": commits,
|
742
|
+
"total_commits": len(commits),
|
743
|
+
"contributors": dict(author_counts),
|
744
|
+
"contributor_count": len(author_counts),
|
745
|
+
}
|
746
|
+
|
747
|
+
except Exception as e:
|
748
|
+
console.print(f"[yellow]Could not retrieve commit history: {e}[/yellow]")
|
749
|
+
return {
|
750
|
+
"commits": [],
|
751
|
+
"total_commits": 0,
|
752
|
+
"contributors": {},
|
753
|
+
"contributor_count": 0,
|
754
|
+
"error": str(e),
|
755
|
+
}
|
756
|
+
|
757
|
+
def _display_catchup(self, data: Dict[str, Any]) -> None:
|
758
|
+
"""Display catchup information to console.
|
759
|
+
|
760
|
+
Args:
|
761
|
+
data: Commit history data from _catchup()
|
762
|
+
"""
|
763
|
+
from rich.panel import Panel
|
764
|
+
from rich.table import Table
|
765
|
+
|
766
|
+
if data.get("error"):
|
767
|
+
console.print(
|
768
|
+
Panel(
|
769
|
+
"[yellow]Not a git repository or no commits found[/yellow]",
|
770
|
+
title="⚠️ Catchup Status",
|
771
|
+
border_style="yellow",
|
772
|
+
)
|
773
|
+
)
|
774
|
+
return
|
775
|
+
|
776
|
+
# Display contributor summary
|
777
|
+
if data["contributors"]:
|
778
|
+
console.print("\n[bold cyan]👥 Active Contributors[/bold cyan]")
|
779
|
+
for author, count in sorted(
|
780
|
+
data["contributors"].items(), key=lambda x: x[1], reverse=True
|
781
|
+
):
|
782
|
+
console.print(
|
783
|
+
f" • [green]{author}[/green]: {count} commit{'s' if count != 1 else ''}"
|
784
|
+
)
|
785
|
+
|
786
|
+
# Display commit history table
|
787
|
+
if data["commits"]:
|
788
|
+
console.print(
|
789
|
+
f"\n[bold cyan]📝 Last {data['total_commits']} Commits[/bold cyan]"
|
790
|
+
)
|
791
|
+
|
792
|
+
table = Table(
|
793
|
+
show_header=True, header_style="bold magenta", border_style="dim"
|
794
|
+
)
|
795
|
+
table.add_column("#", style="dim", width=3)
|
796
|
+
table.add_column("Hash", style="yellow", width=8)
|
797
|
+
table.add_column("Author", style="green", width=20)
|
798
|
+
table.add_column("Date", style="cyan", width=16)
|
799
|
+
table.add_column("Message", style="white")
|
800
|
+
|
801
|
+
for idx, commit in enumerate(data["commits"], 1):
|
802
|
+
# Truncate message if too long
|
803
|
+
msg = commit["message"]
|
804
|
+
if len(msg) > 80:
|
805
|
+
msg = msg[:77] + "..."
|
806
|
+
|
807
|
+
# Truncate author if too long
|
808
|
+
author = commit["author"]
|
809
|
+
if len(author) > 18:
|
810
|
+
author = author[:18] + "..."
|
811
|
+
|
812
|
+
table.add_row(str(idx), commit["hash"], author, commit["date"], msg)
|
813
|
+
|
814
|
+
console.print(table)
|
815
|
+
|
816
|
+
# Display PM recommendations
|
817
|
+
console.print("\n[bold cyan]💡 PM Recommendations[/bold cyan]")
|
818
|
+
console.print(
|
819
|
+
f" • Total activity: {data['total_commits']} commits from {data['contributor_count']} contributor{'s' if data['contributor_count'] != 1 else ''}"
|
820
|
+
)
|
821
|
+
console.print(" • Review commit messages for recent project context")
|
822
|
+
console.print(" • Identify development patterns and focus areas")
|
823
|
+
console.print(" • Use this context to inform current work priorities\n")
|
824
|
+
|
682
825
|
def _generate_activity_report(
|
683
826
|
self, git_analysis: Dict, doc_analysis: Dict, days: int = 30
|
684
827
|
) -> Dict:
|
@@ -1463,6 +1606,11 @@ preserving valuable project-specific information while refreshing standard secti
|
|
1463
1606
|
is_flag=True,
|
1464
1607
|
help="Perform lightweight update based on recent git activity (default: 30 days)",
|
1465
1608
|
)
|
1609
|
+
@click.option(
|
1610
|
+
"--catchup",
|
1611
|
+
is_flag=True,
|
1612
|
+
help="Show recent commit history from all branches for PM context",
|
1613
|
+
)
|
1466
1614
|
@click.option(
|
1467
1615
|
"--non-interactive",
|
1468
1616
|
is_flag=True,
|
@@ -1499,6 +1647,7 @@ def mpm_init(
|
|
1499
1647
|
verbose,
|
1500
1648
|
ast_analysis,
|
1501
1649
|
quick_update,
|
1650
|
+
catchup,
|
1502
1651
|
non_interactive,
|
1503
1652
|
days,
|
1504
1653
|
export,
|
@@ -1544,6 +1693,7 @@ def mpm_init(
|
|
1544
1693
|
preserve_custom=preserve_custom,
|
1545
1694
|
skip_archive=skip_archive,
|
1546
1695
|
quick_update=quick_update,
|
1696
|
+
catchup=catchup,
|
1547
1697
|
non_interactive=non_interactive,
|
1548
1698
|
days=days,
|
1549
1699
|
export=export,
|
@@ -91,6 +91,11 @@ def add_mpm_init_subparser(subparsers: Any) -> None:
|
|
91
91
|
action="store_true",
|
92
92
|
help="Perform lightweight update based on recent git activity (default: 30 days)",
|
93
93
|
)
|
94
|
+
init_group.add_argument(
|
95
|
+
"--catchup",
|
96
|
+
action="store_true",
|
97
|
+
help="Show recent commit history from all branches for PM context",
|
98
|
+
)
|
94
99
|
init_group.add_argument(
|
95
100
|
"--non-interactive",
|
96
101
|
action="store_true",
|
claude_mpm/commands/mpm-init.md
CHANGED
@@ -146,6 +146,23 @@ Fast update based on recent 30-day git activity. Generates activity report and u
|
|
146
146
|
|
147
147
|
**Note**: Typing `/mpm-init update` executes `claude-mpm mpm-init --quick-update` automatically.
|
148
148
|
|
149
|
+
### Catchup Mode
|
150
|
+
|
151
|
+
Show recent commit history to provide PM with project context:
|
152
|
+
|
153
|
+
```bash
|
154
|
+
/mpm-init catchup
|
155
|
+
```
|
156
|
+
|
157
|
+
This displays:
|
158
|
+
- Last 25 commits from all branches
|
159
|
+
- Author attribution (WHO did WHAT)
|
160
|
+
- Temporal context (WHEN)
|
161
|
+
- Contributor activity summary
|
162
|
+
- PM recommendations based on commit patterns
|
163
|
+
|
164
|
+
Useful for understanding recent development activity and getting PM up to speed on project changes.
|
165
|
+
|
149
166
|
### Review Project State
|
150
167
|
```bash
|
151
168
|
/mpm-init --review
|
@@ -1,5 +1,5 @@
|
|
1
1
|
claude_mpm/BUILD_NUMBER,sha256=9JfxhnDtr-8l3kCP2U5TVXSErptHoga8m7XA8zqgGOc,4
|
2
|
-
claude_mpm/VERSION,sha256=
|
2
|
+
claude_mpm/VERSION,sha256=7-IAGCtmvIwdixVQJDoq-zKnNOnvOHQb0ZEINbbBPJ4,6
|
3
3
|
claude_mpm/__init__.py,sha256=UCw6j9e_tZQ3kJtTqmdfNv7MHyw9nD1jkj80WurwM2g,2064
|
4
4
|
claude_mpm/__main__.py,sha256=Ro5UBWBoQaSAIoSqWAr7zkbLyvi4sSy28WShqAhKJG0,723
|
5
5
|
claude_mpm/constants.py,sha256=cChN3myrAcF3jC-6DvHnBFTEnwlDk-TAsIXPvUZr_yw,5953
|
@@ -79,7 +79,7 @@ claude_mpm/cli/commands/analyze_code.py,sha256=yWZpG0aL4XlhcthtvbUqnFSlnvvseWO3V
|
|
79
79
|
claude_mpm/cli/commands/cleanup.py,sha256=TukZoPVfAFSF4ICfKCQUibczDE73EJP8nbEbfuT8GhE,19768
|
80
80
|
claude_mpm/cli/commands/cleanup_orphaned_agents.py,sha256=JR8crvgrz7Sa6d-SI-gKywok5S9rwc_DzDVk_h85sVs,4467
|
81
81
|
claude_mpm/cli/commands/config.py,sha256=Yfi8WO-10_MYz2QipFw-yEzVvHKNQ6iSQXeyW5J85Cg,18559
|
82
|
-
claude_mpm/cli/commands/configure.py,sha256=
|
82
|
+
claude_mpm/cli/commands/configure.py,sha256=rj8IEAyI-TgRAnTxtOJ8AL2xGhP33gOte8z8WzKhsG4,92942
|
83
83
|
claude_mpm/cli/commands/dashboard.py,sha256=4jPTmTl97DRNNJlYREWeE1iDdkct1uL-vv24MZn9fj4,11403
|
84
84
|
claude_mpm/cli/commands/debug.py,sha256=YCfJ3aYf6hOCvLW_grdfINdEqI4RXVS28VJ7tkZBFS8,47115
|
85
85
|
claude_mpm/cli/commands/doctor.py,sha256=nNKLZG3Qv_UsHNgrmetrWKgS7Pe2Jn5vq5aXyl60wKQ,7310
|
@@ -96,7 +96,7 @@ claude_mpm/cli/commands/mcp_setup_external.py,sha256=hfBHkaioNa0JRDhahNEc8agyrUw
|
|
96
96
|
claude_mpm/cli/commands/mcp_tool_commands.py,sha256=q17GzlFT3JiLTrDqwPO2tz1-fKmPO5QU449syTnKTz4,1283
|
97
97
|
claude_mpm/cli/commands/memory.py,sha256=O4T5HGL-Ob_QPt2dZHQvoOrVohnaDKrBjyngq1Mcv1w,26185
|
98
98
|
claude_mpm/cli/commands/monitor.py,sha256=Fjb68hf3dEwTFek2LV8Nh6iU0qEkY7qYlOn32IwNaNg,9566
|
99
|
-
claude_mpm/cli/commands/mpm_init.py,sha256=
|
99
|
+
claude_mpm/cli/commands/mpm_init.py,sha256=njopKyRBzBT_FL-3LrqishRSeKtIESehzWinhUqmRpM,65724
|
100
100
|
claude_mpm/cli/commands/mpm_init_handler.py,sha256=b1CSwZYJ89wMorKzPOKS-RVxOKR2kT9yv9KQLvKkd2U,3532
|
101
101
|
claude_mpm/cli/commands/run.py,sha256=PB2H55piOPTy4yo4OBgbUCjMlcz9K79wbwpxQVc9m5Q,48225
|
102
102
|
claude_mpm/cli/commands/search.py,sha256=_0qbUnop8v758MHsB0fAop8FVxwygD59tec_-iN7pLE,9806
|
@@ -118,7 +118,7 @@ claude_mpm/cli/parsers/debug_parser.py,sha256=F7MZdmiXiPfiIPMv21ZUqB2cMT8Ho1LDmp
|
|
118
118
|
claude_mpm/cli/parsers/mcp_parser.py,sha256=2j6ULhdu55Z2k_-Gu2QxIsFoTQFbDCEMSGePXSuPoQQ,6532
|
119
119
|
claude_mpm/cli/parsers/memory_parser.py,sha256=ZwCDxJEgp-w03L-1tZsWTgisiwamP42s424bA5bvDJc,4760
|
120
120
|
claude_mpm/cli/parsers/monitor_parser.py,sha256=PeoznSi_5Bw6THK_Espl8M20o6dKvvBSmFzAbovkaFQ,4920
|
121
|
-
claude_mpm/cli/parsers/mpm_init_parser.py,sha256=
|
121
|
+
claude_mpm/cli/parsers/mpm_init_parser.py,sha256=iTMd3RjnHzz89Q0O5Lr0MYI_vOUuXQOHHI6D-Zy8PUE,7823
|
122
122
|
claude_mpm/cli/parsers/run_parser.py,sha256=cs34qNonFZG8uYxTYEt0rXi2LcPz3pw8D8hxiywih6w,4927
|
123
123
|
claude_mpm/cli/parsers/search_parser.py,sha256=L8-65kndg-zutSKpzj-eCvTNkeySCZ-WlSHdhk7pEak,6916
|
124
124
|
claude_mpm/cli/parsers/tickets_parser.py,sha256=FYl-VNH7PrZzfZUCcjnf6F7g6JXnL8YDxwrmR5svIcg,6966
|
@@ -136,7 +136,7 @@ claude_mpm/commands/mpm-agents.md,sha256=JnYPJ-eWvIEEtiCB6iPu182P2xDBRvU3ArVXQ7h
|
|
136
136
|
claude_mpm/commands/mpm-config.md,sha256=79Eb-srRpEVV3HCHDHZc8SKec6_LVP6HbXDEVkZKLgw,2929
|
137
137
|
claude_mpm/commands/mpm-doctor.md,sha256=ut5LhFKVRw-2ecjMSPsnaTiRuFXa6Q9t-Wgl3CCnQvk,590
|
138
138
|
claude_mpm/commands/mpm-help.md,sha256=zfhpE0Fd-wW5zWmYYAMRMT-xYK8saqbw-HXRD7csJHI,2850
|
139
|
-
claude_mpm/commands/mpm-init.md,sha256=
|
139
|
+
claude_mpm/commands/mpm-init.md,sha256=5Jqb99qqJ_hQ_41lGmyTyDUhm7V7wQiLCYvksd3tZEo,10696
|
140
140
|
claude_mpm/commands/mpm-monitor.md,sha256=onTHf9Yac1KkdZdENtY2Q5jyw0A-vZLYgoKkPCtZLUY,12193
|
141
141
|
claude_mpm/commands/mpm-organize.md,sha256=T-ysjhwgfW9irjUj02vuY_1jeMdabO_zxcShyjmqsiM,10153
|
142
142
|
claude_mpm/commands/mpm-status.md,sha256=oaM4ybL4ffp55nkT9F0mp_5H4tF-wX9mbqK-LEKEqUU,1919
|
@@ -786,9 +786,9 @@ claude_mpm/utils/subprocess_utils.py,sha256=D0izRT8anjiUb_JG72zlJR_JAw1cDkb7kalN
|
|
786
786
|
claude_mpm/validation/__init__.py,sha256=YZhwE3mhit-lslvRLuwfX82xJ_k4haZeKmh4IWaVwtk,156
|
787
787
|
claude_mpm/validation/agent_validator.py,sha256=GprtAvu80VyMXcKGsK_VhYiXWA6BjKHv7O6HKx0AB9w,20917
|
788
788
|
claude_mpm/validation/frontmatter_validator.py,sha256=YpJlYNNYcV8u6hIOi3_jaRsDnzhbcQpjCBE6eyBKaFY,7076
|
789
|
-
claude_mpm-4.7.
|
790
|
-
claude_mpm-4.7.
|
791
|
-
claude_mpm-4.7.
|
792
|
-
claude_mpm-4.7.
|
793
|
-
claude_mpm-4.7.
|
794
|
-
claude_mpm-4.7.
|
789
|
+
claude_mpm-4.7.9.dist-info/licenses/LICENSE,sha256=lpaivOlPuBZW1ds05uQLJJswy8Rp_HMNieJEbFlqvLk,1072
|
790
|
+
claude_mpm-4.7.9.dist-info/METADATA,sha256=CKRPRgDYQjMEhisRDRh4swunx7DxHsVA022PufJ0t7Q,17517
|
791
|
+
claude_mpm-4.7.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
792
|
+
claude_mpm-4.7.9.dist-info/entry_points.txt,sha256=Vlw3GNi-OtTpKSrez04iNrPmxNxYDpIWxmJCxiZ5Tx8,526
|
793
|
+
claude_mpm-4.7.9.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
|
794
|
+
claude_mpm-4.7.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|