claude-mpm 5.4.90__py3-none-any.whl → 5.4.94__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/monitor.py +2 -2
- claude_mpm/cli/startup_display.py +72 -5
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/commands/mpm-session-resume.md +1 -1
- claude_mpm/core/unified_config.py +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BS0ej2w8.js → 1WZnGYqX.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{7ZAeO_Uj.js → 67pF3qNn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BCWDw8BF.js → 6RxdMKe4.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BVFqgd56.js → 8cZrfX0h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDNOxKrg.js → 9a6T2nm-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{MJf6AOIJ.js → B443AUzu.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B8AwtY2H.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DNI1jw9S.js → BF15LAsF.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DOeJfApz.js → BRcwIQNr.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BfXd4Xj4.js → BV6nKitt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DWDi9IaK.js → BViJ8lZt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BnFPFynJ.js → BcQ-Q0FE.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B_fnSNFx.js → Bpyvgze_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{EKp_wsKE.js → C3rbW_a-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_l0vq62.js → C8WYN38h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDi5wzaD.js → C9I8FlXH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BwpSELyW.js → CIQcWgO2.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CQ94FMOU.js → CIctN7YN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzkNB1Vu.js → CKrS_JZW.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D0Fj1OdD.js → CR6P9C4A.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{NsEh4Ivo.js → CRRR9MD_.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DdIDcQsD.js → CSXtMOf0.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C5Dg_JxJ.js → CT-sbxSk.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyrTH56Q.js → CWm6DJsp.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C86quetY.js → CpqQ1Kzn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dqtg3hb8.js → D9iCMida.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DJN4AVXS.js → D9ykgMoY.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{14Ru8gxt.js → DL2Ldur1.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLeM8wSV.js → DPfltzjH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CvWciI1W.js → DUliQN2b.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DOViuQX_.js → DXlhR01x.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{s04HIjWg.js → D_lyTybS.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{WiqB4NUY.js → DngoTTgh.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CT5eAo1x.js → DqkmHtDC.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DJuK4-OP.js → DsDh8EYs.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C7i47te_.js → DypDmXgd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BfMC7wDI.js → IPYC-LnN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{URSAF6IJ.js → JTLiF7dt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B06ALsCS.js → JpevfAFt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D1ARDjz0.js → Zxy7qc-l.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{vJiSSdpk.js → qtd3IeO4.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BXs4CVzO.js → ulBFON_C.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DTgfNBV9.js → wQVh1CoA.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.CnXU_fEX.js → app.Dr7t0z2J.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.BuxSUm_s.js → 0.RgBboRvH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.znyTz9u3.js → 1.DG-KkbDf.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
- claude_mpm/dashboard/static/svelte-build/index.html +8 -8
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +63 -10
- claude_mpm/hooks/claude_hooks/hook_handler.py +3 -1
- claude_mpm/hooks/claude_hooks/response_tracking.py +2 -16
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +20 -0
- claude_mpm/services/agents/cache_git_manager.py +1 -1
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
- claude_mpm/services/diagnostics/models.py +14 -1
- claude_mpm/services/monitor/daemon_manager.py +15 -4
- claude_mpm/services/monitor/management/lifecycle.py +8 -2
- claude_mpm/services/monitor/server.py +106 -16
- claude_mpm/services/skills/selective_skill_deployer.py +114 -16
- {claude_mpm-5.4.90.dist-info → claude_mpm-5.4.94.dist-info}/METADATA +1 -1
- {claude_mpm-5.4.90.dist-info → claude_mpm-5.4.94.dist-info}/RECORD +92 -90
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B7S5qgOx.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.D3t4z6uz.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/AeivYILh.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cn4nXAfg.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D_vpdI7l.js +0 -325
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DnL7ky1O.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.CUaAfoQJ.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.CLVHDDxl.js +0 -1
- {claude_mpm-5.4.90.dist-info → claude_mpm-5.4.94.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.90.dist-info → claude_mpm-5.4.94.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.90.dist-info → claude_mpm-5.4.94.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.90.dist-info → claude_mpm-5.4.94.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.90.dist-info → claude_mpm-5.4.94.dist-info}/top_level.txt +0 -0
|
@@ -368,6 +368,83 @@ class UnifiedMonitorServer:
|
|
|
368
368
|
finally:
|
|
369
369
|
await self._cleanup_async()
|
|
370
370
|
|
|
371
|
+
def _categorize_event(self, event_name: str) -> str:
|
|
372
|
+
"""Categorize event by name to determine Socket.IO event type.
|
|
373
|
+
|
|
374
|
+
Maps specific event names to their category for frontend filtering.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
event_name: The raw event name (e.g., "subagent_start", "todo_updated")
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
Category name (e.g., "hook_event", "system_event")
|
|
381
|
+
"""
|
|
382
|
+
# Hook events - agent lifecycle and todo updates
|
|
383
|
+
if event_name in ("subagent_start", "subagent_stop", "todo_updated"):
|
|
384
|
+
return "hook_event"
|
|
385
|
+
|
|
386
|
+
# Tool events - both hook-style and direct tool events
|
|
387
|
+
if event_name in (
|
|
388
|
+
"pre_tool",
|
|
389
|
+
"post_tool",
|
|
390
|
+
"tool.start",
|
|
391
|
+
"tool.end",
|
|
392
|
+
"tool_use",
|
|
393
|
+
"tool_result",
|
|
394
|
+
):
|
|
395
|
+
return "tool_event"
|
|
396
|
+
|
|
397
|
+
# Session events - session lifecycle
|
|
398
|
+
if event_name in (
|
|
399
|
+
"session.started",
|
|
400
|
+
"session.ended",
|
|
401
|
+
"session_start",
|
|
402
|
+
"session_end",
|
|
403
|
+
):
|
|
404
|
+
return "session_event"
|
|
405
|
+
|
|
406
|
+
# Response events - API response lifecycle
|
|
407
|
+
if event_name in (
|
|
408
|
+
"response.start",
|
|
409
|
+
"response.end",
|
|
410
|
+
"response_started",
|
|
411
|
+
"response_ended",
|
|
412
|
+
):
|
|
413
|
+
return "response_event"
|
|
414
|
+
|
|
415
|
+
# Agent events - agent delegation and returns
|
|
416
|
+
if event_name in (
|
|
417
|
+
"agent.delegated",
|
|
418
|
+
"agent.returned",
|
|
419
|
+
"agent_start",
|
|
420
|
+
"agent_end",
|
|
421
|
+
):
|
|
422
|
+
return "agent_event"
|
|
423
|
+
|
|
424
|
+
# File events - file operations
|
|
425
|
+
if event_name in (
|
|
426
|
+
"file.read",
|
|
427
|
+
"file.write",
|
|
428
|
+
"file.edit",
|
|
429
|
+
"file_read",
|
|
430
|
+
"file_write",
|
|
431
|
+
):
|
|
432
|
+
return "file_event"
|
|
433
|
+
|
|
434
|
+
# Claude API events
|
|
435
|
+
if event_name in ("user_prompt", "assistant_message"):
|
|
436
|
+
return "claude_event"
|
|
437
|
+
|
|
438
|
+
# System events
|
|
439
|
+
if event_name in ("system_ready", "system_shutdown"):
|
|
440
|
+
return "system_event"
|
|
441
|
+
|
|
442
|
+
# Log uncategorized events for debugging
|
|
443
|
+
self.logger.debug(f"Uncategorized event: {event_name}")
|
|
444
|
+
|
|
445
|
+
# Default to claude_event for unknown events
|
|
446
|
+
return "claude_event"
|
|
447
|
+
|
|
371
448
|
def _setup_event_handlers(self):
|
|
372
449
|
"""Setup Socket.IO event handlers."""
|
|
373
450
|
try:
|
|
@@ -474,7 +551,7 @@ class UnifiedMonitorServer:
|
|
|
474
551
|
)
|
|
475
552
|
if version_file.exists():
|
|
476
553
|
version = version_file.read_text().strip()
|
|
477
|
-
except Exception:
|
|
554
|
+
except Exception: # nosec B110
|
|
478
555
|
pass
|
|
479
556
|
|
|
480
557
|
return web.json_response(
|
|
@@ -499,10 +576,23 @@ class UnifiedMonitorServer:
|
|
|
499
576
|
event = data.get("event", "claude_event")
|
|
500
577
|
event_data = data.get("data", {})
|
|
501
578
|
|
|
502
|
-
#
|
|
579
|
+
# Categorize event and wrap in expected format
|
|
580
|
+
event_type = self._categorize_event(event)
|
|
581
|
+
wrapped_event = {
|
|
582
|
+
"type": event_type,
|
|
583
|
+
"subtype": event,
|
|
584
|
+
"data": event_data,
|
|
585
|
+
"timestamp": event_data.get("timestamp")
|
|
586
|
+
or datetime.now(timezone.utc).isoformat() + "Z",
|
|
587
|
+
"session_id": event_data.get("session_id"),
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
# Emit to Socket.IO clients via the categorized event type
|
|
503
591
|
if self.sio:
|
|
504
|
-
await self.sio.emit(
|
|
505
|
-
self.logger.debug(
|
|
592
|
+
await self.sio.emit(event_type, wrapped_event)
|
|
593
|
+
self.logger.debug(
|
|
594
|
+
f"HTTP event forwarded to Socket.IO: {event} -> {event_type}"
|
|
595
|
+
)
|
|
506
596
|
|
|
507
597
|
return web.Response(status=204) # No content response
|
|
508
598
|
|
|
@@ -859,7 +949,7 @@ class UnifiedMonitorServer:
|
|
|
859
949
|
# Configuration endpoint for dashboard initialization
|
|
860
950
|
async def config_handler(request):
|
|
861
951
|
"""Return configuration for dashboard initialization."""
|
|
862
|
-
import subprocess
|
|
952
|
+
import subprocess # nosec B404
|
|
863
953
|
|
|
864
954
|
config = {
|
|
865
955
|
"workingDirectory": Path.cwd(),
|
|
@@ -870,7 +960,7 @@ class UnifiedMonitorServer:
|
|
|
870
960
|
|
|
871
961
|
# Try to get current git branch
|
|
872
962
|
try:
|
|
873
|
-
result = subprocess.run(
|
|
963
|
+
result = subprocess.run( # nosec B603 B607
|
|
874
964
|
["git", "branch", "--show-current"],
|
|
875
965
|
capture_output=True,
|
|
876
966
|
text=True,
|
|
@@ -880,7 +970,7 @@ class UnifiedMonitorServer:
|
|
|
880
970
|
)
|
|
881
971
|
if result.returncode == 0 and result.stdout.strip():
|
|
882
972
|
config["gitBranch"] = result.stdout.strip()
|
|
883
|
-
except Exception:
|
|
973
|
+
except Exception: # nosec B110
|
|
884
974
|
pass # Keep default "Unknown" value
|
|
885
975
|
|
|
886
976
|
return web.json_response(config)
|
|
@@ -910,7 +1000,7 @@ class UnifiedMonitorServer:
|
|
|
910
1000
|
# Git history handler
|
|
911
1001
|
async def git_history_handler(request: web.Request) -> web.Response:
|
|
912
1002
|
"""Get git history for a file."""
|
|
913
|
-
import subprocess
|
|
1003
|
+
import subprocess # nosec B404
|
|
914
1004
|
|
|
915
1005
|
try:
|
|
916
1006
|
data = await request.json()
|
|
@@ -939,7 +1029,7 @@ class UnifiedMonitorServer:
|
|
|
939
1029
|
)
|
|
940
1030
|
|
|
941
1031
|
# Get git log for file
|
|
942
|
-
result = subprocess.run(
|
|
1032
|
+
result = subprocess.run( # nosec B603 B607
|
|
943
1033
|
[
|
|
944
1034
|
"git",
|
|
945
1035
|
"log",
|
|
@@ -978,7 +1068,7 @@ class UnifiedMonitorServer:
|
|
|
978
1068
|
# Git diff handler
|
|
979
1069
|
async def git_diff_handler(request: web.Request) -> web.Response:
|
|
980
1070
|
"""Get git diff for a file with optional commit selection."""
|
|
981
|
-
import subprocess
|
|
1071
|
+
import subprocess # nosec B404
|
|
982
1072
|
|
|
983
1073
|
try:
|
|
984
1074
|
file_path = request.query.get("path", "")
|
|
@@ -1010,7 +1100,7 @@ class UnifiedMonitorServer:
|
|
|
1010
1100
|
)
|
|
1011
1101
|
|
|
1012
1102
|
# Find git repository root
|
|
1013
|
-
git_root_result = subprocess.run(
|
|
1103
|
+
git_root_result = subprocess.run( # nosec B603 B607
|
|
1014
1104
|
["git", "rev-parse", "--show-toplevel"],
|
|
1015
1105
|
check=False,
|
|
1016
1106
|
capture_output=True,
|
|
@@ -1034,7 +1124,7 @@ class UnifiedMonitorServer:
|
|
|
1034
1124
|
git_root = Path(git_root_result.stdout.strip())
|
|
1035
1125
|
|
|
1036
1126
|
# Check if file is tracked by git
|
|
1037
|
-
ls_files_result = subprocess.run(
|
|
1127
|
+
ls_files_result = subprocess.run( # nosec B603 B607
|
|
1038
1128
|
["git", "ls-files", "--error-unmatch", str(path)],
|
|
1039
1129
|
check=False,
|
|
1040
1130
|
capture_output=True,
|
|
@@ -1056,7 +1146,7 @@ class UnifiedMonitorServer:
|
|
|
1056
1146
|
)
|
|
1057
1147
|
|
|
1058
1148
|
# Get commit history for this file (last 5 commits)
|
|
1059
|
-
history_result = subprocess.run(
|
|
1149
|
+
history_result = subprocess.run( # nosec B603 B607
|
|
1060
1150
|
[
|
|
1061
1151
|
"git",
|
|
1062
1152
|
"log",
|
|
@@ -1087,7 +1177,7 @@ class UnifiedMonitorServer:
|
|
|
1087
1177
|
)
|
|
1088
1178
|
|
|
1089
1179
|
# Check for uncommitted changes
|
|
1090
|
-
uncommitted_result = subprocess.run(
|
|
1180
|
+
uncommitted_result = subprocess.run( # nosec B603 B607
|
|
1091
1181
|
["git", "diff", "HEAD", str(path)],
|
|
1092
1182
|
check=False,
|
|
1093
1183
|
capture_output=True,
|
|
@@ -1100,7 +1190,7 @@ class UnifiedMonitorServer:
|
|
|
1100
1190
|
# Get diff based on commit parameter
|
|
1101
1191
|
if commit_hash:
|
|
1102
1192
|
# Get diff for specific commit
|
|
1103
|
-
result = subprocess.run(
|
|
1193
|
+
result = subprocess.run( # nosec B603 B607
|
|
1104
1194
|
["git", "show", commit_hash, "--", str(path)],
|
|
1105
1195
|
check=False,
|
|
1106
1196
|
capture_output=True,
|
|
@@ -1469,7 +1559,7 @@ class UnifiedMonitorServer:
|
|
|
1469
1559
|
gather = asyncio.gather(*tasks_to_cancel, return_exceptions=True)
|
|
1470
1560
|
try:
|
|
1471
1561
|
loop.run_until_complete(gather)
|
|
1472
|
-
except Exception:
|
|
1562
|
+
except Exception: # nosec B110
|
|
1473
1563
|
# Some tasks might fail to cancel, that's ok
|
|
1474
1564
|
pass
|
|
1475
1565
|
|
|
@@ -214,18 +214,90 @@ def get_skills_from_mapping(agent_ids: List[str]) -> Set[str]:
|
|
|
214
214
|
return set()
|
|
215
215
|
|
|
216
216
|
|
|
217
|
+
def extract_skills_from_content(agent_file: Path) -> Set[str]:
|
|
218
|
+
"""Extract skill names from [SKILL: skill-name] markers in agent file content.
|
|
219
|
+
|
|
220
|
+
This function complements frontmatter skill extraction by finding inline
|
|
221
|
+
skill references in the agent's markdown content body.
|
|
222
|
+
|
|
223
|
+
Supports multiple formats:
|
|
224
|
+
- Bold marker: **[SKILL: skill-name]**
|
|
225
|
+
- Plain marker: [SKILL: skill-name]
|
|
226
|
+
- Backtick list: - `skill-name` - Description
|
|
227
|
+
- With spaces: [SKILL: skill-name ]
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
agent_file: Path to agent markdown file
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Set of skill names found in content body
|
|
234
|
+
|
|
235
|
+
Example:
|
|
236
|
+
>>> skills = extract_skills_from_content(Path("pm.md"))
|
|
237
|
+
>>> # Finds skills from markers like **[SKILL: mpm-delegation-patterns]**
|
|
238
|
+
>>> # Also finds from lists like - `mpm-teaching-mode` - Description
|
|
239
|
+
>>> print(f"Found {len(skills)} skills in content")
|
|
240
|
+
"""
|
|
241
|
+
try:
|
|
242
|
+
content = agent_file.read_text(encoding="utf-8")
|
|
243
|
+
except Exception as e:
|
|
244
|
+
logger.warning(f"Failed to read {agent_file}: {e}")
|
|
245
|
+
return set()
|
|
246
|
+
|
|
247
|
+
skills = set()
|
|
248
|
+
|
|
249
|
+
# Pattern 1: [SKILL: skill-name] markers (with optional markdown bold)
|
|
250
|
+
# Handles: **[SKILL: skill-name]** or [SKILL: skill-name]
|
|
251
|
+
# Pattern breakdown:
|
|
252
|
+
# - \*{0,2}: Optional bold markdown (0-2 asterisks)
|
|
253
|
+
# - \[SKILL:\s*: Opening bracket with optional whitespace
|
|
254
|
+
# - ([a-zA-Z0-9_-]+): Skill name (capture group)
|
|
255
|
+
# - \s*\]: Closing bracket with optional whitespace
|
|
256
|
+
# - \*{0,2}: Optional closing bold markdown
|
|
257
|
+
pattern1 = r"\*{0,2}\[SKILL:\s*([a-zA-Z0-9_-]+)\s*\]\*{0,2}"
|
|
258
|
+
matches1 = re.findall(pattern1, content, re.IGNORECASE)
|
|
259
|
+
skills.update(matches1)
|
|
260
|
+
|
|
261
|
+
# Pattern 2: Backtick list items with mpm-* or toolchains-* skills
|
|
262
|
+
# Handles: - `mpm-skill-name` - Description
|
|
263
|
+
# Pattern breakdown:
|
|
264
|
+
# - ^-\s+: Start with dash and whitespace (list item)
|
|
265
|
+
# - `: Opening backtick
|
|
266
|
+
# - ((?:mpm-|toolchains-|universal-)[a-zA-Z0-9_-]+): Skill name starting with prefix
|
|
267
|
+
# - `: Closing backtick
|
|
268
|
+
# - \s+-: Followed by whitespace and dash (description separator)
|
|
269
|
+
pattern2 = r"^-\s+`((?:mpm-|toolchains-|universal-)[a-zA-Z0-9_-]+)`\s+-"
|
|
270
|
+
matches2 = re.findall(pattern2, content, re.MULTILINE | re.IGNORECASE)
|
|
271
|
+
skills.update(matches2)
|
|
272
|
+
|
|
273
|
+
if skills:
|
|
274
|
+
logger.debug(
|
|
275
|
+
f"Found {len(skills)} skills from content markers in {agent_file.name}"
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
return skills
|
|
279
|
+
|
|
280
|
+
|
|
217
281
|
def get_required_skills_from_agents(agents_dir: Path) -> Set[str]:
|
|
218
282
|
"""Extract all skills referenced by deployed agents.
|
|
219
283
|
|
|
220
|
-
MAJOR CHANGE (Phase 3): Now
|
|
284
|
+
MAJOR CHANGE (Phase 3): Now uses TWO sources for skill discovery:
|
|
285
|
+
1. Frontmatter-declared skills (skills: field)
|
|
286
|
+
2. Content body markers ([SKILL: skill-name])
|
|
287
|
+
|
|
221
288
|
The static skill_to_agent_mapping.yaml is DEPRECATED. Each agent must
|
|
222
|
-
declare its skills
|
|
289
|
+
declare its skills via frontmatter OR inline markers.
|
|
223
290
|
|
|
224
291
|
This change:
|
|
225
292
|
- Eliminates dual-source complexity (frontmatter + mapping)
|
|
226
293
|
- Makes skill requirements explicit per agent
|
|
227
|
-
- Enables per-agent customization via frontmatter
|
|
294
|
+
- Enables per-agent customization via frontmatter or inline markers
|
|
228
295
|
- Removes dependency on static YAML mapping
|
|
296
|
+
- Fixes PM skills being removed as orphaned (they use inline markers)
|
|
297
|
+
|
|
298
|
+
Special handling for PM_INSTRUCTIONS.md:
|
|
299
|
+
- Also scans .claude-mpm/PM_INSTRUCTIONS.md for skill markers
|
|
300
|
+
- PM instructions are not in agents_dir but contain [SKILL: ...] references
|
|
229
301
|
|
|
230
302
|
Args:
|
|
231
303
|
agents_dir: Path to deployed agents directory (e.g., .claude/agents/)
|
|
@@ -244,37 +316,63 @@ def get_required_skills_from_agents(agents_dir: Path) -> Set[str]:
|
|
|
244
316
|
|
|
245
317
|
# Scan all agent markdown files
|
|
246
318
|
agent_files = list(agents_dir.glob("*.md"))
|
|
247
|
-
logger.debug(f"Scanning {len(agent_files)} agent files in {agents_dir}")
|
|
248
319
|
|
|
249
|
-
#
|
|
320
|
+
# Special case: Add PM_INSTRUCTIONS.md if it exists
|
|
321
|
+
# PM instructions live in .claude-mpm/ not .claude/agents/
|
|
322
|
+
pm_instructions = agents_dir.parent.parent / ".claude-mpm" / "PM_INSTRUCTIONS.md"
|
|
323
|
+
if pm_instructions.exists():
|
|
324
|
+
agent_files.append(pm_instructions)
|
|
325
|
+
logger.debug("Added PM_INSTRUCTIONS.md for skill scanning")
|
|
326
|
+
|
|
327
|
+
logger.debug(f"Scanning {len(agent_files)} agent files (including PM instructions)")
|
|
328
|
+
|
|
329
|
+
# Use TWO sources: frontmatter AND content markers
|
|
250
330
|
frontmatter_skills = set()
|
|
331
|
+
content_skills = set()
|
|
251
332
|
|
|
252
333
|
for agent_file in agent_files:
|
|
253
334
|
agent_id = agent_file.stem
|
|
254
335
|
|
|
336
|
+
# Source 1: Extract from frontmatter
|
|
255
337
|
frontmatter = parse_agent_frontmatter(agent_file)
|
|
256
|
-
|
|
338
|
+
agent_fm_skills = get_skills_from_agent(frontmatter)
|
|
257
339
|
|
|
258
|
-
if
|
|
259
|
-
frontmatter_skills.update(
|
|
340
|
+
if agent_fm_skills:
|
|
341
|
+
frontmatter_skills.update(agent_fm_skills)
|
|
260
342
|
logger.debug(
|
|
261
|
-
f"Agent {agent_id}: {len(
|
|
343
|
+
f"Agent {agent_id}: {len(agent_fm_skills)} skills from frontmatter"
|
|
262
344
|
)
|
|
263
|
-
|
|
264
|
-
|
|
345
|
+
|
|
346
|
+
# Source 2: Extract from content body [SKILL: ...] markers
|
|
347
|
+
agent_content_skills = extract_skills_from_content(agent_file)
|
|
348
|
+
|
|
349
|
+
if agent_content_skills:
|
|
350
|
+
content_skills.update(agent_content_skills)
|
|
351
|
+
logger.debug(
|
|
352
|
+
f"Agent {agent_id}: {len(agent_content_skills)} skills from content markers"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
if not agent_fm_skills and not agent_content_skills:
|
|
356
|
+
logger.debug(
|
|
357
|
+
f"Agent {agent_id}: No skills declared (checked frontmatter + content)"
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# Combine both sources
|
|
361
|
+
all_skills = frontmatter_skills | content_skills
|
|
265
362
|
|
|
266
363
|
logger.info(
|
|
267
|
-
f"Found {len(
|
|
268
|
-
f"(
|
|
364
|
+
f"Found {len(all_skills)} unique skills "
|
|
365
|
+
f"({len(frontmatter_skills)} from frontmatter, "
|
|
366
|
+
f"{len(content_skills)} from content markers)"
|
|
269
367
|
)
|
|
270
368
|
|
|
271
369
|
# Normalize skill paths: convert slashes to dashes for compatibility with deployment
|
|
272
370
|
# Some skills may use slash format, normalize to dashes
|
|
273
|
-
normalized_skills = {skill.replace("/", "-") for skill in
|
|
371
|
+
normalized_skills = {skill.replace("/", "-") for skill in all_skills}
|
|
274
372
|
|
|
275
|
-
if normalized_skills !=
|
|
373
|
+
if normalized_skills != all_skills:
|
|
276
374
|
logger.debug(
|
|
277
|
-
f"Normalized {len(
|
|
375
|
+
f"Normalized {len(all_skills)} skills to {len(normalized_skills)} "
|
|
278
376
|
"(converted slashes to dashes)"
|
|
279
377
|
)
|
|
280
378
|
|