claude-mpm 5.4.55__py3-none-any.whl → 5.4.57__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/startup.py +72 -42
- claude_mpm/services/skills/git_skill_source_manager.py +20 -8
- claude_mpm/services/skills/selective_skill_deployer.py +91 -82
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.57.dist-info}/METADATA +1 -1
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.57.dist-info}/RECORD +11 -11
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.57.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.57.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.57.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.57.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.57.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.4.
|
|
1
|
+
5.4.57
|
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
|
-
#
|
|
994
|
-
#
|
|
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 (
|
|
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
|
-
|
|
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: {
|
|
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: {
|
|
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
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
if
|
|
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
|
|
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
|
-
|
|
1299
|
+
deployed_count = len(skill_dirs)
|
|
1270
1300
|
|
|
1271
|
-
# Count
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
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
|
|
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(
|
|
1315
|
+
for root, dirs, files in os.walk(repo_dir):
|
|
1286
1316
|
if "SKILL.md" in files:
|
|
1287
|
-
# Exclude build artifacts and hidden directories
|
|
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
|
|
1322
|
+
for part in root_path.parts
|
|
1295
1323
|
):
|
|
1296
|
-
|
|
1324
|
+
cached_count += 1
|
|
1297
1325
|
|
|
1298
|
-
# Display summary
|
|
1299
|
-
|
|
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: {
|
|
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
|
|
1010
|
-
|
|
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']}
|
|
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
|
|
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
|
|
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(
|
|
@@ -51,6 +51,59 @@ logger = get_logger(__name__)
|
|
|
51
51
|
# Deployment tracking index file
|
|
52
52
|
DEPLOYED_INDEX_FILE = ".mpm-deployed-skills.json"
|
|
53
53
|
|
|
54
|
+
# Core skills that are universally useful across all projects
|
|
55
|
+
# These are deployed when skill mapping returns too many skills (>60)
|
|
56
|
+
# Target: ~25-30 core skills for balanced functionality
|
|
57
|
+
CORE_SKILLS = {
|
|
58
|
+
# Universal debugging and verification (4 skills)
|
|
59
|
+
"universal-debugging-systematic-debugging",
|
|
60
|
+
"universal-debugging-verification-before-completion",
|
|
61
|
+
"universal-verification-pre-merge",
|
|
62
|
+
"universal-verification-screenshot",
|
|
63
|
+
|
|
64
|
+
# Universal testing patterns (2 skills)
|
|
65
|
+
"universal-testing-test-driven-development",
|
|
66
|
+
"universal-testing-testing-anti-patterns",
|
|
67
|
+
|
|
68
|
+
# Universal architecture and design (1 skill)
|
|
69
|
+
"universal-architecture-software-patterns",
|
|
70
|
+
|
|
71
|
+
# Universal infrastructure (3 skills)
|
|
72
|
+
"universal-infrastructure-env-manager",
|
|
73
|
+
"universal-infrastructure-docker",
|
|
74
|
+
"universal-infrastructure-github-actions",
|
|
75
|
+
|
|
76
|
+
# Universal collaboration (1 skill)
|
|
77
|
+
"universal-collaboration-stacked-prs",
|
|
78
|
+
|
|
79
|
+
# Universal emergency/operations (1 skill)
|
|
80
|
+
"toolchains-universal-emergency-release",
|
|
81
|
+
"toolchains-universal-dependency-audit",
|
|
82
|
+
|
|
83
|
+
# Common language toolchains (6 skills)
|
|
84
|
+
"toolchains-typescript-core",
|
|
85
|
+
"toolchains-python-core",
|
|
86
|
+
"toolchains-javascript-tooling-biome",
|
|
87
|
+
"toolchains-python-tooling-mypy",
|
|
88
|
+
"toolchains-typescript-testing-vitest",
|
|
89
|
+
"toolchains-python-frameworks-flask",
|
|
90
|
+
|
|
91
|
+
# Common web frameworks (4 skills)
|
|
92
|
+
"toolchains-javascript-frameworks-nextjs",
|
|
93
|
+
"toolchains-nextjs-core",
|
|
94
|
+
"toolchains-typescript-frameworks-nodejs-backend",
|
|
95
|
+
"toolchains-javascript-frameworks-react-state-machine",
|
|
96
|
+
|
|
97
|
+
# Common testing tools (2 skills)
|
|
98
|
+
"toolchains-javascript-testing-playwright",
|
|
99
|
+
"toolchains-typescript-testing-jest",
|
|
100
|
+
|
|
101
|
+
# Common data/UI tools (3 skills)
|
|
102
|
+
"universal-data-xlsx",
|
|
103
|
+
"toolchains-ui-styling-tailwind",
|
|
104
|
+
"toolchains-ui-components-headlessui",
|
|
105
|
+
}
|
|
106
|
+
|
|
54
107
|
|
|
55
108
|
def parse_agent_frontmatter(agent_file: Path) -> Dict[str, Any]:
|
|
56
109
|
"""Parse YAML frontmatter from agent markdown file.
|
|
@@ -140,22 +193,14 @@ def get_skills_from_agent(frontmatter: Dict[str, Any]) -> Set[str]:
|
|
|
140
193
|
def get_skills_from_mapping(agent_ids: List[str]) -> Set[str]:
|
|
141
194
|
"""Get skills for agents using SkillToAgentMapper inference.
|
|
142
195
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
CRITICAL DESIGN DECISION: This function ONLY returns skills for the DEPLOYED agents
|
|
147
|
-
provided in agent_ids. It does NOT return skills for all agents in the mapping
|
|
148
|
-
configuration (skill_to_agent_mapping.yaml lists 41 agents, but only 33 may be deployed).
|
|
196
|
+
DEPRECATED: This function is deprecated as of Phase 3 refactor.
|
|
197
|
+
Skills are now declared exclusively in agent frontmatter.
|
|
149
198
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
specialized agents exist, we skip "engineer" if specialized agents are present.
|
|
199
|
+
The static skill_to_agent_mapping.yaml is no longer used for skill deployment.
|
|
200
|
+
Each agent must declare its skills in frontmatter or it gets zero skills.
|
|
153
201
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
- User may only have 33 agents deployed in ~/.claude/agents/
|
|
157
|
-
- Without filtering, we'd deploy skills for all 41 agents (over-deployment)
|
|
158
|
-
- Solution: Only query skills for DEPLOYED agents (passed in agent_ids)
|
|
202
|
+
This function remains for backward compatibility but is NO LONGER CALLED
|
|
203
|
+
by get_required_skills_from_agents().
|
|
159
204
|
|
|
160
205
|
Args:
|
|
161
206
|
agent_ids: List of DEPLOYED agent identifiers (e.g., ["python-engineer", "typescript-engineer"])
|
|
@@ -163,67 +208,34 @@ def get_skills_from_mapping(agent_ids: List[str]) -> Set[str]:
|
|
|
163
208
|
|
|
164
209
|
Returns:
|
|
165
210
|
Set of unique skill names inferred from mapping configuration for DEPLOYED agents only
|
|
211
|
+
NOTE: This is now an empty set as the function is deprecated.
|
|
166
212
|
|
|
167
213
|
Example:
|
|
168
|
-
>>> #
|
|
214
|
+
>>> # DEPRECATED - use frontmatter instead
|
|
169
215
|
>>> deployed_agent_ids = ["python-engineer", "typescript-engineer", "qa"]
|
|
170
|
-
>>> skills = get_skills_from_mapping(deployed_agent_ids)
|
|
171
|
-
>>> print(f"Found {len(skills)} skills for {len(deployed_agent_ids)} deployed agents")
|
|
216
|
+
>>> skills = get_skills_from_mapping(deployed_agent_ids) # Returns empty set
|
|
172
217
|
"""
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
# agents like "python-engineer", "typescript-engineer", etc.
|
|
181
|
-
#
|
|
182
|
-
# Solution: Filter out "engineer" from agent_ids if specialized agents exist
|
|
183
|
-
specialized_engineers = [
|
|
184
|
-
aid for aid in agent_ids if aid.endswith("-engineer") and aid != "engineer"
|
|
185
|
-
]
|
|
186
|
-
|
|
187
|
-
# If specialized engineers exist, exclude generic "engineer" from skill mapping
|
|
188
|
-
# This prevents deploying 100+ skills when only a subset is needed
|
|
189
|
-
agents_to_query = agent_ids
|
|
190
|
-
if specialized_engineers and "engineer" in agent_ids:
|
|
191
|
-
agents_to_query = [aid for aid in agent_ids if aid != "engineer"]
|
|
192
|
-
logger.info(
|
|
193
|
-
f"Excluding generic 'engineer' agent from skill mapping "
|
|
194
|
-
f"(found {len(specialized_engineers)} specialized engineers: "
|
|
195
|
-
f"{', '.join(specialized_engineers[:5])}{'...' if len(specialized_engineers) > 5 else ''})"
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
# IMPORTANT: Only query skills for DEPLOYED agents (those in agent_ids)
|
|
199
|
-
# Do NOT query all agents from skill_to_agent_mapping.yaml (that's 41 agents)
|
|
200
|
-
for agent_id in agents_to_query:
|
|
201
|
-
agent_skills = mapper.get_skills_for_agent(agent_id)
|
|
202
|
-
if agent_skills:
|
|
203
|
-
all_skills.update(agent_skills)
|
|
204
|
-
logger.debug(f"Mapped {len(agent_skills)} skills to {agent_id}")
|
|
205
|
-
|
|
206
|
-
logger.info(
|
|
207
|
-
f"Mapped {len(all_skills)} unique skills for {len(agents_to_query)} deployed agents "
|
|
208
|
-
f"(out of {len(agent_ids)} total deployed, excluding generic 'engineer' if specialized exist)"
|
|
209
|
-
)
|
|
210
|
-
return all_skills
|
|
211
|
-
|
|
212
|
-
except Exception as e:
|
|
213
|
-
logger.warning(f"Failed to load SkillToAgentMapper: {e}")
|
|
214
|
-
logger.info("Falling back to frontmatter-only skill discovery")
|
|
215
|
-
return set()
|
|
218
|
+
# DEPRECATED: Return empty set
|
|
219
|
+
logger.warning(
|
|
220
|
+
"get_skills_from_mapping() is DEPRECATED and returns empty set. "
|
|
221
|
+
"Skills are now declared in agent frontmatter only. "
|
|
222
|
+
"Update your agents with 'skills:' field in frontmatter."
|
|
223
|
+
)
|
|
224
|
+
return set()
|
|
216
225
|
|
|
217
226
|
|
|
218
227
|
def get_required_skills_from_agents(agents_dir: Path) -> Set[str]:
|
|
219
228
|
"""Extract all skills referenced by deployed agents.
|
|
220
229
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
230
|
+
MAJOR CHANGE (Phase 3): Now ONLY uses frontmatter-declared skills.
|
|
231
|
+
The static skill_to_agent_mapping.yaml is DEPRECATED. Each agent must
|
|
232
|
+
declare its skills in frontmatter or it gets zero skills deployed.
|
|
224
233
|
|
|
225
|
-
This
|
|
226
|
-
|
|
234
|
+
This change:
|
|
235
|
+
- Eliminates dual-source complexity (frontmatter + mapping)
|
|
236
|
+
- Makes skill requirements explicit per agent
|
|
237
|
+
- Enables per-agent customization via frontmatter
|
|
238
|
+
- Removes dependency on static YAML mapping
|
|
227
239
|
|
|
228
240
|
Args:
|
|
229
241
|
agents_dir: Path to deployed agents directory (e.g., .claude/agents/)
|
|
@@ -244,13 +256,11 @@ def get_required_skills_from_agents(agents_dir: Path) -> Set[str]:
|
|
|
244
256
|
agent_files = list(agents_dir.glob("*.md"))
|
|
245
257
|
logger.debug(f"Scanning {len(agent_files)} agent files in {agents_dir}")
|
|
246
258
|
|
|
247
|
-
#
|
|
259
|
+
# ONLY use frontmatter skills - no more mapping inference
|
|
248
260
|
frontmatter_skills = set()
|
|
249
|
-
agent_ids = []
|
|
250
261
|
|
|
251
262
|
for agent_file in agent_files:
|
|
252
263
|
agent_id = agent_file.stem
|
|
253
|
-
agent_ids.append(agent_id)
|
|
254
264
|
|
|
255
265
|
frontmatter = parse_agent_frontmatter(agent_file)
|
|
256
266
|
agent_skills = get_skills_from_agent(frontmatter)
|
|
@@ -260,24 +270,23 @@ def get_required_skills_from_agents(agents_dir: Path) -> Set[str]:
|
|
|
260
270
|
logger.debug(
|
|
261
271
|
f"Agent {agent_id}: {len(agent_skills)} skills from frontmatter"
|
|
262
272
|
)
|
|
273
|
+
else:
|
|
274
|
+
logger.debug(f"Agent {agent_id}: No skills declared in frontmatter")
|
|
263
275
|
|
|
264
|
-
logger.info(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
# Combine both sources
|
|
270
|
-
required_skills = frontmatter_skills | mapped_skills
|
|
276
|
+
logger.info(
|
|
277
|
+
f"Found {len(frontmatter_skills)} unique skills from agent frontmatter "
|
|
278
|
+
f"(static mapping no longer used)"
|
|
279
|
+
)
|
|
271
280
|
|
|
272
281
|
# Normalize skill paths: convert slashes to dashes for compatibility with deployment
|
|
273
|
-
#
|
|
274
|
-
|
|
275
|
-
normalized_skills = {skill.replace("/", "-") for skill in required_skills}
|
|
282
|
+
# Some skills may use slash format, normalize to dashes
|
|
283
|
+
normalized_skills = {skill.replace("/", "-") for skill in frontmatter_skills}
|
|
276
284
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
285
|
+
if normalized_skills != frontmatter_skills:
|
|
286
|
+
logger.debug(
|
|
287
|
+
f"Normalized {len(frontmatter_skills)} skills to {len(normalized_skills)} "
|
|
288
|
+
"(converted slashes to dashes)"
|
|
289
|
+
)
|
|
281
290
|
|
|
282
291
|
return normalized_skills
|
|
283
292
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
claude_mpm/BUILD_NUMBER,sha256=9JfxhnDtr-8l3kCP2U5TVXSErptHoga8m7XA8zqgGOc,4
|
|
2
|
-
claude_mpm/VERSION,sha256=
|
|
2
|
+
claude_mpm/VERSION,sha256=hPaPbcOTW6MtG4jAk-rO_X3DLacWiuZPeOwlRGpkqWw,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=
|
|
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,8 +701,8 @@ 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=
|
|
705
|
-
claude_mpm/services/skills/selective_skill_deployer.py,sha256=
|
|
704
|
+
claude_mpm/services/skills/git_skill_source_manager.py,sha256=H0MEug2lCix6RHqF4nVt3-Olfe9DM9bKlm_Bw9OJC4g,51529
|
|
705
|
+
claude_mpm/services/skills/selective_skill_deployer.py,sha256=XfNmNn4U8LL6nIPbRhB7j_yMaFsR7tv9bGvx6BrobIU,25836
|
|
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
|
|
708
708
|
claude_mpm/services/socketio/__init__.py,sha256=PS-2twllga-2mhSfKdu4MgpikfKp_730gMLAqU_9YX4,556
|
|
@@ -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.
|
|
863
|
-
claude_mpm-5.4.
|
|
864
|
-
claude_mpm-5.4.
|
|
865
|
-
claude_mpm-5.4.
|
|
866
|
-
claude_mpm-5.4.
|
|
867
|
-
claude_mpm-5.4.
|
|
868
|
-
claude_mpm-5.4.
|
|
862
|
+
claude_mpm-5.4.57.dist-info/licenses/LICENSE,sha256=ca3y_Rk4aPrbF6f62z8Ht5MJM9OAvbGlHvEDcj9vUQ4,3867
|
|
863
|
+
claude_mpm-5.4.57.dist-info/licenses/LICENSE-FAQ.md,sha256=TxfEkXVCK98RzDOer09puc7JVCP_q_bN4dHtZKHCMcM,5104
|
|
864
|
+
claude_mpm-5.4.57.dist-info/METADATA,sha256=Ri9BtdOI3pQPHjXWE67QjqbQ8hfF9qJz2R2YwW-5vpg,37997
|
|
865
|
+
claude_mpm-5.4.57.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
866
|
+
claude_mpm-5.4.57.dist-info/entry_points.txt,sha256=n-Uk4vwHPpuvu-g_I7-GHORzTnN_m6iyOsoLveKKD0E,228
|
|
867
|
+
claude_mpm-5.4.57.dist-info/top_level.txt,sha256=1nUg3FEaBySgm8t-s54jK5zoPnu3_eY6EP6IOlekyHA,11
|
|
868
|
+
claude_mpm-5.4.57.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|