claude-mpm 5.4.55__py3-none-any.whl → 5.4.56__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 CHANGED
@@ -1 +1 @@
1
- 5.4.55
1
+ 5.4.56
claude_mpm/cli/startup.py CHANGED
@@ -990,22 +990,37 @@ def sync_remote_skills_on_startup():
990
990
 
991
991
  # Phase 2: Scan agents and save to configuration.yaml
992
992
  # This step populates configuration.yaml with agent-referenced skills
993
- # BUGFIX: Removed `if results["synced_count"] > 0` condition to ensure
994
- # agent_referenced is always populated, even when using cached skills.
995
- # Previous behavior: If skills were cached, agent scan was skipped,
996
- # leaving agent_referenced: [] empty, which prevented cleanup.
993
+ # CRITICAL: Always scan agents to populate agent_referenced, even when using cached skills.
994
+ # Without this, skill_filter=None causes ALL skills to deploy and NO cleanup to run.
997
995
  agents_dir = Path.cwd() / ".claude" / "agents"
998
996
 
999
- # Scan agents for skill requirements (always run, not just on sync)
997
+ # Scan agents for skill requirements (ALWAYS run to ensure cleanup works)
1000
998
  agent_skills = get_required_skills_from_agents(agents_dir)
999
+ logger.info(
1000
+ f"Agent scan found {len(agent_skills)} unique skills across deployed agents"
1001
+ )
1001
1002
 
1002
1003
  # Save to project-level configuration.yaml
1003
1004
  project_config_path = Path.cwd() / ".claude-mpm" / "configuration.yaml"
1004
1005
  save_agent_skills_to_config(list(agent_skills), project_config_path)
1006
+ logger.debug(
1007
+ f"Saved {len(agent_skills)} agent-referenced skills to {project_config_path}"
1008
+ )
1005
1009
 
1006
1010
  # Phase 3: Resolve which skills to deploy (user_defined or agent_referenced)
1007
1011
  skills_to_deploy, skill_source = get_skills_to_deploy(project_config_path)
1008
1012
 
1013
+ # CRITICAL DEBUG: Log deployment resolution to diagnose cleanup issues
1014
+ if skills_to_deploy:
1015
+ logger.info(
1016
+ f"Resolved {len(skills_to_deploy)} skills from {skill_source} (cleanup will run)"
1017
+ )
1018
+ else:
1019
+ logger.warning(
1020
+ f"No skills resolved from {skill_source} - will deploy ALL skills WITHOUT cleanup! "
1021
+ f"This may indicate agent_referenced is empty in configuration.yaml."
1022
+ )
1023
+
1009
1024
  # Phase 4: Apply profile filtering if active
1010
1025
  if active_profile and profile_manager.active_profile:
1011
1026
  # Filter skills based on profile
@@ -1078,6 +1093,7 @@ def sync_remote_skills_on_startup():
1078
1093
  deployed = deployment_result.get("deployed_count", 0)
1079
1094
  skipped = deployment_result.get("skipped_count", 0)
1080
1095
  filtered = deployment_result.get("filtered_count", 0)
1096
+ removed = deployment_result.get("removed_count", 0)
1081
1097
  total_available = deployed + skipped
1082
1098
 
1083
1099
  # Only show progress bar if there are skills to deploy
@@ -1107,16 +1123,25 @@ def sync_remote_skills_on_startup():
1107
1123
  "user override" if skill_source == "user_defined" else "from agents"
1108
1124
  )
1109
1125
 
1110
- if deployed > 0:
1126
+ # Build finish message with cleanup info
1127
+ if deployed > 0 or removed > 0:
1128
+ parts = []
1129
+ if deployed > 0:
1130
+ parts.append(f"{deployed} new")
1131
+ if skipped > 0:
1132
+ parts.append(f"{skipped} unchanged")
1133
+ if removed > 0:
1134
+ parts.append(f"{removed} removed")
1135
+
1136
+ status = ", ".join(parts)
1137
+
1111
1138
  if filtered > 0:
1112
1139
  deploy_progress.finish(
1113
- f"Complete: {deployed} new, {skipped} unchanged "
1114
- f"({total_available} {source_label}, {filtered} files in cache)"
1140
+ f"Complete: {status} ({total_available} {source_label}, {filtered} files in cache)"
1115
1141
  )
1116
1142
  else:
1117
1143
  deploy_progress.finish(
1118
- f"Complete: {deployed} new, {skipped} unchanged "
1119
- f"({total_available} skills {source_label} from {total_skill_count} files in cache)"
1144
+ f"Complete: {status} ({total_available} skills {source_label} from {total_skill_count} files in cache)"
1120
1145
  )
1121
1146
  elif filtered > 0:
1122
1147
  # Skills filtered means agents require fewer skills than available
@@ -1124,10 +1149,12 @@ def sync_remote_skills_on_startup():
1124
1149
  f"No skills needed ({source_label}, {total_skill_count} files in cache)"
1125
1150
  )
1126
1151
  else:
1127
- deploy_progress.finish(
1128
- f"Complete: {total_available} skills {source_label} "
1129
- f"({total_skill_count} files in cache)"
1130
- )
1152
+ # No changes - all skills already deployed
1153
+ msg = f"Complete: {total_available} skills {source_label}"
1154
+ if removed > 0:
1155
+ msg += f", {removed} removed"
1156
+ msg += f" ({total_skill_count} files in cache)"
1157
+ deploy_progress.finish(msg)
1131
1158
 
1132
1159
  # Log deployment errors if any
1133
1160
  from ..core.logger import get_logger
@@ -1244,61 +1271,64 @@ def show_skill_summary():
1244
1271
  Display skill availability summary on startup.
1245
1272
 
1246
1273
  WHY: Users should see at a glance how many skills are deployed and available
1247
- from collections, similar to the agent summary.
1274
+ from cache, similar to the agent summary showing "X deployed / Y cached".
1275
+
1276
+ DESIGN DECISION: Fast, non-blocking check that counts skills from:
1277
+ - Deployed skills: PROJECT-level .claude/skills/ directory
1278
+ - Cached skills: ~/.claude-mpm/cache/skills/ directory (from remote sources)
1248
1279
 
1249
- DESIGN DECISION: Fast, non-blocking check that counts skills from deployment
1250
- directory and collection repos. Shows "X installed (Y available)" format.
1280
+ Shows format: "✓ Skills: X deployed / Y cached"
1251
1281
  Failures are silent to avoid blocking startup.
1252
1282
  """
1253
1283
  try:
1254
1284
  from pathlib import Path
1255
1285
 
1256
- # Count deployed skills (installed)
1257
- skills_dir = Path.home() / ".claude" / "skills"
1258
- installed_count = 0
1259
- if skills_dir.exists():
1286
+ # Count deployed skills (PROJECT-level, not user-level)
1287
+ project_skills_dir = Path.cwd() / ".claude" / "skills"
1288
+ deployed_count = 0
1289
+ if project_skills_dir.exists():
1260
1290
  # Count directories with SKILL.md (excludes collection repos)
1261
1291
  # Exclude collection directories (obra-superpowers, etc.)
1262
1292
  skill_dirs = [
1263
1293
  d
1264
- for d in skills_dir.iterdir()
1294
+ for d in project_skills_dir.iterdir()
1265
1295
  if d.is_dir()
1266
1296
  and (d / "SKILL.md").exists()
1267
1297
  and not (d / ".git").exists() # Exclude collection repos
1268
1298
  ]
1269
- installed_count = len(skill_dirs)
1299
+ deployed_count = len(skill_dirs)
1270
1300
 
1271
- # Count available skills in collections
1272
- available_count = 0
1273
- if skills_dir.exists():
1274
- # Scan all collection directories (those with .git)
1275
- for collection_dir in skills_dir.iterdir():
1276
- if (
1277
- not collection_dir.is_dir()
1278
- or not (collection_dir / ".git").exists()
1279
- ):
1301
+ # Count cached skills (from remote sources, not deployed yet)
1302
+ # This matches the agent summary pattern: deployed vs cached
1303
+ cache_dir = Path.home() / ".claude-mpm" / "cache" / "skills"
1304
+ cached_count = 0
1305
+ if cache_dir.exists():
1306
+ # Scan all repository directories in cache
1307
+ # Cache structure: ~/.claude-mpm/cache/skills/{owner}/{repo}/...
1308
+ for repo_dir in cache_dir.rglob("*"):
1309
+ if not repo_dir.is_dir():
1280
1310
  continue
1281
1311
 
1282
- # Count skill directories in this collection
1312
+ # Count skill directories (those with SKILL.md)
1283
1313
  # Skills can be nested in: skills/category/skill-name/SKILL.md
1284
1314
  # or in flat structure: skill-name/SKILL.md
1285
- for root, dirs, files in os.walk(collection_dir):
1315
+ for root, dirs, files in os.walk(repo_dir):
1286
1316
  if "SKILL.md" in files:
1287
- # Exclude build artifacts and hidden directories (within the collection)
1288
- # Get relative path from collection_dir to avoid excluding based on .claude parent
1317
+ # Exclude build artifacts and hidden directories
1289
1318
  root_path = Path(root)
1290
- relative_parts = root_path.relative_to(collection_dir).parts
1291
1319
  if not any(
1292
1320
  part.startswith(".")
1293
1321
  or part in ["dist", "build", "__pycache__"]
1294
- for part in relative_parts
1322
+ for part in root_path.parts
1295
1323
  ):
1296
- available_count += 1
1324
+ cached_count += 1
1297
1325
 
1298
- # Display summary if we have skills
1299
- if installed_count > 0 or available_count > 0:
1326
+ # Display summary using agent summary format: "X deployed / Y cached"
1327
+ # Only show non-deployed cached skills (subtract deployed from cached)
1328
+ non_deployed_cached = max(0, cached_count - deployed_count)
1329
+ if deployed_count > 0 or non_deployed_cached > 0:
1300
1330
  print(
1301
- f"✓ Skills: {installed_count} installed ({available_count} available)",
1331
+ f"✓ Skills: {deployed_count} deployed / {non_deployed_cached} cached",
1302
1332
  flush=True,
1303
1333
  )
1304
1334
 
@@ -991,12 +991,17 @@ class GitSkillSourceManager:
991
991
  progress_callback=None,
992
992
  skill_filter: Optional[Set[str]] = None,
993
993
  ) -> Dict[str, Any]:
994
- """Deploy skills from cache to target directory with flat structure.
994
+ """Deploy skills from cache to target directory with flat structure and automatic cleanup.
995
995
 
996
996
  Flattens nested Git repository structure into Claude Code compatible
997
997
  flat directory structure. Each skill directory is copied with a
998
998
  hyphen-separated name derived from its path.
999
999
 
1000
+ CRITICAL: When skill_filter is provided (agent-referenced skills), this function:
1001
+ 1. Deploys ONLY the filtered skills
1002
+ 2. REMOVES orphaned skills (deployed but not in filter)
1003
+ 3. Returns removed_count and removed_skills in result
1004
+
1000
1005
  Transformation Example:
1001
1006
  Cache: collaboration/dispatching-parallel-agents/SKILL.md
1002
1007
  Deploy: collaboration-dispatching-parallel-agents/SKILL.md
@@ -1006,8 +1011,8 @@ class GitSkillSourceManager:
1006
1011
  force: Overwrite existing skills
1007
1012
  progress_callback: Optional callback(increment: int) called for each skill deployed
1008
1013
  skill_filter: Optional set of skill names to deploy (selective deployment).
1009
- If None, deploys all skills. If provided, only deploys skills
1010
- whose name matches an entry in the filter set.
1014
+ If None, deploys ALL skills WITHOUT cleanup.
1015
+ If provided, deploys ONLY filtered skills AND removes orphans.
1011
1016
 
1012
1017
  Returns:
1013
1018
  Dict with deployment results:
@@ -1018,7 +1023,9 @@ class GitSkillSourceManager:
1018
1023
  "deployed_skills": List[str],
1019
1024
  "skipped_skills": List[str],
1020
1025
  "errors": List[str],
1021
- "filtered_count": int # Number of skills filtered out
1026
+ "filtered_count": int, # Number of skills filtered out
1027
+ "removed_count": int, # Number of orphaned skills removed
1028
+ "removed_skills": List[str] # Names of removed orphaned skills
1022
1029
  }
1023
1030
 
1024
1031
  Example:
@@ -1026,10 +1033,10 @@ class GitSkillSourceManager:
1026
1033
  >>> result = manager.deploy_skills()
1027
1034
  >>> print(f"Deployed {result['deployed_count']} skills")
1028
1035
 
1029
- # Selective deployment based on agent requirements:
1036
+ # Selective deployment based on agent requirements (with cleanup):
1030
1037
  >>> required = {"typescript-core", "react-patterns"}
1031
1038
  >>> result = manager.deploy_skills(skill_filter=required)
1032
- >>> print(f"Deployed {result['deployed_count']} of {len(required)} required skills")
1039
+ >>> print(f"Deployed {result['deployed_count']}, removed {result['removed_count']} orphans")
1033
1040
  """
1034
1041
  if target_dir is None:
1035
1042
  target_dir = Path.home() / ".claude" / "skills"
@@ -1040,6 +1047,7 @@ class GitSkillSourceManager:
1040
1047
  skipped = []
1041
1048
  errors = []
1042
1049
  filtered_count = 0
1050
+ removed_skills = [] # Track removed orphaned skills
1043
1051
 
1044
1052
  # Get all skills from all sources
1045
1053
  all_skills = self.get_all_skills()
@@ -1082,11 +1090,12 @@ class GitSkillSourceManager:
1082
1090
  )
1083
1091
 
1084
1092
  # Cleanup: Remove skills from target directory that aren't in the filtered set
1085
- # This ensures only the skills in the profile are deployed
1093
+ # This ensures only agent-referenced skills remain deployed
1086
1094
  removed_skills = self._cleanup_unfiltered_skills(target_dir, all_skills)
1087
1095
  if removed_skills:
1088
1096
  self.logger.info(
1089
- f"Removed {len(removed_skills)} skills not in profile filter: {removed_skills}"
1097
+ f"Removed {len(removed_skills)} orphaned skills not referenced by agents: {removed_skills[:10]}"
1098
+ + (f" (and {len(removed_skills) - 10} more)" if len(removed_skills) > 10 else "")
1090
1099
  )
1091
1100
 
1092
1101
  self.logger.info(
@@ -1130,6 +1139,7 @@ class GitSkillSourceManager:
1130
1139
  self.logger.info(
1131
1140
  f"Deployment complete: {len(deployed)} deployed, "
1132
1141
  f"{len(skipped)} skipped, {len(errors)} errors"
1142
+ + (f", {len(removed_skills)} removed" if removed_skills else "")
1133
1143
  )
1134
1144
 
1135
1145
  return {
@@ -1140,6 +1150,8 @@ class GitSkillSourceManager:
1140
1150
  "skipped_skills": skipped,
1141
1151
  "errors": errors,
1142
1152
  "filtered_count": filtered_count,
1153
+ "removed_count": len(removed_skills),
1154
+ "removed_skills": removed_skills,
1143
1155
  }
1144
1156
 
1145
1157
  def _cleanup_unfiltered_skills(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-mpm
3
- Version: 5.4.55
3
+ Version: 5.4.56
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
@@ -1,5 +1,5 @@
1
1
  claude_mpm/BUILD_NUMBER,sha256=9JfxhnDtr-8l3kCP2U5TVXSErptHoga8m7XA8zqgGOc,4
2
- claude_mpm/VERSION,sha256=TLVDS_92yJkYECBgEYt0HT-TGor6SzTV9a_IxlrvsrM,7
2
+ claude_mpm/VERSION,sha256=nwC2X1qTiadu5lECbdz8Av1yO5Df8yo2mz7YehUJ0yI,7
3
3
  claude_mpm/__init__.py,sha256=AGfh00BHKvLYD-UVFw7qbKtl7NMRIzRXOWw7vEuZ-h4,2214
4
4
  claude_mpm/__main__.py,sha256=Ro5UBWBoQaSAIoSqWAr7zkbLyvi4sSy28WShqAhKJG0,723
5
5
  claude_mpm/constants.py,sha256=CU7v8LZT-cFCNNLZU4dpaMD-4c3ib16v8fVE620wUjk,6761
@@ -41,7 +41,7 @@ claude_mpm/cli/chrome_devtools_installer.py,sha256=efA_ZX1iR3oaJi3222079BQw6DEG8
41
41
  claude_mpm/cli/executor.py,sha256=cetKiY2YS4XETUBXmfySdHYloI_M_lBJUFViRQ9TcS4,10544
42
42
  claude_mpm/cli/helpers.py,sha256=CypEhw0tbNH6_GzVTaQdi4w7ThCWO43Ep92YbJzPR4I,3638
43
43
  claude_mpm/cli/parser.py,sha256=Vqx9n-6Xo1uNhXR4rThmgWpZXTr0nOtkgDf3oMS9b0g,5855
44
- claude_mpm/cli/startup.py,sha256=FMLtXI0wy_69J7yWD61W5mH3CIpm6ZUHd6kJ_f_p6hI,71604
44
+ claude_mpm/cli/startup.py,sha256=3NuNrGG7keeTbC5YNcmgEVgtFQlIz9ui6XNy-1YEF7c,72965
45
45
  claude_mpm/cli/startup_display.py,sha256=R_QIamjfdaY5o_VxpIeymyYj1Qde2B9hPXy1P-KmUKI,14972
46
46
  claude_mpm/cli/startup_logging.py,sha256=RTuyd6CbhiFQz7Z07LDDhK_ZAnZfuJ9B0NghVSntHFI,29390
47
47
  claude_mpm/cli/utils.py,sha256=FSMPftBZM8MeUyTtiB63Lz7oFOgkzwTetQs58RbRb_Q,8785
@@ -701,7 +701,7 @@ claude_mpm/services/shared/lifecycle_service_base.py,sha256=YJZHs2sUrnIsbHHjrd8l
701
701
  claude_mpm/services/shared/manager_base.py,sha256=kmjhpVqgfYC1N4YQnPAilCfdrSpAh9Qz7wcQ602L4x4,9296
702
702
  claude_mpm/services/shared/service_factory.py,sha256=9yvnD62urrNQCGmtk_3OcR5tVUCnoS6wHkaI5PK34mg,9891
703
703
  claude_mpm/services/skills/__init__.py,sha256=X1fPRCGZjteLd35HlhWv2M6tAJ_WbYrEv84kbaqBAiU,742
704
- claude_mpm/services/skills/git_skill_source_manager.py,sha256=YztvRMqayar2MLQElfyMX3XNCi9VwMrzlYwIir7LXQ0,50713
704
+ claude_mpm/services/skills/git_skill_source_manager.py,sha256=H0MEug2lCix6RHqF4nVt3-Olfe9DM9bKlm_Bw9OJC4g,51529
705
705
  claude_mpm/services/skills/selective_skill_deployer.py,sha256=7iTtPYSSO33rLCeZEZ_ZNNa369rhKsYCiqL0QcpMVbg,26478
706
706
  claude_mpm/services/skills/skill_discovery_service.py,sha256=riy0PTnJS8e5R2ai8y1KPhBIR7SxlYIa9bnI9YccRMQ,20247
707
707
  claude_mpm/services/skills/skill_to_agent_mapper.py,sha256=4PRwcSDSNGS55lg4t-VmBK2ottE_cGFq1zsvjiumAlI,14847
@@ -859,10 +859,10 @@ claude_mpm/utils/subprocess_utils.py,sha256=D0izRT8anjiUb_JG72zlJR_JAw1cDkb7kalN
859
859
  claude_mpm/validation/__init__.py,sha256=YZhwE3mhit-lslvRLuwfX82xJ_k4haZeKmh4IWaVwtk,156
860
860
  claude_mpm/validation/agent_validator.py,sha256=GprtAvu80VyMXcKGsK_VhYiXWA6BjKHv7O6HKx0AB9w,20917
861
861
  claude_mpm/validation/frontmatter_validator.py,sha256=YpJlYNNYcV8u6hIOi3_jaRsDnzhbcQpjCBE6eyBKaFY,7076
862
- claude_mpm-5.4.55.dist-info/licenses/LICENSE,sha256=ca3y_Rk4aPrbF6f62z8Ht5MJM9OAvbGlHvEDcj9vUQ4,3867
863
- claude_mpm-5.4.55.dist-info/licenses/LICENSE-FAQ.md,sha256=TxfEkXVCK98RzDOer09puc7JVCP_q_bN4dHtZKHCMcM,5104
864
- claude_mpm-5.4.55.dist-info/METADATA,sha256=K7MO9bTExO-LL2ZPoJUTeHbkbcvBMo50Uz-qXBbj5eM,37997
865
- claude_mpm-5.4.55.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
866
- claude_mpm-5.4.55.dist-info/entry_points.txt,sha256=n-Uk4vwHPpuvu-g_I7-GHORzTnN_m6iyOsoLveKKD0E,228
867
- claude_mpm-5.4.55.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
868
- claude_mpm-5.4.55.dist-info/RECORD,,
862
+ claude_mpm-5.4.56.dist-info/licenses/LICENSE,sha256=ca3y_Rk4aPrbF6f62z8Ht5MJM9OAvbGlHvEDcj9vUQ4,3867
863
+ claude_mpm-5.4.56.dist-info/licenses/LICENSE-FAQ.md,sha256=TxfEkXVCK98RzDOer09puc7JVCP_q_bN4dHtZKHCMcM,5104
864
+ claude_mpm-5.4.56.dist-info/METADATA,sha256=TNxOdaTaMPQctoM85R8jZl7eixue-OGtgCrzj13lpCk,37997
865
+ claude_mpm-5.4.56.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
866
+ claude_mpm-5.4.56.dist-info/entry_points.txt,sha256=n-Uk4vwHPpuvu-g_I7-GHORzTnN_m6iyOsoLveKKD0E,228
867
+ claude_mpm-5.4.56.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
868
+ claude_mpm-5.4.56.dist-info/RECORD,,