claude-mpm 4.10.0__py3-none-any.whl → 4.11.1__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/mpm_init.py +304 -1
- claude_mpm/cli/commands/mpm_init_handler.py +30 -1
- claude_mpm/cli/parsers/mpm_init_parser.py +60 -0
- claude_mpm/commands/mpm-init.md +138 -10
- claude_mpm/storage/state_storage.py +15 -15
- claude_mpm/utils/git_analyzer.py +305 -0
- {claude_mpm-4.10.0.dist-info → claude_mpm-4.11.1.dist-info}/METADATA +1 -1
- {claude_mpm-4.10.0.dist-info → claude_mpm-4.11.1.dist-info}/RECORD +13 -12
- {claude_mpm-4.10.0.dist-info → claude_mpm-4.11.1.dist-info}/WHEEL +0 -0
- {claude_mpm-4.10.0.dist-info → claude_mpm-4.11.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.10.0.dist-info → claude_mpm-4.11.1.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.10.0.dist-info → claude_mpm-4.11.1.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
4.
|
1
|
+
4.11.1
|
@@ -1464,6 +1464,198 @@ preserving valuable project-specific information while refreshing standard secti
|
|
1464
1464
|
logger.error(f"Initialization failed: {e}")
|
1465
1465
|
return {"status": "error", "message": str(e)}
|
1466
1466
|
|
1467
|
+
def handle_context(
|
1468
|
+
self,
|
1469
|
+
session_id: Optional[str] = None,
|
1470
|
+
list_sessions: bool = False,
|
1471
|
+
days: int = 7,
|
1472
|
+
) -> Dict[str, Any]:
|
1473
|
+
"""
|
1474
|
+
Provide intelligent context for resuming work based on git history.
|
1475
|
+
|
1476
|
+
Analyzes recent commits to identify:
|
1477
|
+
- Active work streams (what was being worked on)
|
1478
|
+
- Intent and motivation (why this work)
|
1479
|
+
- Risks and blockers
|
1480
|
+
- Recommended next actions
|
1481
|
+
|
1482
|
+
This delegates to Research agent for deep analysis.
|
1483
|
+
|
1484
|
+
Args:
|
1485
|
+
session_id: Unused parameter (for compatibility)
|
1486
|
+
list_sessions: Unused parameter (for compatibility)
|
1487
|
+
days: Number of days of git history to analyze (default: 7)
|
1488
|
+
|
1489
|
+
Returns:
|
1490
|
+
Dict containing context result
|
1491
|
+
"""
|
1492
|
+
from claude_mpm.utils.git_analyzer import analyze_recent_activity
|
1493
|
+
|
1494
|
+
# 1. Analyze git history
|
1495
|
+
console.print(f"\n🔍 Analyzing last {days} days of git history...\n")
|
1496
|
+
git_analysis = analyze_recent_activity(
|
1497
|
+
repo_path=str(self.project_path), days=days, max_commits=50
|
1498
|
+
)
|
1499
|
+
|
1500
|
+
if git_analysis.get("error"):
|
1501
|
+
console.print(
|
1502
|
+
f"[yellow]⚠️ Could not analyze git history: {git_analysis['error']}[/yellow]"
|
1503
|
+
)
|
1504
|
+
console.print(
|
1505
|
+
"[dim]Ensure this is a git repository with commit history.[/dim]\n"
|
1506
|
+
)
|
1507
|
+
return {
|
1508
|
+
"status": "error",
|
1509
|
+
"message": git_analysis["error"],
|
1510
|
+
}
|
1511
|
+
|
1512
|
+
if not git_analysis.get("has_activity"):
|
1513
|
+
console.print(
|
1514
|
+
f"[yellow]⚠️ No git activity found in the last {days} days.[/yellow]"
|
1515
|
+
)
|
1516
|
+
console.print("[dim]Try increasing the --days parameter.[/dim]\n")
|
1517
|
+
return {
|
1518
|
+
"status": "error",
|
1519
|
+
"message": f"No git activity in last {days} days",
|
1520
|
+
}
|
1521
|
+
|
1522
|
+
# 2. Build Research delegation prompt
|
1523
|
+
research_prompt = self._build_research_context_prompt(git_analysis, days)
|
1524
|
+
|
1525
|
+
# 3. Display prompt for PM to delegate
|
1526
|
+
console.print("\n" + "=" * 80)
|
1527
|
+
console.print("📋 DELEGATE TO RESEARCH AGENT:")
|
1528
|
+
console.print("=" * 80 + "\n")
|
1529
|
+
console.print(research_prompt)
|
1530
|
+
console.print("\n" + "=" * 80 + "\n")
|
1531
|
+
|
1532
|
+
return {
|
1533
|
+
"status": "context_ready",
|
1534
|
+
"git_analysis": git_analysis,
|
1535
|
+
"research_prompt": research_prompt,
|
1536
|
+
"recommendation": "PM should delegate this prompt to Research agent",
|
1537
|
+
}
|
1538
|
+
|
1539
|
+
def _build_research_context_prompt(
|
1540
|
+
self, git_analysis: Dict[str, Any], days: int
|
1541
|
+
) -> str:
|
1542
|
+
"""Build structured Research agent delegation prompt from git analysis."""
|
1543
|
+
|
1544
|
+
# Extract key data
|
1545
|
+
commits = git_analysis.get("commits", [])
|
1546
|
+
branches = git_analysis.get("branches", [])
|
1547
|
+
contributors = git_analysis.get("contributors", {})
|
1548
|
+
file_changes = git_analysis.get("file_changes", {})
|
1549
|
+
|
1550
|
+
# Build prompt following Prompt Engineer's template
|
1551
|
+
prompt = f"""# Project Context Analysis Mission
|
1552
|
+
|
1553
|
+
You are Research agent analyzing git history to provide PM with intelligent project context for resuming work.
|
1554
|
+
|
1555
|
+
## Analysis Scope
|
1556
|
+
- **Time Range**: Last {days} days
|
1557
|
+
- **Commits Analyzed**: {len(commits)} commits
|
1558
|
+
- **Branches**: {', '.join(branches[:5]) if branches else 'main'}
|
1559
|
+
- **Contributors**: {', '.join(contributors.keys()) if contributors else 'Unknown'}
|
1560
|
+
|
1561
|
+
## Your Mission
|
1562
|
+
|
1563
|
+
Analyze git history to answer these questions for PM:
|
1564
|
+
|
1565
|
+
1. **What was being worked on?** (Active work streams)
|
1566
|
+
2. **Why was this work happening?** (Intent and motivation)
|
1567
|
+
3. **What's the natural next step?** (Continuation recommendations)
|
1568
|
+
4. **What needs attention?** (Risks, stalls, conflicts)
|
1569
|
+
|
1570
|
+
## Git Data Provided
|
1571
|
+
|
1572
|
+
### Recent Commits ({min(len(commits), 10)} most recent):
|
1573
|
+
"""
|
1574
|
+
|
1575
|
+
# Add recent commits
|
1576
|
+
for commit in commits[:10]:
|
1577
|
+
author = commit.get("author", "Unknown")
|
1578
|
+
timestamp = commit.get("timestamp", "Unknown date")
|
1579
|
+
message = commit.get("message", "No message")
|
1580
|
+
files = commit.get("files", [])
|
1581
|
+
|
1582
|
+
prompt += f"\n- **{timestamp}** by {author}"
|
1583
|
+
prompt += f"\n {message}"
|
1584
|
+
prompt += f"\n Files changed: {len(files)}\n"
|
1585
|
+
|
1586
|
+
# Add file change summary
|
1587
|
+
if file_changes:
|
1588
|
+
# Sort by modifications count
|
1589
|
+
sorted_files = sorted(
|
1590
|
+
file_changes.items(),
|
1591
|
+
key=lambda x: x[1].get("modifications", 0),
|
1592
|
+
reverse=True,
|
1593
|
+
)
|
1594
|
+
prompt += "\n### Most Changed Files:\n"
|
1595
|
+
for file_path, file_data in sorted_files[:10]:
|
1596
|
+
modifications = file_data.get("modifications", 0)
|
1597
|
+
file_contributors = file_data.get("contributors", [])
|
1598
|
+
prompt += f"- {file_path}: {modifications} changes ({len(file_contributors)} contributor{'s' if len(file_contributors) != 1 else ''})\n"
|
1599
|
+
|
1600
|
+
# Add contributor summary
|
1601
|
+
if contributors:
|
1602
|
+
prompt += "\n### Contributors:\n"
|
1603
|
+
sorted_contributors = sorted(
|
1604
|
+
contributors.items(),
|
1605
|
+
key=lambda x: x[1].get("commits", 0),
|
1606
|
+
reverse=True,
|
1607
|
+
)
|
1608
|
+
for name, info in sorted_contributors[:5]:
|
1609
|
+
commit_count = info.get("commits", 0)
|
1610
|
+
prompt += f"- {name}: {commit_count} commit{'s' if commit_count != 1 else ''}\n"
|
1611
|
+
|
1612
|
+
# Add analysis instructions
|
1613
|
+
prompt += """
|
1614
|
+
|
1615
|
+
## Analysis Instructions
|
1616
|
+
|
1617
|
+
### Phase 1: Work Stream Identification
|
1618
|
+
Group related commits into thematic work streams. For each stream:
|
1619
|
+
- **Name**: Infer from commit messages (e.g., "Authentication refactor")
|
1620
|
+
- **Status**: ongoing/completed/stalled
|
1621
|
+
- **Commits**: Count of commits in this stream
|
1622
|
+
- **Intent**: WHY this work (from commit bodies/messages)
|
1623
|
+
- **Key Files**: Most changed files in this stream
|
1624
|
+
|
1625
|
+
### Phase 2: Risk Detection
|
1626
|
+
Identify:
|
1627
|
+
- **Stalled Work**: Work streams with no activity >3 days
|
1628
|
+
- **Anti-Patterns**: WIP commits, temp commits, debug commits
|
1629
|
+
- **Documentation Lag**: Code changes without doc updates
|
1630
|
+
- **Conflicts**: Merge conflicts or divergent branches
|
1631
|
+
|
1632
|
+
### Phase 3: Recommendations
|
1633
|
+
Based on analysis:
|
1634
|
+
1. **Primary Focus**: Most active/recent work to continue
|
1635
|
+
2. **Quick Wins**: Small tasks that could be finished
|
1636
|
+
3. **Blockers**: Issues preventing progress
|
1637
|
+
4. **Next Steps**: Logical continuation points
|
1638
|
+
|
1639
|
+
## Output Format
|
1640
|
+
|
1641
|
+
Provide a clear markdown summary with:
|
1642
|
+
|
1643
|
+
1. **Active Work Streams** (What was being worked on)
|
1644
|
+
2. **Intent Summary** (Why this work matters)
|
1645
|
+
3. **Risks Detected** (What needs attention)
|
1646
|
+
4. **Recommended Next Actions** (What to work on)
|
1647
|
+
|
1648
|
+
Keep it concise (<1000 words) but actionable.
|
1649
|
+
|
1650
|
+
## Success Criteria
|
1651
|
+
- Work streams accurately reflect development themes
|
1652
|
+
- Intent captures the "why" not just "what"
|
1653
|
+
- Recommendations are specific and actionable
|
1654
|
+
- Risks are prioritized by impact
|
1655
|
+
"""
|
1656
|
+
|
1657
|
+
return prompt
|
1658
|
+
|
1467
1659
|
def _display_results(self, result: Dict, verbose: bool):
|
1468
1660
|
"""Display initialization results."""
|
1469
1661
|
if result["status"] == "success":
|
@@ -1500,7 +1692,7 @@ preserving valuable project-specific information while refreshing standard secti
|
|
1500
1692
|
self.display.display_success_panel("Success", success_content)
|
1501
1693
|
|
1502
1694
|
|
1503
|
-
@click.
|
1695
|
+
@click.group(name="mpm-init", invoke_without_command=True)
|
1504
1696
|
@click.option(
|
1505
1697
|
"--project-type",
|
1506
1698
|
type=click.Choice(
|
@@ -1589,7 +1781,9 @@ preserving valuable project-specific information while refreshing standard secti
|
|
1589
1781
|
required=False,
|
1590
1782
|
default=".",
|
1591
1783
|
)
|
1784
|
+
@click.pass_context
|
1592
1785
|
def mpm_init(
|
1786
|
+
ctx,
|
1593
1787
|
project_type,
|
1594
1788
|
framework,
|
1595
1789
|
force,
|
@@ -1619,18 +1813,27 @@ def mpm_init(
|
|
1619
1813
|
- Optimize for AI agent understanding
|
1620
1814
|
- Perform AST analysis for enhanced developer documentation
|
1621
1815
|
|
1816
|
+
Context Management:
|
1817
|
+
- resume: Analyze git history to provide context for resuming work
|
1818
|
+
- --catchup: Show recent commit history for PM context
|
1819
|
+
|
1622
1820
|
Update Mode:
|
1623
1821
|
When CLAUDE.md exists, the command offers to update rather than recreate,
|
1624
1822
|
preserving custom content while refreshing standard sections.
|
1625
1823
|
|
1626
1824
|
Examples:
|
1627
1825
|
claude-mpm mpm-init # Initialize/update current directory
|
1826
|
+
claude-mpm mpm-init --catchup # Show recent git history for context
|
1628
1827
|
claude-mpm mpm-init --review # Review project state without changes
|
1629
1828
|
claude-mpm mpm-init --update # Force update mode
|
1630
1829
|
claude-mpm mpm-init --organize # Organize misplaced files
|
1631
1830
|
claude-mpm mpm-init --project-type web # Initialize as web project
|
1632
1831
|
claude-mpm mpm-init /path/to/project --force # Force reinitialize project
|
1633
1832
|
"""
|
1833
|
+
# If a subcommand is being invoked, don't run the main command
|
1834
|
+
if ctx.invoked_subcommand is not None:
|
1835
|
+
return
|
1836
|
+
|
1634
1837
|
try:
|
1635
1838
|
# Create command instance
|
1636
1839
|
command = MPMInitCommand(Path(project_path))
|
@@ -1668,5 +1871,105 @@ def mpm_init(
|
|
1668
1871
|
sys.exit(1)
|
1669
1872
|
|
1670
1873
|
|
1874
|
+
@mpm_init.command(name="context")
|
1875
|
+
@click.option(
|
1876
|
+
"--session-id",
|
1877
|
+
"-i",
|
1878
|
+
type=str,
|
1879
|
+
help="Unused (for compatibility) - will be removed in future version",
|
1880
|
+
)
|
1881
|
+
@click.option(
|
1882
|
+
"--days",
|
1883
|
+
type=int,
|
1884
|
+
default=7,
|
1885
|
+
help="Number of days of git history to analyze (default: 7)",
|
1886
|
+
)
|
1887
|
+
@click.argument(
|
1888
|
+
"project_path",
|
1889
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
1890
|
+
required=False,
|
1891
|
+
default=".",
|
1892
|
+
)
|
1893
|
+
def context_command(session_id, days, project_path):
|
1894
|
+
"""
|
1895
|
+
Provide intelligent context for resuming work based on git history.
|
1896
|
+
|
1897
|
+
Analyzes recent git history and generates a Research agent delegation
|
1898
|
+
prompt for intelligent project context reconstruction.
|
1899
|
+
|
1900
|
+
Examples:
|
1901
|
+
claude-mpm mpm-init context # Analyze last 7 days
|
1902
|
+
claude-mpm mpm-init context --days 14 # Analyze last 14 days
|
1903
|
+
claude-mpm mpm-init context --days 30 # Analyze last 30 days
|
1904
|
+
|
1905
|
+
Note: 'resume' is deprecated, use 'context' instead.
|
1906
|
+
"""
|
1907
|
+
try:
|
1908
|
+
command = MPMInitCommand(Path(project_path))
|
1909
|
+
|
1910
|
+
result = command.handle_context(session_id=session_id, days=days)
|
1911
|
+
|
1912
|
+
if result["status"] == "success" or result["status"] == "context_ready":
|
1913
|
+
sys.exit(0)
|
1914
|
+
else:
|
1915
|
+
sys.exit(1)
|
1916
|
+
|
1917
|
+
except KeyboardInterrupt:
|
1918
|
+
console.print("\n[yellow]Context analysis cancelled by user[/yellow]")
|
1919
|
+
sys.exit(130)
|
1920
|
+
except Exception as e:
|
1921
|
+
console.print(f"[red]Context analysis failed: {e}[/red]")
|
1922
|
+
sys.exit(1)
|
1923
|
+
|
1924
|
+
|
1925
|
+
# Add deprecated 'resume' alias for backward compatibility
|
1926
|
+
@mpm_init.command(name="resume", hidden=True)
|
1927
|
+
@click.option(
|
1928
|
+
"--session-id",
|
1929
|
+
"-i",
|
1930
|
+
type=str,
|
1931
|
+
help="Unused (for compatibility) - will be removed in future version",
|
1932
|
+
)
|
1933
|
+
@click.option(
|
1934
|
+
"--days",
|
1935
|
+
type=int,
|
1936
|
+
default=7,
|
1937
|
+
help="Number of days of git history to analyze (default: 7)",
|
1938
|
+
)
|
1939
|
+
@click.argument(
|
1940
|
+
"project_path",
|
1941
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
1942
|
+
required=False,
|
1943
|
+
default=".",
|
1944
|
+
)
|
1945
|
+
def resume_session(session_id, days, project_path):
|
1946
|
+
"""
|
1947
|
+
[DEPRECATED] Use 'context' instead.
|
1948
|
+
|
1949
|
+
This command is deprecated and will be removed in a future version.
|
1950
|
+
Please use 'claude-mpm mpm-init context' instead.
|
1951
|
+
"""
|
1952
|
+
console.print(
|
1953
|
+
"[yellow]⚠️ Warning: 'resume' is deprecated. Use 'context' instead.[/yellow]"
|
1954
|
+
)
|
1955
|
+
console.print("[dim]Run: claude-mpm mpm-init context[/dim]\n")
|
1956
|
+
|
1957
|
+
try:
|
1958
|
+
command = MPMInitCommand(Path(project_path))
|
1959
|
+
result = command.handle_context(session_id=session_id, days=days)
|
1960
|
+
|
1961
|
+
if result["status"] == "success" or result["status"] == "context_ready":
|
1962
|
+
sys.exit(0)
|
1963
|
+
else:
|
1964
|
+
sys.exit(1)
|
1965
|
+
|
1966
|
+
except KeyboardInterrupt:
|
1967
|
+
console.print("\n[yellow]Context analysis cancelled by user[/yellow]")
|
1968
|
+
sys.exit(130)
|
1969
|
+
except Exception as e:
|
1970
|
+
console.print(f"[red]Context analysis failed: {e}[/red]")
|
1971
|
+
sys.exit(1)
|
1972
|
+
|
1973
|
+
|
1671
1974
|
# Export for CLI registration
|
1672
1975
|
__all__ = ["mpm_init"]
|
@@ -25,6 +25,36 @@ def manage_mpm_init(args):
|
|
25
25
|
# Import the command implementation
|
26
26
|
from .mpm_init import MPMInitCommand
|
27
27
|
|
28
|
+
# Handle context subcommands
|
29
|
+
subcommand = getattr(args, "subcommand", None)
|
30
|
+
|
31
|
+
if subcommand in ("context", "resume"):
|
32
|
+
# Show deprecation warning for 'resume'
|
33
|
+
if subcommand == "resume":
|
34
|
+
console.print(
|
35
|
+
"[yellow]⚠️ Warning: 'resume' is deprecated. Use 'context' instead.[/yellow]"
|
36
|
+
)
|
37
|
+
console.print("[dim]Run: claude-mpm mpm-init context[/dim]\n")
|
38
|
+
|
39
|
+
# Get project path
|
40
|
+
project_path = (
|
41
|
+
Path(args.project_path) if hasattr(args, "project_path") else Path.cwd()
|
42
|
+
)
|
43
|
+
|
44
|
+
# Create command instance
|
45
|
+
command = MPMInitCommand(project_path)
|
46
|
+
|
47
|
+
# Handle context with optional session ID and days
|
48
|
+
result = command.handle_context(
|
49
|
+
session_id=getattr(args, "session_id", None),
|
50
|
+
days=getattr(args, "days", 7),
|
51
|
+
)
|
52
|
+
|
53
|
+
# Return appropriate exit code
|
54
|
+
if result.get("status") in ("success", "context_ready"):
|
55
|
+
return 0
|
56
|
+
return 1
|
57
|
+
|
28
58
|
# Handle special flags
|
29
59
|
if getattr(args, "list_templates", False):
|
30
60
|
# List available templates
|
@@ -55,7 +85,6 @@ def manage_mpm_init(args):
|
|
55
85
|
"framework": getattr(args, "framework", None),
|
56
86
|
"force": getattr(args, "force", False),
|
57
87
|
"verbose": getattr(args, "verbose", False),
|
58
|
-
"use_venv": getattr(args, "use_venv", False),
|
59
88
|
"ast_analysis": getattr(args, "ast_analysis", True),
|
60
89
|
"update_mode": getattr(args, "update", False),
|
61
90
|
"review_only": getattr(args, "review", False),
|
@@ -31,6 +31,7 @@ def add_mpm_init_subparser(subparsers: Any) -> None:
|
|
31
31
|
epilog=(
|
32
32
|
"Examples:\n"
|
33
33
|
" claude-mpm mpm-init # Initialize/update current directory\n"
|
34
|
+
" claude-mpm mpm-init --catchup # Show recent git history for context\n"
|
34
35
|
" claude-mpm mpm-init --review # Review project state without changes\n"
|
35
36
|
" claude-mpm mpm-init --update # Update existing CLAUDE.md\n"
|
36
37
|
" claude-mpm mpm-init --quick-update # Quick update based on recent git activity\n"
|
@@ -205,5 +206,64 @@ def add_mpm_init_subparser(subparsers: Any) -> None:
|
|
205
206
|
help="Path to project directory (default: current directory)",
|
206
207
|
)
|
207
208
|
|
209
|
+
# Add subparsers for context commands
|
210
|
+
subcommands = mpm_init_parser.add_subparsers(
|
211
|
+
dest="subcommand",
|
212
|
+
title="context management",
|
213
|
+
description="Commands for managing project context",
|
214
|
+
)
|
215
|
+
|
216
|
+
# Context subcommand (primary name)
|
217
|
+
context_parser = subcommands.add_parser(
|
218
|
+
"context",
|
219
|
+
help="Provide intelligent context for resuming work",
|
220
|
+
description="Analyze git history to provide context for resuming work",
|
221
|
+
epilog="Note: 'resume' is deprecated, use 'context' instead",
|
222
|
+
)
|
223
|
+
context_parser.add_argument(
|
224
|
+
"--session-id",
|
225
|
+
"-i",
|
226
|
+
type=str,
|
227
|
+
help="Unused (for compatibility) - will be removed in future version",
|
228
|
+
)
|
229
|
+
context_parser.add_argument(
|
230
|
+
"--days",
|
231
|
+
type=int,
|
232
|
+
default=7,
|
233
|
+
help="Number of days of git history to analyze (default: 7)",
|
234
|
+
)
|
235
|
+
context_parser.add_argument(
|
236
|
+
"project_path",
|
237
|
+
nargs="?",
|
238
|
+
default=".",
|
239
|
+
help="Path to project directory (default: current directory)",
|
240
|
+
)
|
241
|
+
|
242
|
+
# Resume subcommand (deprecated alias for backward compatibility)
|
243
|
+
resume_parser = subcommands.add_parser(
|
244
|
+
"resume",
|
245
|
+
help="[DEPRECATED] Use 'context' instead",
|
246
|
+
description="[DEPRECATED] This command is deprecated. Use 'context' instead.\n\n"
|
247
|
+
"Analyze git history to provide context for resuming work",
|
248
|
+
)
|
249
|
+
resume_parser.add_argument(
|
250
|
+
"--session-id",
|
251
|
+
"-i",
|
252
|
+
type=str,
|
253
|
+
help="Unused (for compatibility) - will be removed in future version",
|
254
|
+
)
|
255
|
+
resume_parser.add_argument(
|
256
|
+
"--days",
|
257
|
+
type=int,
|
258
|
+
default=7,
|
259
|
+
help="Number of days of git history to analyze (default: 7)",
|
260
|
+
)
|
261
|
+
resume_parser.add_argument(
|
262
|
+
"project_path",
|
263
|
+
nargs="?",
|
264
|
+
default=".",
|
265
|
+
help="Path to project directory (default: current directory)",
|
266
|
+
)
|
267
|
+
|
208
268
|
# Set the command handler
|
209
269
|
mpm_init_parser.set_defaults(command="mpm-init")
|
claude_mpm/commands/mpm-init.md
CHANGED
@@ -7,6 +7,9 @@ Initialize or intelligently update your project for optimal use with Claude Code
|
|
7
7
|
```
|
8
8
|
/mpm-init # Auto-detects and offers update or create
|
9
9
|
/mpm-init update # Lightweight update based on recent git activity
|
10
|
+
/mpm-init context # Intelligent context analysis from git history
|
11
|
+
/mpm-init context --days 14 # Analyze last 14 days of git history
|
12
|
+
/mpm-init catchup # Quick commit history display (no analysis)
|
10
13
|
/mpm-init --review # Review project state without changes
|
11
14
|
/mpm-init --update # Full update of existing CLAUDE.md
|
12
15
|
/mpm-init --organize # Organize project structure
|
@@ -17,7 +20,11 @@ Initialize or intelligently update your project for optimal use with Claude Code
|
|
17
20
|
|
18
21
|
## Description
|
19
22
|
|
20
|
-
This command
|
23
|
+
This command has two primary modes:
|
24
|
+
- **Project initialization/updates**: Delegates to the Agentic Coder Optimizer agent for documentation, tooling, and workflow setup
|
25
|
+
- **Context analysis** (context/catchup): Provides intelligent project context from git history for resuming work
|
26
|
+
|
27
|
+
**Note**: The `resume` subcommand is deprecated. Use `context` instead. The `resume` command still works for backward compatibility but will be removed in a future version.
|
21
28
|
|
22
29
|
**Quick Update Mode**: Running `/mpm-init update` performs a lightweight update focused on recent git activity. It analyzes recent commits, generates an activity report, and updates documentation with minimal changes. Perfect for quick refreshes after development sprints.
|
23
30
|
|
@@ -54,6 +61,46 @@ This command delegates to the Agentic Coder Optimizer agent to establish clear,
|
|
54
61
|
- `--no-preserve-custom`: Don't preserve custom sections
|
55
62
|
- `--skip-archive`: Skip archiving existing files before updating
|
56
63
|
|
64
|
+
## Context Analysis
|
65
|
+
|
66
|
+
**Purpose**: Provide intelligent project context for resuming work by analyzing git history.
|
67
|
+
|
68
|
+
### Commands
|
69
|
+
|
70
|
+
#### `/mpm-init context` (Primary)
|
71
|
+
```bash
|
72
|
+
/mpm-init context # Analyze last 7 days of git history
|
73
|
+
/mpm-init context --days 14 # Analyze last 14 days
|
74
|
+
```
|
75
|
+
|
76
|
+
Analyzes recent git commits to identify:
|
77
|
+
- **Active work streams**: What was being worked on (themes from commit patterns)
|
78
|
+
- **Intent and motivation**: Why this work matters (from commit messages)
|
79
|
+
- **Risks and blockers**: What needs attention (stalled work, conflicts, anti-patterns)
|
80
|
+
- **Recommended next actions**: What to work on next (logical continuations)
|
81
|
+
|
82
|
+
**How it works**:
|
83
|
+
1. Parses git history (default: last 7 days)
|
84
|
+
2. PM delegates to Research agent with structured prompt
|
85
|
+
3. Research analyzes work streams, intent, risks, recommendations
|
86
|
+
4. PM presents intelligent summary for seamless work resumption
|
87
|
+
|
88
|
+
**NOT session state**: This does NOT save/restore conversation state like Claude Code. Instead, it reconstructs project context from git history using conventional commits and commit message analysis.
|
89
|
+
|
90
|
+
#### `/mpm-init resume` [DEPRECATED]
|
91
|
+
Alias for `context`. Use `context` instead.
|
92
|
+
|
93
|
+
### `/mpm-init catchup` (Simple Git History)
|
94
|
+
```bash
|
95
|
+
/mpm-init catchup
|
96
|
+
```
|
97
|
+
|
98
|
+
Quick display of last 25 commits across all branches. No analysis - just raw git log output with authors and dates. Use this for quick "what happened recently?" checks.
|
99
|
+
|
100
|
+
**Distinction**:
|
101
|
+
- **catchup**: Quick commit history (instant, no analysis)
|
102
|
+
- **context**: Intelligent work resumption (10-30s, deep analysis)
|
103
|
+
|
57
104
|
## What This Command Does
|
58
105
|
|
59
106
|
### Auto-Detection (NEW)
|
@@ -146,22 +193,52 @@ Fast update based on recent 30-day git activity. Generates activity report and u
|
|
146
193
|
|
147
194
|
**Note**: Typing `/mpm-init update` executes `claude-mpm mpm-init --quick-update` automatically.
|
148
195
|
|
149
|
-
###
|
196
|
+
### Context Analysis (Intelligent Resumption)
|
197
|
+
|
198
|
+
Get intelligent context for resuming work based on git history analysis:
|
199
|
+
|
200
|
+
**Standard Context Analysis:**
|
201
|
+
```bash
|
202
|
+
/mpm-init context # Analyze last 7 days (default)
|
203
|
+
/mpm-init context --days 14 # Analyze last 14 days
|
204
|
+
/mpm-init context --days 30 # Analyze last 30 days
|
205
|
+
```
|
206
|
+
|
207
|
+
This provides intelligent analysis including:
|
208
|
+
- **Work stream identification** from commit patterns
|
209
|
+
- **Intent analysis** (why work was done)
|
210
|
+
- **Risk detection** (stalled work, conflicts, etc.)
|
211
|
+
- **Recommended next actions** for seamless continuation
|
212
|
+
|
213
|
+
**How it works:**
|
214
|
+
1. Parses git history (7 days default)
|
215
|
+
2. PM delegates to Research agent with structured prompt
|
216
|
+
3. Research agent provides deep analysis
|
217
|
+
4. PM presents intelligent summary
|
150
218
|
|
151
|
-
|
219
|
+
**NOT session state**: This reconstructs context from git history, not saved conversation state.
|
220
|
+
|
221
|
+
**Backward Compatibility:**
|
222
|
+
```bash
|
223
|
+
/mpm-init resume # Still works but deprecated
|
224
|
+
```
|
225
|
+
|
226
|
+
The old `resume` command redirects to `context` with a deprecation warning.
|
227
|
+
|
228
|
+
### Quick Git History (Catchup)
|
229
|
+
|
230
|
+
Display recent commit history without analysis:
|
152
231
|
|
153
232
|
```bash
|
154
233
|
/mpm-init catchup
|
155
234
|
```
|
156
235
|
|
157
|
-
|
236
|
+
Shows:
|
158
237
|
- Last 25 commits from all branches
|
159
|
-
- Author attribution
|
160
|
-
- Temporal context (WHEN)
|
238
|
+
- Author attribution and timestamps
|
161
239
|
- Contributor activity summary
|
162
|
-
- PM recommendations based on commit patterns
|
163
240
|
|
164
|
-
|
241
|
+
Use this for quick "what happened recently?" checks. For intelligent analysis, use `context` instead.
|
165
242
|
|
166
243
|
### Review Project State
|
167
244
|
```bash
|
@@ -210,6 +287,49 @@ Quick initialization without code analysis.
|
|
210
287
|
|
211
288
|
This command routes between different modes:
|
212
289
|
|
290
|
+
### Context Analysis Commands
|
291
|
+
|
292
|
+
**IMPORTANT**: Context analysis commands (`/mpm-init context`, `/mpm-init catchup`) have distinct behaviors:
|
293
|
+
|
294
|
+
**`/mpm-init context` - Delegates to PM**:
|
295
|
+
```bash
|
296
|
+
claude-mpm mpm-init context --days 7
|
297
|
+
```
|
298
|
+
|
299
|
+
This command delegates work to the PM framework:
|
300
|
+
1. Parses git history (7 days default)
|
301
|
+
2. PM constructs structured Research delegation prompt
|
302
|
+
3. PM presents prompt for Research agent to analyze
|
303
|
+
4. Research identifies work streams, intent, risks, recommendations
|
304
|
+
5. PM synthesizes for user
|
305
|
+
|
306
|
+
This is intelligent analysis requiring Research agent expertise.
|
307
|
+
|
308
|
+
**How the PM delegates to Research:**
|
309
|
+
The PM creates a delegation prompt that asks Research to analyze:
|
310
|
+
- **Work Stream Identification**: Groups related commits into themes
|
311
|
+
- **Intent Analysis**: Infers why work was done from commit messages
|
312
|
+
- **Risk Detection**: Identifies stalled work, conflicts, and blockers
|
313
|
+
- **Recommended Actions**: Suggests logical next steps for continuation
|
314
|
+
|
315
|
+
**`/mpm-init catchup` - Direct CLI execution**:
|
316
|
+
```bash
|
317
|
+
claude-mpm mpm-init catchup
|
318
|
+
```
|
319
|
+
|
320
|
+
This executes directly via CLI without agent delegation:
|
321
|
+
- Displays last 25 commits from all branches
|
322
|
+
- Shows authors, dates, commit messages
|
323
|
+
- Instant output (no analysis)
|
324
|
+
|
325
|
+
This is a simple git log display utility.
|
326
|
+
|
327
|
+
---
|
328
|
+
|
329
|
+
### Project Initialization/Update Commands
|
330
|
+
|
331
|
+
**IMPORTANT**: Standard initialization and update commands delegate to the Agentic Coder Optimizer agent.
|
332
|
+
|
213
333
|
**Quick Update Mode** (`/mpm-init update`):
|
214
334
|
```bash
|
215
335
|
claude-mpm mpm-init --quick-update
|
@@ -268,12 +388,20 @@ The command delegates to the Agentic Coder Optimizer agent which:
|
|
268
388
|
## Notes
|
269
389
|
|
270
390
|
- **Quick Update vs Full Update**: Use `/mpm-init update` for fast activity-based updates (30 days), or `/mpm-init --update` for comprehensive doc refresh
|
391
|
+
- **Context Analysis**: Use `/mpm-init context` to analyze git history and get intelligent resumption context from Research agent
|
392
|
+
- **Quick History**: Use `/mpm-init catchup` for instant commit history display without analysis
|
393
|
+
- **Deprecation Notice**: The `resume` command is deprecated. Use `context` instead. The old command still works but shows a warning.
|
271
394
|
- **Smart Mode**: Automatically detects existing CLAUDE.md and offers update vs recreate
|
272
395
|
- **Safe Updates**: Previous versions always archived before updating
|
273
396
|
- **Custom Content**: Your project-specific sections are preserved by default
|
274
|
-
- **Git Integration**: Analyzes recent commits to understand project evolution
|
397
|
+
- **Git Integration**: Analyzes recent commits to understand project evolution and provide work context
|
398
|
+
- **Backward Compatibility**: All existing `resume` commands redirect to `context` with deprecation warning
|
275
399
|
- **Argument Processing**: The slash command processes the `update` argument and routes to `--quick-update` flag
|
276
|
-
-
|
400
|
+
- **Agent Delegation**:
|
401
|
+
- Project initialization and updates use the Agentic Coder Optimizer agent
|
402
|
+
- Context analysis (`context`) delegates to PM, who coordinates with Research agent
|
403
|
+
- Simple git history (`catchup`) executes directly via CLI without agent delegation
|
404
|
+
- **NOT Session State**: Context analysis reconstructs project understanding from git history, not saved conversation state
|
277
405
|
- AST analysis is enabled by default for comprehensive documentation
|
278
406
|
- Priority rankings help AI agents focus on critical instructions first
|
279
407
|
- The holistic review ensures documentation quality and completeness
|
@@ -86,7 +86,7 @@ class StateStorage:
|
|
86
86
|
return True
|
87
87
|
|
88
88
|
except Exception as e:
|
89
|
-
|
89
|
+
logger.error(f"Failed to write JSON to {file_path}: {e}")
|
90
90
|
self.error_count += 1
|
91
91
|
return False
|
92
92
|
|
@@ -106,7 +106,7 @@ class StateStorage:
|
|
106
106
|
file_path = Path(file_path)
|
107
107
|
|
108
108
|
if not file_path.exists():
|
109
|
-
|
109
|
+
logger.debug(f"File not found: {file_path}")
|
110
110
|
return None
|
111
111
|
|
112
112
|
# Auto-detect compression
|
@@ -125,7 +125,7 @@ class StateStorage:
|
|
125
125
|
return data
|
126
126
|
|
127
127
|
except Exception as e:
|
128
|
-
|
128
|
+
logger.error(f"Failed to read JSON from {file_path}: {e}")
|
129
129
|
self.error_count += 1
|
130
130
|
return None
|
131
131
|
|
@@ -166,7 +166,7 @@ class StateStorage:
|
|
166
166
|
return True
|
167
167
|
|
168
168
|
except Exception as e:
|
169
|
-
|
169
|
+
logger.error(f"Failed to write pickle to {file_path}: {e}")
|
170
170
|
self.error_count += 1
|
171
171
|
return False
|
172
172
|
|
@@ -186,7 +186,7 @@ class StateStorage:
|
|
186
186
|
file_path = Path(file_path)
|
187
187
|
|
188
188
|
if not file_path.exists():
|
189
|
-
|
189
|
+
logger.debug(f"File not found: {file_path}")
|
190
190
|
return None
|
191
191
|
|
192
192
|
# Auto-detect compression
|
@@ -205,7 +205,7 @@ class StateStorage:
|
|
205
205
|
return data
|
206
206
|
|
207
207
|
except Exception as e:
|
208
|
-
|
208
|
+
logger.error(f"Failed to read pickle from {file_path}: {e}")
|
209
209
|
self.error_count += 1
|
210
210
|
return None
|
211
211
|
|
@@ -262,7 +262,7 @@ class StateStorage:
|
|
262
262
|
Path(temp_path).replace(file_path)
|
263
263
|
|
264
264
|
self.write_count += 1
|
265
|
-
|
265
|
+
logger.debug(f"Atomic write successful: {file_path}")
|
266
266
|
return True
|
267
267
|
|
268
268
|
finally:
|
@@ -271,7 +271,7 @@ class StateStorage:
|
|
271
271
|
Path(temp_path).unlink()
|
272
272
|
|
273
273
|
except Exception as e:
|
274
|
-
|
274
|
+
logger.error(f"Atomic write failed for {file_path}: {e}")
|
275
275
|
self.error_count += 1
|
276
276
|
return False
|
277
277
|
|
@@ -339,7 +339,7 @@ class StateStorage:
|
|
339
339
|
f.write(checksum)
|
340
340
|
|
341
341
|
except Exception as e:
|
342
|
-
|
342
|
+
logger.warning(f"Could not add checksum: {e}")
|
343
343
|
|
344
344
|
def verify_checksum(self, file_path: Union[str, Path]) -> bool:
|
345
345
|
"""Verify file checksum for integrity.
|
@@ -370,13 +370,13 @@ class StateStorage:
|
|
370
370
|
actual = hasher.hexdigest()
|
371
371
|
|
372
372
|
if actual != expected:
|
373
|
-
|
373
|
+
logger.error(f"Checksum mismatch for {file_path}")
|
374
374
|
return False
|
375
375
|
|
376
376
|
return True
|
377
377
|
|
378
378
|
except Exception as e:
|
379
|
-
|
379
|
+
logger.warning(f"Could not verify checksum: {e}")
|
380
380
|
return True # Assume valid if can't verify
|
381
381
|
|
382
382
|
def cleanup_temp_files(self) -> int:
|
@@ -396,7 +396,7 @@ class StateStorage:
|
|
396
396
|
if age > 3600:
|
397
397
|
temp_file.unlink()
|
398
398
|
cleaned += 1
|
399
|
-
|
399
|
+
logger.debug(f"Cleaned up temp file: {temp_file}")
|
400
400
|
except Exception:
|
401
401
|
pass
|
402
402
|
|
@@ -408,12 +408,12 @@ class StateStorage:
|
|
408
408
|
cleaned += 1
|
409
409
|
|
410
410
|
if cleaned > 0:
|
411
|
-
|
411
|
+
logger.info(f"Cleaned up {cleaned} temporary files")
|
412
412
|
|
413
413
|
return cleaned
|
414
414
|
|
415
415
|
except Exception as e:
|
416
|
-
|
416
|
+
logger.error(f"Error cleaning up temp files: {e}")
|
417
417
|
return 0
|
418
418
|
|
419
419
|
def get_storage_info(self) -> Dict[str, Any]:
|
@@ -447,7 +447,7 @@ class StateStorage:
|
|
447
447
|
}
|
448
448
|
|
449
449
|
except Exception as e:
|
450
|
-
|
450
|
+
logger.error(f"Error getting storage info: {e}")
|
451
451
|
return {"storage_directory": str(self.storage_dir), "error": str(e)}
|
452
452
|
|
453
453
|
|
@@ -0,0 +1,305 @@
|
|
1
|
+
"""
|
2
|
+
Git history analysis utilities for intelligent context reconstruction.
|
3
|
+
|
4
|
+
This module provides utilities to analyze git repository activity for
|
5
|
+
context reconstruction and project intelligence. Extracted from the
|
6
|
+
session management system to support git-based context approaches.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import subprocess
|
10
|
+
from pathlib import Path
|
11
|
+
from typing import Any, Dict, List, Optional
|
12
|
+
|
13
|
+
from claude_mpm.core.logging_utils import get_logger
|
14
|
+
|
15
|
+
logger = get_logger(__name__)
|
16
|
+
|
17
|
+
|
18
|
+
def analyze_recent_activity(
|
19
|
+
repo_path: str = ".", days: int = 7, max_commits: int = 50
|
20
|
+
) -> Dict[str, Any]:
|
21
|
+
"""
|
22
|
+
Analyze recent git activity for context reconstruction.
|
23
|
+
|
24
|
+
This function analyzes git history to provide comprehensive context about
|
25
|
+
recent project activity including commits, contributors, file changes,
|
26
|
+
and branch activity.
|
27
|
+
|
28
|
+
Args:
|
29
|
+
repo_path: Path to the git repository (default: current directory)
|
30
|
+
days: Number of days to look back (default: 7)
|
31
|
+
max_commits: Maximum number of commits to analyze (default: 50)
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
Dict containing:
|
35
|
+
- time_range: str - Description of analysis period
|
36
|
+
- commits: List[Dict] - Recent commits with metadata
|
37
|
+
- branches: List[str] - Active branches in the repository
|
38
|
+
- contributors: Dict[str, Dict] - Contributor statistics
|
39
|
+
- file_changes: Dict[str, Dict] - File change statistics
|
40
|
+
- has_activity: bool - Whether any activity was found
|
41
|
+
- error: Optional[str] - Error message if analysis failed
|
42
|
+
"""
|
43
|
+
repo_path_obj = Path(repo_path)
|
44
|
+
analysis = {
|
45
|
+
"time_range": f"last {days} days",
|
46
|
+
"commits": [],
|
47
|
+
"branches": [],
|
48
|
+
"contributors": {},
|
49
|
+
"file_changes": {},
|
50
|
+
"has_activity": False,
|
51
|
+
}
|
52
|
+
|
53
|
+
try:
|
54
|
+
# Get all branches
|
55
|
+
result = subprocess.run(
|
56
|
+
["git", "branch", "-a"],
|
57
|
+
cwd=str(repo_path_obj),
|
58
|
+
capture_output=True,
|
59
|
+
text=True,
|
60
|
+
check=True,
|
61
|
+
)
|
62
|
+
branches = [
|
63
|
+
line.strip().replace("* ", "").replace("remotes/origin/", "")
|
64
|
+
for line in result.stdout.strip().split("\n")
|
65
|
+
if line.strip()
|
66
|
+
]
|
67
|
+
analysis["branches"] = list(set(branches))
|
68
|
+
|
69
|
+
# Get recent commits from all branches
|
70
|
+
result = subprocess.run(
|
71
|
+
[
|
72
|
+
"git",
|
73
|
+
"log",
|
74
|
+
"--all",
|
75
|
+
f"--since={days} days ago",
|
76
|
+
f"--max-count={max_commits}",
|
77
|
+
"--format=%h|%an|%ae|%ai|%s",
|
78
|
+
"--name-status",
|
79
|
+
],
|
80
|
+
cwd=str(repo_path_obj),
|
81
|
+
capture_output=True,
|
82
|
+
text=True,
|
83
|
+
check=True,
|
84
|
+
)
|
85
|
+
|
86
|
+
if not result.stdout.strip():
|
87
|
+
return analysis
|
88
|
+
|
89
|
+
analysis["has_activity"] = True
|
90
|
+
|
91
|
+
# Parse commit log
|
92
|
+
commits = []
|
93
|
+
current_commit = None
|
94
|
+
file_changes = {}
|
95
|
+
|
96
|
+
for line in result.stdout.strip().split("\n"):
|
97
|
+
if not line.strip():
|
98
|
+
continue
|
99
|
+
|
100
|
+
if "|" in line:
|
101
|
+
# Commit line
|
102
|
+
if current_commit:
|
103
|
+
commits.append(current_commit)
|
104
|
+
|
105
|
+
parts = line.split("|", 4)
|
106
|
+
if len(parts) == 5:
|
107
|
+
sha, author, email, timestamp, message = parts
|
108
|
+
current_commit = {
|
109
|
+
"sha": sha,
|
110
|
+
"author": author,
|
111
|
+
"email": email,
|
112
|
+
"timestamp": timestamp,
|
113
|
+
"message": message,
|
114
|
+
"files": [],
|
115
|
+
}
|
116
|
+
|
117
|
+
# Track contributors
|
118
|
+
if author not in analysis["contributors"]:
|
119
|
+
analysis["contributors"][author] = {
|
120
|
+
"email": email,
|
121
|
+
"commits": 0,
|
122
|
+
}
|
123
|
+
analysis["contributors"][author]["commits"] += 1
|
124
|
+
# File change line
|
125
|
+
elif current_commit and "\t" in line:
|
126
|
+
parts = line.split("\t", 1)
|
127
|
+
if len(parts) == 2:
|
128
|
+
status, file_path = parts
|
129
|
+
current_commit["files"].append(
|
130
|
+
{"status": status, "path": file_path}
|
131
|
+
)
|
132
|
+
|
133
|
+
# Track file changes
|
134
|
+
if file_path not in file_changes:
|
135
|
+
file_changes[file_path] = {
|
136
|
+
"modifications": 0,
|
137
|
+
"contributors": set(),
|
138
|
+
}
|
139
|
+
file_changes[file_path]["modifications"] += 1
|
140
|
+
file_changes[file_path]["contributors"].add(
|
141
|
+
current_commit["author"]
|
142
|
+
)
|
143
|
+
|
144
|
+
# Add last commit
|
145
|
+
if current_commit:
|
146
|
+
commits.append(current_commit)
|
147
|
+
|
148
|
+
analysis["commits"] = commits
|
149
|
+
|
150
|
+
# Convert file changes to serializable format
|
151
|
+
analysis["file_changes"] = {
|
152
|
+
path: {
|
153
|
+
"modifications": info["modifications"],
|
154
|
+
"contributors": list(info["contributors"]),
|
155
|
+
}
|
156
|
+
for path, info in file_changes.items()
|
157
|
+
}
|
158
|
+
|
159
|
+
except subprocess.CalledProcessError as e:
|
160
|
+
logger.warning(f"Git command failed: {e}")
|
161
|
+
analysis["error"] = f"Git command failed: {e}"
|
162
|
+
except Exception as e:
|
163
|
+
logger.warning(f"Could not analyze recent activity: {e}")
|
164
|
+
analysis["error"] = str(e)
|
165
|
+
|
166
|
+
return analysis
|
167
|
+
|
168
|
+
|
169
|
+
def get_current_branch(repo_path: str = ".") -> Optional[str]:
|
170
|
+
"""
|
171
|
+
Get the current git branch name.
|
172
|
+
|
173
|
+
Args:
|
174
|
+
repo_path: Path to the git repository (default: current directory)
|
175
|
+
|
176
|
+
Returns:
|
177
|
+
Current branch name or None if not in a git repository
|
178
|
+
"""
|
179
|
+
try:
|
180
|
+
result = subprocess.run(
|
181
|
+
["git", "branch", "--show-current"],
|
182
|
+
cwd=str(Path(repo_path)),
|
183
|
+
capture_output=True,
|
184
|
+
text=True,
|
185
|
+
check=True,
|
186
|
+
)
|
187
|
+
return result.stdout.strip()
|
188
|
+
except Exception:
|
189
|
+
return None
|
190
|
+
|
191
|
+
|
192
|
+
def get_commits_since(since_sha: str, repo_path: str = ".") -> List[Dict[str, str]]:
|
193
|
+
"""
|
194
|
+
Get commits since a specific SHA.
|
195
|
+
|
196
|
+
Args:
|
197
|
+
since_sha: The SHA to get commits after
|
198
|
+
repo_path: Path to the git repository (default: current directory)
|
199
|
+
|
200
|
+
Returns:
|
201
|
+
List of commit dicts with sha, author, timestamp, and message
|
202
|
+
"""
|
203
|
+
try:
|
204
|
+
result = subprocess.run(
|
205
|
+
["git", "log", f"{since_sha}..HEAD", "--format=%h|%an|%ai|%s"],
|
206
|
+
cwd=str(Path(repo_path)),
|
207
|
+
capture_output=True,
|
208
|
+
text=True,
|
209
|
+
check=True,
|
210
|
+
)
|
211
|
+
|
212
|
+
commits = []
|
213
|
+
for line in result.stdout.strip().split("\n"):
|
214
|
+
if not line:
|
215
|
+
continue
|
216
|
+
parts = line.split("|", 3)
|
217
|
+
if len(parts) == 4:
|
218
|
+
sha, author, timestamp, message = parts
|
219
|
+
commits.append(
|
220
|
+
{
|
221
|
+
"sha": sha,
|
222
|
+
"author": author,
|
223
|
+
"timestamp": timestamp,
|
224
|
+
"message": message,
|
225
|
+
}
|
226
|
+
)
|
227
|
+
|
228
|
+
return commits
|
229
|
+
|
230
|
+
except Exception as e:
|
231
|
+
logger.warning(f"Could not get commits: {e}")
|
232
|
+
return []
|
233
|
+
|
234
|
+
|
235
|
+
def get_current_status(repo_path: str = ".") -> Dict[str, Any]:
|
236
|
+
"""
|
237
|
+
Get current git status.
|
238
|
+
|
239
|
+
Args:
|
240
|
+
repo_path: Path to the git repository (default: current directory)
|
241
|
+
|
242
|
+
Returns:
|
243
|
+
Dict with:
|
244
|
+
- clean: bool - Whether working directory is clean
|
245
|
+
- modified_files: List[str] - Modified files
|
246
|
+
- untracked_files: List[str] - Untracked files
|
247
|
+
"""
|
248
|
+
status = {"clean": True, "modified_files": [], "untracked_files": []}
|
249
|
+
|
250
|
+
try:
|
251
|
+
result = subprocess.run(
|
252
|
+
["git", "status", "--porcelain"],
|
253
|
+
cwd=str(Path(repo_path)),
|
254
|
+
capture_output=True,
|
255
|
+
text=True,
|
256
|
+
check=True,
|
257
|
+
)
|
258
|
+
|
259
|
+
modified_files = []
|
260
|
+
untracked_files = []
|
261
|
+
|
262
|
+
for line in result.stdout.strip().split("\n"):
|
263
|
+
if not line:
|
264
|
+
continue
|
265
|
+
status_code = line[:2]
|
266
|
+
file_path = line[3:]
|
267
|
+
|
268
|
+
if status_code.startswith("??"):
|
269
|
+
untracked_files.append(file_path)
|
270
|
+
else:
|
271
|
+
modified_files.append(file_path)
|
272
|
+
|
273
|
+
status = {
|
274
|
+
"clean": len(modified_files) == 0 and len(untracked_files) == 0,
|
275
|
+
"modified_files": modified_files,
|
276
|
+
"untracked_files": untracked_files,
|
277
|
+
}
|
278
|
+
|
279
|
+
except Exception as e:
|
280
|
+
logger.warning(f"Could not get status: {e}")
|
281
|
+
|
282
|
+
return status
|
283
|
+
|
284
|
+
|
285
|
+
def is_git_repository(repo_path: str = ".") -> bool:
|
286
|
+
"""
|
287
|
+
Check if the given path is a git repository.
|
288
|
+
|
289
|
+
Args:
|
290
|
+
repo_path: Path to check (default: current directory)
|
291
|
+
|
292
|
+
Returns:
|
293
|
+
True if the path is a git repository, False otherwise
|
294
|
+
"""
|
295
|
+
try:
|
296
|
+
result = subprocess.run(
|
297
|
+
["git", "rev-parse", "--git-dir"],
|
298
|
+
cwd=str(Path(repo_path)),
|
299
|
+
capture_output=True,
|
300
|
+
text=True,
|
301
|
+
check=False,
|
302
|
+
)
|
303
|
+
return result.returncode == 0
|
304
|
+
except Exception:
|
305
|
+
return False
|
@@ -1,5 +1,5 @@
|
|
1
1
|
claude_mpm/BUILD_NUMBER,sha256=9JfxhnDtr-8l3kCP2U5TVXSErptHoga8m7XA8zqgGOc,4
|
2
|
-
claude_mpm/VERSION,sha256
|
2
|
+
claude_mpm/VERSION,sha256=-pg3IRz2ArU7tcZ-4AUcxOirJEkowIcnQL1C8A2FelI,7
|
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=sLjJF6Kw7H4V9WWeaEYltM-77TgXqzEMX5vx4ukM5-0,5977
|
@@ -100,8 +100,8 @@ claude_mpm/cli/commands/mcp_setup_external.py,sha256=hfBHkaioNa0JRDhahNEc8agyrUw
|
|
100
100
|
claude_mpm/cli/commands/mcp_tool_commands.py,sha256=q17GzlFT3JiLTrDqwPO2tz1-fKmPO5QU449syTnKTz4,1283
|
101
101
|
claude_mpm/cli/commands/memory.py,sha256=O4T5HGL-Ob_QPt2dZHQvoOrVohnaDKrBjyngq1Mcv1w,26185
|
102
102
|
claude_mpm/cli/commands/monitor.py,sha256=Fjb68hf3dEwTFek2LV8Nh6iU0qEkY7qYlOn32IwNaNg,9566
|
103
|
-
claude_mpm/cli/commands/mpm_init.py,sha256=
|
104
|
-
claude_mpm/cli/commands/mpm_init_handler.py,sha256=
|
103
|
+
claude_mpm/cli/commands/mpm_init.py,sha256=oNEF_hwpu7LCvj06vqXbmVJpXm7yo4ebODyQcZN52nM,74346
|
104
|
+
claude_mpm/cli/commands/mpm_init_handler.py,sha256=FZRoo_zfEakLc_W1G_ej_bhxtaU6ybngyBP5GLxaKD4,4583
|
105
105
|
claude_mpm/cli/commands/run.py,sha256=PB2H55piOPTy4yo4OBgbUCjMlcz9K79wbwpxQVc9m5Q,48225
|
106
106
|
claude_mpm/cli/commands/search.py,sha256=alv6udvKcn-xkqeBlLuPRvfSDV1yxEX4n9mjjRT5uLM,16581
|
107
107
|
claude_mpm/cli/commands/tickets.py,sha256=kl2dklTBnG3Y4jUUJ_PcEVsTx4CtVJfkGWboWBx_mQM,21234
|
@@ -123,7 +123,7 @@ claude_mpm/cli/parsers/debug_parser.py,sha256=F7MZdmiXiPfiIPMv21ZUqB2cMT8Ho1LDmp
|
|
123
123
|
claude_mpm/cli/parsers/mcp_parser.py,sha256=2j6ULhdu55Z2k_-Gu2QxIsFoTQFbDCEMSGePXSuPoQQ,6532
|
124
124
|
claude_mpm/cli/parsers/memory_parser.py,sha256=ZwCDxJEgp-w03L-1tZsWTgisiwamP42s424bA5bvDJc,4760
|
125
125
|
claude_mpm/cli/parsers/monitor_parser.py,sha256=PeoznSi_5Bw6THK_Espl8M20o6dKvvBSmFzAbovkaFQ,4920
|
126
|
-
claude_mpm/cli/parsers/mpm_init_parser.py,sha256=
|
126
|
+
claude_mpm/cli/parsers/mpm_init_parser.py,sha256=EVyHc7KYpsyxhomZAUnlGS6kq43ix2wjrHLiM5Gf34E,9684
|
127
127
|
claude_mpm/cli/parsers/run_parser.py,sha256=cs34qNonFZG8uYxTYEt0rXi2LcPz3pw8D8hxiywih6w,4927
|
128
128
|
claude_mpm/cli/parsers/search_parser.py,sha256=L8-65kndg-zutSKpzj-eCvTNkeySCZ-WlSHdhk7pEak,6916
|
129
129
|
claude_mpm/cli/parsers/tickets_parser.py,sha256=FYl-VNH7PrZzfZUCcjnf6F7g6JXnL8YDxwrmR5svIcg,6966
|
@@ -141,7 +141,7 @@ claude_mpm/commands/mpm-agents.md,sha256=JnYPJ-eWvIEEtiCB6iPu182P2xDBRvU3ArVXQ7h
|
|
141
141
|
claude_mpm/commands/mpm-config.md,sha256=79Eb-srRpEVV3HCHDHZc8SKec6_LVP6HbXDEVkZKLgw,2929
|
142
142
|
claude_mpm/commands/mpm-doctor.md,sha256=ut5LhFKVRw-2ecjMSPsnaTiRuFXa6Q9t-Wgl3CCnQvk,590
|
143
143
|
claude_mpm/commands/mpm-help.md,sha256=zfhpE0Fd-wW5zWmYYAMRMT-xYK8saqbw-HXRD7csJHI,2850
|
144
|
-
claude_mpm/commands/mpm-init.md,sha256=
|
144
|
+
claude_mpm/commands/mpm-init.md,sha256=wwYHkToq8U5ALdhu8bDPygqAsKZ77aMaai7ZJC3oBqU,16054
|
145
145
|
claude_mpm/commands/mpm-monitor.md,sha256=onTHf9Yac1KkdZdENtY2Q5jyw0A-vZLYgoKkPCtZLUY,12193
|
146
146
|
claude_mpm/commands/mpm-organize.md,sha256=T-ysjhwgfW9irjUj02vuY_1jeMdabO_zxcShyjmqsiM,10153
|
147
147
|
claude_mpm/commands/mpm-status.md,sha256=oaM4ybL4ffp55nkT9F0mp_5H4tF-wX9mbqK-LEKEqUU,1919
|
@@ -766,7 +766,7 @@ claude_mpm/services/version_control/version_parser.py,sha256=DbNncYZKKy9--ZUCO9g
|
|
766
766
|
claude_mpm/services/visualization/__init__.py,sha256=cTtWxRi07rSLXQKVZa7SKZsYfj6nRouw7HVO85Towfg,401
|
767
767
|
claude_mpm/services/visualization/mermaid_generator.py,sha256=6QGgXtMg_N9rKeRn8wYs3IxuWGKdDVAwFMFWKfIw9s4,34277
|
768
768
|
claude_mpm/storage/__init__.py,sha256=DXnmee6iGqC6ctFLW7_Ty1cVCjYDFuCMkwO4EV0i25k,274
|
769
|
-
claude_mpm/storage/state_storage.py,sha256=
|
769
|
+
claude_mpm/storage/state_storage.py,sha256=6jEZ4z35MjtAuTTUdqETTy4_dbMMK8EN_C2kVa62PjY,16811
|
770
770
|
claude_mpm/tools/__init__.py,sha256=T3GuCYNAHtjVcKCeivY674PaDm48WX96AriQfTKUknY,347
|
771
771
|
claude_mpm/tools/__main__.py,sha256=MtpJoWA2HEmRE_XDsSjjto6DND7Ft0MZoiY4ivNHh5A,5714
|
772
772
|
claude_mpm/tools/code_tree_analyzer.py,sha256=BBkEuSVHd_P1jBlxpBfzsLqUyT01rid3r2_cQCBeGyQ,64818
|
@@ -787,6 +787,7 @@ claude_mpm/utils/environment_context.py,sha256=mCnRJqQLTyaAv-7M4bp9N9WqVgfSQb5xb
|
|
787
787
|
claude_mpm/utils/error_handler.py,sha256=RWL7DnXttJKCgYhevUm9XlMC33rEhX2CXo1IiCtwV4g,7969
|
788
788
|
claude_mpm/utils/file_utils.py,sha256=pv3MEKLsn4WIOra5JoHnCm_FaJbNcKMuS3EKuXAWyLc,7859
|
789
789
|
claude_mpm/utils/framework_detection.py,sha256=dCY-N0HzuYS9KBg1BjDw8mJnNb22JqmKBoodjTMzd5w,1183
|
790
|
+
claude_mpm/utils/git_analyzer.py,sha256=JZwahnhgPXsITRnq7jJBNK4or82bZjazQI-VGyXocP4,9323
|
790
791
|
claude_mpm/utils/import_migration_example.py,sha256=sKlV-apfkHDJzzez6uubHXjx91YF_5HPuzu2sLs_U0E,989
|
791
792
|
claude_mpm/utils/imports.py,sha256=IGcW0W6V-2jTmkVlO-l0Zodo3LRA3zkaumD9J2qn7dE,6654
|
792
793
|
claude_mpm/utils/log_cleanup.py,sha256=1d8K1V2uOFPGJJKFOk6E_AMj22sifAibOg-VQyGooWo,22301
|
@@ -797,9 +798,9 @@ claude_mpm/utils/subprocess_utils.py,sha256=D0izRT8anjiUb_JG72zlJR_JAw1cDkb7kalN
|
|
797
798
|
claude_mpm/validation/__init__.py,sha256=YZhwE3mhit-lslvRLuwfX82xJ_k4haZeKmh4IWaVwtk,156
|
798
799
|
claude_mpm/validation/agent_validator.py,sha256=GprtAvu80VyMXcKGsK_VhYiXWA6BjKHv7O6HKx0AB9w,20917
|
799
800
|
claude_mpm/validation/frontmatter_validator.py,sha256=YpJlYNNYcV8u6hIOi3_jaRsDnzhbcQpjCBE6eyBKaFY,7076
|
800
|
-
claude_mpm-4.
|
801
|
-
claude_mpm-4.
|
802
|
-
claude_mpm-4.
|
803
|
-
claude_mpm-4.
|
804
|
-
claude_mpm-4.
|
805
|
-
claude_mpm-4.
|
801
|
+
claude_mpm-4.11.1.dist-info/licenses/LICENSE,sha256=lpaivOlPuBZW1ds05uQLJJswy8Rp_HMNieJEbFlqvLk,1072
|
802
|
+
claude_mpm-4.11.1.dist-info/METADATA,sha256=Wgt4BuOeYfVIUCmhtlUQwLKtCF4RAC_AEnw1W0_5pmI,17967
|
803
|
+
claude_mpm-4.11.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
804
|
+
claude_mpm-4.11.1.dist-info/entry_points.txt,sha256=Vlw3GNi-OtTpKSrez04iNrPmxNxYDpIWxmJCxiZ5Tx8,526
|
805
|
+
claude_mpm-4.11.1.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
|
806
|
+
claude_mpm-4.11.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|