claude-mpm 5.4.85__py3-none-any.whl → 5.6.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/agents/CLAUDE_MPM_OUTPUT_STYLE.md +8 -5
- claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md → CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
- claude_mpm/agents/PM_INSTRUCTIONS.md +101 -703
- claude_mpm/agents/WORKFLOW.md +2 -0
- claude_mpm/agents/templates/circuit-breakers.md +26 -17
- claude_mpm/cli/commands/autotodos.py +566 -0
- claude_mpm/cli/commands/commander.py +46 -0
- claude_mpm/cli/commands/hook_errors.py +60 -60
- claude_mpm/cli/commands/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init/core.py +2 -2
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/executor.py +119 -16
- claude_mpm/cli/parsers/base_parser.py +71 -1
- claude_mpm/cli/parsers/commander_parser.py +83 -0
- claude_mpm/cli/parsers/run_parser.py +10 -0
- claude_mpm/cli/startup.py +54 -16
- claude_mpm/cli/startup_display.py +72 -5
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/cli/utils.py +7 -3
- claude_mpm/commander/__init__.py +72 -0
- claude_mpm/commander/adapters/__init__.py +31 -0
- claude_mpm/commander/adapters/base.py +191 -0
- claude_mpm/commander/adapters/claude_code.py +361 -0
- claude_mpm/commander/adapters/communication.py +366 -0
- claude_mpm/commander/api/__init__.py +16 -0
- claude_mpm/commander/api/app.py +105 -0
- claude_mpm/commander/api/errors.py +112 -0
- claude_mpm/commander/api/routes/__init__.py +8 -0
- claude_mpm/commander/api/routes/events.py +184 -0
- claude_mpm/commander/api/routes/inbox.py +171 -0
- claude_mpm/commander/api/routes/messages.py +148 -0
- claude_mpm/commander/api/routes/projects.py +271 -0
- claude_mpm/commander/api/routes/sessions.py +215 -0
- claude_mpm/commander/api/routes/work.py +260 -0
- claude_mpm/commander/api/schemas.py +182 -0
- claude_mpm/commander/chat/__init__.py +7 -0
- claude_mpm/commander/chat/cli.py +107 -0
- claude_mpm/commander/chat/commands.py +96 -0
- claude_mpm/commander/chat/repl.py +310 -0
- claude_mpm/commander/config.py +49 -0
- claude_mpm/commander/config_loader.py +115 -0
- claude_mpm/commander/daemon.py +398 -0
- claude_mpm/commander/events/__init__.py +26 -0
- claude_mpm/commander/events/manager.py +332 -0
- claude_mpm/commander/frameworks/__init__.py +12 -0
- claude_mpm/commander/frameworks/base.py +143 -0
- claude_mpm/commander/frameworks/claude_code.py +58 -0
- claude_mpm/commander/frameworks/mpm.py +62 -0
- claude_mpm/commander/inbox/__init__.py +16 -0
- claude_mpm/commander/inbox/dedup.py +128 -0
- claude_mpm/commander/inbox/inbox.py +224 -0
- claude_mpm/commander/inbox/models.py +70 -0
- claude_mpm/commander/instance_manager.py +337 -0
- claude_mpm/commander/llm/__init__.py +6 -0
- claude_mpm/commander/llm/openrouter_client.py +167 -0
- claude_mpm/commander/llm/summarizer.py +70 -0
- claude_mpm/commander/models/__init__.py +18 -0
- claude_mpm/commander/models/events.py +121 -0
- claude_mpm/commander/models/project.py +162 -0
- claude_mpm/commander/models/work.py +214 -0
- claude_mpm/commander/parsing/__init__.py +20 -0
- claude_mpm/commander/parsing/extractor.py +132 -0
- claude_mpm/commander/parsing/output_parser.py +270 -0
- claude_mpm/commander/parsing/patterns.py +100 -0
- claude_mpm/commander/persistence/__init__.py +11 -0
- claude_mpm/commander/persistence/event_store.py +274 -0
- claude_mpm/commander/persistence/state_store.py +309 -0
- claude_mpm/commander/persistence/work_store.py +164 -0
- claude_mpm/commander/polling/__init__.py +13 -0
- claude_mpm/commander/polling/event_detector.py +104 -0
- claude_mpm/commander/polling/output_buffer.py +49 -0
- claude_mpm/commander/polling/output_poller.py +153 -0
- claude_mpm/commander/project_session.py +268 -0
- claude_mpm/commander/proxy/__init__.py +12 -0
- claude_mpm/commander/proxy/formatter.py +89 -0
- claude_mpm/commander/proxy/output_handler.py +191 -0
- claude_mpm/commander/proxy/relay.py +155 -0
- claude_mpm/commander/registry.py +404 -0
- claude_mpm/commander/runtime/__init__.py +10 -0
- claude_mpm/commander/runtime/executor.py +191 -0
- claude_mpm/commander/runtime/monitor.py +316 -0
- claude_mpm/commander/session/__init__.py +6 -0
- claude_mpm/commander/session/context.py +81 -0
- claude_mpm/commander/session/manager.py +59 -0
- claude_mpm/commander/tmux_orchestrator.py +361 -0
- claude_mpm/commander/web/__init__.py +1 -0
- claude_mpm/commander/work/__init__.py +30 -0
- claude_mpm/commander/work/executor.py +189 -0
- claude_mpm/commander/work/queue.py +405 -0
- claude_mpm/commander/workflow/__init__.py +27 -0
- claude_mpm/commander/workflow/event_handler.py +219 -0
- claude_mpm/commander/workflow/notifier.py +146 -0
- claude_mpm/commands/mpm-config.md +8 -0
- claude_mpm/commands/mpm-doctor.md +8 -0
- claude_mpm/commands/mpm-help.md +8 -0
- claude_mpm/commands/mpm-init.md +8 -0
- claude_mpm/commands/mpm-monitor.md +8 -0
- claude_mpm/commands/mpm-organize.md +8 -0
- claude_mpm/commands/mpm-postmortem.md +8 -0
- claude_mpm/commands/mpm-session-resume.md +9 -1
- claude_mpm/commands/mpm-status.md +8 -0
- claude_mpm/commands/mpm-ticket-view.md +8 -0
- claude_mpm/commands/mpm-version.md +8 -0
- claude_mpm/commands/mpm.md +8 -0
- claude_mpm/config/agent_presets.py +8 -7
- claude_mpm/core/config.py +5 -0
- claude_mpm/core/hook_manager.py +51 -3
- claude_mpm/core/logger.py +10 -7
- claude_mpm/core/logging_utils.py +4 -2
- claude_mpm/core/output_style_manager.py +15 -5
- claude_mpm/core/unified_config.py +10 -6
- 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/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.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/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.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/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.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/{DI7hHRFL.js → DUliQN2b.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.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/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.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.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.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 +9 -9
- claude_mpm/experimental/cli_enhancements.py +2 -1
- claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
- claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +486 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +250 -11
- claude_mpm/hooks/claude_hooks/hook_handler.py +106 -89
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
- claude_mpm/hooks/claude_hooks/installer.py +69 -5
- claude_mpm/hooks/claude_hooks/response_tracking.py +3 -1
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +20 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +14 -77
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +30 -6
- claude_mpm/hooks/session_resume_hook.py +85 -1
- claude_mpm/init.py +1 -1
- claude_mpm/scripts/claude-hook-handler.sh +36 -10
- claude_mpm/services/agents/agent_recommendation_service.py +8 -8
- 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/agents/loading/framework_agent_loader.py +75 -2
- claude_mpm/services/cli/__init__.py +3 -0
- claude_mpm/services/cli/incremental_pause_manager.py +561 -0
- claude_mpm/services/cli/session_resume_helper.py +10 -2
- claude_mpm/services/delegation_detector.py +175 -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/event_log.py +325 -0
- claude_mpm/services/infrastructure/__init__.py +4 -0
- claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
- claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
- 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/pm_skills_deployer.py +259 -87
- claude_mpm/services/skills/git_skill_source_manager.py +51 -2
- claude_mpm/services/skills/selective_skill_deployer.py +114 -16
- claude_mpm/services/skills/skill_discovery_service.py +57 -3
- claude_mpm/services/socketio/handlers/hook.py +14 -7
- claude_mpm/services/socketio/server/main.py +12 -4
- claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
- claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
- claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
- claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
- claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
- claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
- claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
- claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
- claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
- claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
- claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
- claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
- claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
- claude_mpm/skills/bundled/pm/{pm-teaching-mode → mpm-teaching-mode}/SKILL.md +2 -2
- claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
- claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
- claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
- claude_mpm/skills/skill_manager.py +4 -4
- claude_mpm-5.6.1.dist-info/METADATA +391 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/RECORD +244 -145
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.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/5shd3_w0.js +0 -24
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
- claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
- claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
- claude_mpm-5.4.85.dist-info/METADATA +0 -1023
- /claude_mpm/skills/bundled/pm/{pm-bug-reporting/pm-bug-reporting.md → mpm-bug-reporting/SKILL.md} +0 -0
- /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
- /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/top_level.txt +0 -0
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
"""PM Skills Deployer Service - Deploy bundled PM skills to projects.
|
|
2
2
|
|
|
3
|
-
WHY: PM agents require specific
|
|
3
|
+
WHY: PM agents require specific framework management skills for proper operation.
|
|
4
4
|
This service manages deployment of bundled PM skills from the claude-mpm
|
|
5
|
-
package to
|
|
5
|
+
package to the Claude Code skills directory with version tracking.
|
|
6
6
|
|
|
7
7
|
DESIGN DECISIONS:
|
|
8
|
-
- Deploys from src/claude_mpm/skills/bundled/pm/ to .claude
|
|
8
|
+
- Deploys from src/claude_mpm/skills/bundled/pm/ to .claude/skills/
|
|
9
|
+
- Skills named mpm-* (framework management skills)
|
|
10
|
+
- Direct deployment (no intermediate .claude-mpm/skills/pm/ step)
|
|
9
11
|
- Uses package-relative paths (works for both installed and dev mode)
|
|
10
|
-
- Supports
|
|
11
|
-
|
|
12
|
-
2. Flat files: skill-name.md (legacy format in .claude-mpm/templates/)
|
|
13
|
-
- Per-project deployment (NOT global like Claude Code skills)
|
|
12
|
+
- Supports directory structure: mpm-skill-name/SKILL.md
|
|
13
|
+
- Per-project deployment to .claude/skills/ (Claude Code location)
|
|
14
14
|
- Version tracking via .claude-mpm/pm_skills_registry.yaml
|
|
15
15
|
- Checksum validation for integrity verification
|
|
16
|
+
- Conflict resolution: mpm-* skills from src WIN (overwrite existing)
|
|
17
|
+
- Non-mpm-* skills in .claude/skills/ are untouched (user/git managed)
|
|
16
18
|
- Non-blocking verification (returns warnings, doesn't halt execution)
|
|
17
19
|
- Force flag to redeploy even if versions match
|
|
18
20
|
|
|
19
21
|
ARCHITECTURE:
|
|
20
22
|
1. Discovery: Find bundled PM skills in package (skills/bundled/pm/)
|
|
21
|
-
2. Deployment: Copy SKILL.md files to .claude
|
|
22
|
-
3.
|
|
23
|
-
4.
|
|
24
|
-
5.
|
|
23
|
+
2. Deployment: Copy SKILL.md files to .claude/skills/{name}/SKILL.md
|
|
24
|
+
3. Conflict Check: Overwrite mpm-* skills, preserve non-mpm-* skills
|
|
25
|
+
4. Registry: Track deployed versions and checksums
|
|
26
|
+
5. Verification: Check deployment status (non-blocking)
|
|
27
|
+
6. Updates: Compare bundled vs deployed versions
|
|
25
28
|
|
|
26
29
|
PATH RESOLUTION:
|
|
27
30
|
- Installed package: Uses __file__ to find skills/bundled/pm/
|
|
@@ -47,6 +50,46 @@ from claude_mpm.core.mixins import LoggerMixin
|
|
|
47
50
|
# Security constants
|
|
48
51
|
MAX_YAML_SIZE = 10 * 1024 * 1024 # 10MB limit to prevent YAML bombs
|
|
49
52
|
|
|
53
|
+
# Tier 1: Required PM skills that MUST be deployed for PM agent to function properly
|
|
54
|
+
# These are core framework management skills for basic PM operation
|
|
55
|
+
REQUIRED_PM_SKILLS = [
|
|
56
|
+
# Core command-based skills (new consolidated CLI)
|
|
57
|
+
"mpm",
|
|
58
|
+
"mpm-init",
|
|
59
|
+
"mpm-status",
|
|
60
|
+
"mpm-help",
|
|
61
|
+
"mpm-doctor",
|
|
62
|
+
# Legacy framework management skills
|
|
63
|
+
"mpm-git-file-tracking",
|
|
64
|
+
"mpm-pr-workflow",
|
|
65
|
+
"mpm-ticketing-integration",
|
|
66
|
+
"mpm-delegation-patterns",
|
|
67
|
+
"mpm-verification-protocols",
|
|
68
|
+
"mpm-bug-reporting",
|
|
69
|
+
"mpm-teaching-mode",
|
|
70
|
+
"mpm-agent-update-workflow",
|
|
71
|
+
"mpm-circuit-breaker-enforcement",
|
|
72
|
+
"mpm-tool-usage-guide",
|
|
73
|
+
"mpm-session-management",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
# Tier 2: Recommended skills (deployed with standard install)
|
|
77
|
+
# These provide enhanced functionality for common workflows
|
|
78
|
+
RECOMMENDED_PM_SKILLS = [
|
|
79
|
+
"mpm-config",
|
|
80
|
+
"mpm-ticket-view",
|
|
81
|
+
"mpm-session-resume",
|
|
82
|
+
"mpm-postmortem",
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
# Tier 3: Optional skills (deployed with full install)
|
|
86
|
+
# These provide additional features for advanced use cases
|
|
87
|
+
OPTIONAL_PM_SKILLS = [
|
|
88
|
+
"mpm-monitor",
|
|
89
|
+
"mpm-version",
|
|
90
|
+
"mpm-organize",
|
|
91
|
+
]
|
|
92
|
+
|
|
50
93
|
|
|
51
94
|
@dataclass
|
|
52
95
|
class PMSkillInfo:
|
|
@@ -96,6 +139,7 @@ class VerificationResult:
|
|
|
96
139
|
verified: Whether all skills are properly deployed
|
|
97
140
|
warnings: List of warning messages
|
|
98
141
|
missing_skills: List of missing skill names
|
|
142
|
+
corrupted_skills: List of corrupted skill names (checksum mismatch)
|
|
99
143
|
outdated_skills: List of outdated skill names
|
|
100
144
|
message: Summary message
|
|
101
145
|
skill_count: Total number of deployed skills
|
|
@@ -104,6 +148,7 @@ class VerificationResult:
|
|
|
104
148
|
verified: bool
|
|
105
149
|
warnings: List[str]
|
|
106
150
|
missing_skills: List[str]
|
|
151
|
+
corrupted_skills: List[str]
|
|
107
152
|
outdated_skills: List[str]
|
|
108
153
|
message: str
|
|
109
154
|
skill_count: int = 0
|
|
@@ -130,8 +175,9 @@ class PMSkillsDeployerService(LoggerMixin):
|
|
|
130
175
|
"""Deploy and manage PM skills from bundled sources to projects.
|
|
131
176
|
|
|
132
177
|
This service provides:
|
|
133
|
-
- Discovery of bundled PM skills (
|
|
134
|
-
- Deployment to .claude
|
|
178
|
+
- Discovery of bundled PM skills (mpm-* framework management skills)
|
|
179
|
+
- Deployment to .claude/skills/ (Claude Code location)
|
|
180
|
+
- Conflict resolution (mpm-* skills from src WIN)
|
|
135
181
|
- Version tracking via pm_skills_registry.yaml
|
|
136
182
|
- Checksum validation for integrity
|
|
137
183
|
- Non-blocking verification (warnings only)
|
|
@@ -140,7 +186,7 @@ class PMSkillsDeployerService(LoggerMixin):
|
|
|
140
186
|
Example:
|
|
141
187
|
>>> deployer = PMSkillsDeployerService()
|
|
142
188
|
>>> result = deployer.deploy_pm_skills(Path("/project/root"))
|
|
143
|
-
>>> print(f"Deployed {len(result.deployed)} skills")
|
|
189
|
+
>>> print(f"Deployed {len(result.deployed)} skills to .claude/skills/")
|
|
144
190
|
>>>
|
|
145
191
|
>>> verify_result = deployer.verify_pm_skills(Path("/project/root"))
|
|
146
192
|
>>> if not verify_result.verified:
|
|
@@ -246,9 +292,9 @@ class PMSkillsDeployerService(LoggerMixin):
|
|
|
246
292
|
project_dir: Project root directory
|
|
247
293
|
|
|
248
294
|
Returns:
|
|
249
|
-
Path to .claude
|
|
295
|
+
Path to .claude/skills/
|
|
250
296
|
"""
|
|
251
|
-
return project_dir / ".claude
|
|
297
|
+
return project_dir / ".claude" / "skills"
|
|
252
298
|
|
|
253
299
|
def _load_registry(self, project_dir: Path) -> Dict[str, Any]:
|
|
254
300
|
"""Load PM skills registry with security checks.
|
|
@@ -322,14 +368,12 @@ class PMSkillsDeployerService(LoggerMixin):
|
|
|
322
368
|
def _discover_bundled_pm_skills(self) -> List[Dict[str, Any]]:
|
|
323
369
|
"""Discover all PM skills in bundled templates directory.
|
|
324
370
|
|
|
325
|
-
PM skills
|
|
326
|
-
1. Directory structure: pm-skill-name/SKILL.md (new format)
|
|
327
|
-
2. Flat files: skill-name.md (legacy format for .claude-mpm/templates/)
|
|
371
|
+
PM skills follow mpm-skill-name/SKILL.md structure.
|
|
328
372
|
|
|
329
373
|
Returns:
|
|
330
374
|
List of skill dictionaries containing:
|
|
331
|
-
- name: Skill name (directory
|
|
332
|
-
- path: Full path to skill file (SKILL.md
|
|
375
|
+
- name: Skill name (directory name, e.g., mpm-git-file-tracking)
|
|
376
|
+
- path: Full path to skill file (SKILL.md)
|
|
333
377
|
- type: File type (always 'md')
|
|
334
378
|
"""
|
|
335
379
|
skills = []
|
|
@@ -340,11 +384,16 @@ class PMSkillsDeployerService(LoggerMixin):
|
|
|
340
384
|
)
|
|
341
385
|
return skills
|
|
342
386
|
|
|
343
|
-
# Scan for skill directories containing SKILL.md
|
|
387
|
+
# Scan for skill directories containing SKILL.md
|
|
344
388
|
for skill_dir in self.bundled_pm_skills_path.iterdir():
|
|
345
389
|
if not skill_dir.is_dir() or skill_dir.name.startswith("."):
|
|
346
390
|
continue
|
|
347
391
|
|
|
392
|
+
# Only process mpm-* skills (framework management)
|
|
393
|
+
if not skill_dir.name.startswith("mpm-"):
|
|
394
|
+
self.logger.debug(f"Skipping non-mpm skill: {skill_dir.name}")
|
|
395
|
+
continue
|
|
396
|
+
|
|
348
397
|
skill_file = skill_dir / "SKILL.md"
|
|
349
398
|
if skill_file.exists():
|
|
350
399
|
skills.append(
|
|
@@ -355,46 +404,91 @@ class PMSkillsDeployerService(LoggerMixin):
|
|
|
355
404
|
}
|
|
356
405
|
)
|
|
357
406
|
|
|
358
|
-
# Fallback: Scan for .md files directly (legacy format)
|
|
359
|
-
for skill_file in self.bundled_pm_skills_path.glob("*.md"):
|
|
360
|
-
if skill_file.name.startswith("."):
|
|
361
|
-
continue
|
|
362
|
-
|
|
363
|
-
skills.append(
|
|
364
|
-
{
|
|
365
|
-
"name": skill_file.stem,
|
|
366
|
-
"path": skill_file,
|
|
367
|
-
"type": "md",
|
|
368
|
-
}
|
|
369
|
-
)
|
|
370
|
-
|
|
371
407
|
self.logger.info(f"Discovered {len(skills)} bundled PM skills")
|
|
372
408
|
return skills
|
|
373
409
|
|
|
410
|
+
def _get_skills_for_tier(self, tier: str) -> List[str]:
|
|
411
|
+
"""Get list of skills to deploy based on tier.
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
tier: Deployment tier - "minimal", "standard", or "full"
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
List of skill names to deploy
|
|
418
|
+
|
|
419
|
+
Raises:
|
|
420
|
+
ValueError: If tier is invalid
|
|
421
|
+
"""
|
|
422
|
+
if tier == "minimal":
|
|
423
|
+
return REQUIRED_PM_SKILLS
|
|
424
|
+
if tier == "standard":
|
|
425
|
+
return REQUIRED_PM_SKILLS + RECOMMENDED_PM_SKILLS
|
|
426
|
+
if tier == "full":
|
|
427
|
+
return REQUIRED_PM_SKILLS + RECOMMENDED_PM_SKILLS + OPTIONAL_PM_SKILLS
|
|
428
|
+
raise ValueError(
|
|
429
|
+
f"Invalid tier '{tier}'. Must be 'minimal', 'standard', or 'full'"
|
|
430
|
+
)
|
|
431
|
+
|
|
374
432
|
def deploy_pm_skills(
|
|
375
433
|
self,
|
|
376
434
|
project_dir: Path,
|
|
377
435
|
force: bool = False,
|
|
436
|
+
tier: str = "standard",
|
|
378
437
|
progress_callback: Optional[Callable[[str, int, int], None]] = None,
|
|
379
438
|
) -> DeploymentResult:
|
|
380
|
-
"""Deploy bundled PM skills to project directory.
|
|
439
|
+
"""Deploy bundled PM skills to project directory with tier-based selection.
|
|
381
440
|
|
|
382
|
-
Copies PM skills from bundled templates to .claude
|
|
441
|
+
Copies PM skills from bundled templates to .claude/skills/{name}/SKILL.md
|
|
383
442
|
and updates registry with version and checksum information.
|
|
384
443
|
|
|
444
|
+
Deployment Tiers:
|
|
445
|
+
- "minimal": Only REQUIRED_PM_SKILLS (Tier 1 - core functionality)
|
|
446
|
+
- "standard": REQUIRED_PM_SKILLS + RECOMMENDED_PM_SKILLS (Tier 1+2 - common workflows)
|
|
447
|
+
- "full": All skills (Tier 1+2+3 - advanced features)
|
|
448
|
+
|
|
449
|
+
Conflict Resolution:
|
|
450
|
+
- mpm-* skills from src WIN (overwrite existing)
|
|
451
|
+
- Non-mpm-* skills in .claude/skills/ are untouched
|
|
452
|
+
|
|
385
453
|
Args:
|
|
386
454
|
project_dir: Project root directory
|
|
387
455
|
force: If True, redeploy even if skill already exists
|
|
456
|
+
tier: Deployment tier - "minimal", "standard" (default), or "full"
|
|
388
457
|
progress_callback: Optional callback(skill_name, current, total) for progress
|
|
389
458
|
|
|
390
459
|
Returns:
|
|
391
460
|
DeploymentResult with deployment status and details
|
|
392
461
|
|
|
393
462
|
Example:
|
|
394
|
-
>>>
|
|
395
|
-
>>>
|
|
463
|
+
>>> # Standard deployment (Tier 1 + Tier 2)
|
|
464
|
+
>>> result = deployer.deploy_pm_skills(Path("/project"))
|
|
465
|
+
>>> print(f"Deployed: {len(result.deployed)} to .claude/skills/")
|
|
466
|
+
>>>
|
|
467
|
+
>>> # Minimal deployment (Tier 1 only)
|
|
468
|
+
>>> result = deployer.deploy_pm_skills(Path("/project"), tier="minimal")
|
|
469
|
+
>>>
|
|
470
|
+
>>> # Full deployment (all tiers)
|
|
471
|
+
>>> result = deployer.deploy_pm_skills(Path("/project"), tier="full")
|
|
396
472
|
"""
|
|
397
|
-
|
|
473
|
+
# Get tier-based skill filter
|
|
474
|
+
try:
|
|
475
|
+
tier_skills = self._get_skills_for_tier(tier)
|
|
476
|
+
except ValueError as e:
|
|
477
|
+
return DeploymentResult(
|
|
478
|
+
success=False,
|
|
479
|
+
deployed=[],
|
|
480
|
+
skipped=[],
|
|
481
|
+
errors=[{"skill": "all", "error": str(e)}],
|
|
482
|
+
message=str(e),
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
# Discover all bundled skills, then filter by tier
|
|
486
|
+
all_skills = self._discover_bundled_pm_skills()
|
|
487
|
+
skills = [s for s in all_skills if s["name"] in tier_skills]
|
|
488
|
+
|
|
489
|
+
self.logger.info(
|
|
490
|
+
f"Deploying {len(skills)}/{len(all_skills)} skills for tier '{tier}'"
|
|
491
|
+
)
|
|
398
492
|
deployed = []
|
|
399
493
|
skipped = []
|
|
400
494
|
errors = []
|
|
@@ -447,8 +541,12 @@ class PMSkillsDeployerService(LoggerMixin):
|
|
|
447
541
|
if progress_callback:
|
|
448
542
|
progress_callback(skill_name, idx + 1, total_skills)
|
|
449
543
|
|
|
450
|
-
#
|
|
451
|
-
|
|
544
|
+
# Create skill directory: .claude/skills/{skill_name}/
|
|
545
|
+
skill_dir = deployment_dir / skill_name
|
|
546
|
+
skill_dir.mkdir(parents=True, exist_ok=True)
|
|
547
|
+
|
|
548
|
+
# Target path: .claude/skills/{skill_name}/SKILL.md
|
|
549
|
+
target_path = skill_dir / "SKILL.md"
|
|
452
550
|
|
|
453
551
|
# SECURITY: Validate target path
|
|
454
552
|
if not self._validate_safe_path(deployment_dir, target_path):
|
|
@@ -468,7 +566,7 @@ class PMSkillsDeployerService(LoggerMixin):
|
|
|
468
566
|
)
|
|
469
567
|
continue
|
|
470
568
|
|
|
471
|
-
# Deploy skill
|
|
569
|
+
# Deploy skill (overwrites if exists - mpm-* skills WIN)
|
|
472
570
|
shutil.copy2(source_path, target_path)
|
|
473
571
|
|
|
474
572
|
# Add to deployed list
|
|
@@ -506,7 +604,7 @@ class PMSkillsDeployerService(LoggerMixin):
|
|
|
506
604
|
|
|
507
605
|
success = len(errors) == 0
|
|
508
606
|
message = (
|
|
509
|
-
f"Deployed {len(deployed)} skills, skipped {len(skipped)}, "
|
|
607
|
+
f"Deployed {len(deployed)} skills (tier: {tier}), skipped {len(skipped)}, "
|
|
510
608
|
f"{len(errors)} errors"
|
|
511
609
|
)
|
|
512
610
|
|
|
@@ -520,76 +618,107 @@ class PMSkillsDeployerService(LoggerMixin):
|
|
|
520
618
|
message=message,
|
|
521
619
|
)
|
|
522
620
|
|
|
523
|
-
def verify_pm_skills(
|
|
524
|
-
|
|
621
|
+
def verify_pm_skills(
|
|
622
|
+
self, project_dir: Path, auto_repair: bool = True
|
|
623
|
+
) -> VerificationResult:
|
|
624
|
+
"""Verify PM skills are properly deployed with enhanced validation.
|
|
525
625
|
|
|
526
|
-
Checks
|
|
527
|
-
|
|
626
|
+
Checks ALL required PM skills for:
|
|
627
|
+
- Existence in deployment directory
|
|
628
|
+
- File integrity (non-empty, valid checksums)
|
|
629
|
+
- Version currency (compared to bundled source)
|
|
630
|
+
|
|
631
|
+
Auto-repair logic:
|
|
632
|
+
- If auto_repair=True (default), automatically deploys missing/corrupted skills
|
|
633
|
+
- Reports what was fixed in the result
|
|
528
634
|
|
|
529
635
|
Args:
|
|
530
636
|
project_dir: Project root directory
|
|
637
|
+
auto_repair: If True, auto-deploy missing/corrupted skills (default: True)
|
|
531
638
|
|
|
532
639
|
Returns:
|
|
533
|
-
VerificationResult with verification status
|
|
640
|
+
VerificationResult with detailed verification status:
|
|
641
|
+
- verified: True if all required skills are deployed and valid
|
|
642
|
+
- missing_skills: List of required skills not deployed
|
|
643
|
+
- corrupted_skills: List of skills with checksum mismatches
|
|
644
|
+
- warnings: List of warning messages
|
|
645
|
+
- skill_count: Total number of deployed skills
|
|
534
646
|
|
|
535
647
|
Example:
|
|
536
648
|
>>> result = deployer.verify_pm_skills(Path("/project"))
|
|
537
649
|
>>> if not result.verified:
|
|
538
|
-
...
|
|
539
|
-
...
|
|
650
|
+
... print(f"Missing: {result.missing_skills}")
|
|
651
|
+
... print(f"Corrupted: {result.corrupted_skills}")
|
|
540
652
|
"""
|
|
541
653
|
warnings = []
|
|
542
654
|
missing_skills = []
|
|
655
|
+
corrupted_skills = []
|
|
543
656
|
outdated_skills = []
|
|
544
657
|
|
|
545
658
|
# Check if registry exists
|
|
546
659
|
registry = self._load_registry(project_dir)
|
|
547
|
-
if not registry:
|
|
548
|
-
warnings.append("PM skills registry not found or invalid")
|
|
549
|
-
missing_skills.append("all")
|
|
550
|
-
return VerificationResult(
|
|
551
|
-
verified=False,
|
|
552
|
-
warnings=warnings,
|
|
553
|
-
missing_skills=missing_skills,
|
|
554
|
-
outdated_skills=outdated_skills,
|
|
555
|
-
message="PM skills not deployed. Run 'claude-mpm init' to deploy.",
|
|
556
|
-
skill_count=0,
|
|
557
|
-
)
|
|
558
|
-
|
|
559
|
-
# Check each registered skill exists
|
|
560
660
|
deployment_dir = self._get_deployment_dir(project_dir)
|
|
561
|
-
|
|
661
|
+
deployed_skills_data = registry.get("skills", []) if registry else []
|
|
562
662
|
|
|
563
|
-
for
|
|
564
|
-
|
|
565
|
-
skill_file = deployment_dir / f"{skill_name}.md"
|
|
663
|
+
# Build lookup for deployed skills
|
|
664
|
+
deployed_lookup = {skill["name"]: skill for skill in deployed_skills_data}
|
|
566
665
|
|
|
666
|
+
# Check ALL required PM skills
|
|
667
|
+
for required_skill in REQUIRED_PM_SKILLS:
|
|
668
|
+
# Check if skill is in registry
|
|
669
|
+
if required_skill not in deployed_lookup:
|
|
670
|
+
warnings.append(f"Required PM skill missing: {required_skill}")
|
|
671
|
+
missing_skills.append(required_skill)
|
|
672
|
+
continue
|
|
673
|
+
|
|
674
|
+
# Check if skill file exists
|
|
675
|
+
skill_file = deployment_dir / required_skill / "SKILL.md"
|
|
567
676
|
if not skill_file.exists():
|
|
568
|
-
warnings.append(
|
|
569
|
-
|
|
677
|
+
warnings.append(
|
|
678
|
+
f"Required PM skill file missing: {required_skill}/SKILL.md"
|
|
679
|
+
)
|
|
680
|
+
missing_skills.append(required_skill)
|
|
681
|
+
continue
|
|
682
|
+
|
|
683
|
+
# Check if skill file is empty/corrupted
|
|
684
|
+
try:
|
|
685
|
+
file_size = skill_file.stat().st_size
|
|
686
|
+
if file_size == 0:
|
|
687
|
+
warnings.append(
|
|
688
|
+
f"Required PM skill file is empty: {required_skill}/SKILL.md"
|
|
689
|
+
)
|
|
690
|
+
corrupted_skills.append(required_skill)
|
|
691
|
+
continue
|
|
692
|
+
except OSError as e:
|
|
693
|
+
warnings.append(
|
|
694
|
+
f"Cannot read required PM skill file: {required_skill}/SKILL.md - {e}"
|
|
695
|
+
)
|
|
696
|
+
corrupted_skills.append(required_skill)
|
|
570
697
|
continue
|
|
571
698
|
|
|
572
699
|
# Verify checksum
|
|
700
|
+
deployed_skill = deployed_lookup[required_skill]
|
|
573
701
|
current_checksum = self._compute_checksum(skill_file)
|
|
574
|
-
expected_checksum =
|
|
702
|
+
expected_checksum = deployed_skill.get("checksum", "")
|
|
575
703
|
|
|
576
704
|
if current_checksum != expected_checksum:
|
|
577
705
|
warnings.append(
|
|
578
|
-
f"
|
|
706
|
+
f"Required PM skill checksum mismatch: {required_skill} (file may be corrupted)"
|
|
579
707
|
)
|
|
580
|
-
|
|
708
|
+
corrupted_skills.append(required_skill)
|
|
581
709
|
|
|
582
|
-
# Check for available updates
|
|
710
|
+
# Check for available updates (bundled skills newer than deployed)
|
|
583
711
|
bundled_skills = {s["name"]: s for s in self._discover_bundled_pm_skills()}
|
|
584
712
|
for skill_name, bundled_skill in bundled_skills.items():
|
|
713
|
+
# Skip non-required skills
|
|
714
|
+
if skill_name not in REQUIRED_PM_SKILLS:
|
|
715
|
+
continue
|
|
716
|
+
|
|
585
717
|
# Find corresponding deployed skill
|
|
586
|
-
deployed_skill =
|
|
587
|
-
(s for s in deployed_skills if s["name"] == skill_name), None
|
|
588
|
-
)
|
|
718
|
+
deployed_skill = deployed_lookup.get(skill_name)
|
|
589
719
|
|
|
590
720
|
if not deployed_skill:
|
|
591
|
-
|
|
592
|
-
missing_skills.append(skill_name)
|
|
721
|
+
# Already tracked as missing
|
|
593
722
|
continue
|
|
594
723
|
|
|
595
724
|
# Check if checksums differ
|
|
@@ -597,23 +726,66 @@ class PMSkillsDeployerService(LoggerMixin):
|
|
|
597
726
|
deployed_checksum = deployed_skill.get("checksum", "")
|
|
598
727
|
|
|
599
728
|
if bundled_checksum != deployed_checksum:
|
|
600
|
-
|
|
601
|
-
|
|
729
|
+
# Don't add to outdated_skills if already in corrupted_skills
|
|
730
|
+
if skill_name not in corrupted_skills:
|
|
731
|
+
warnings.append(f"PM skill update available: {skill_name}")
|
|
732
|
+
outdated_skills.append(skill_name)
|
|
733
|
+
|
|
734
|
+
# Auto-repair if enabled and issues found
|
|
735
|
+
repaired_skills = []
|
|
736
|
+
if auto_repair and (missing_skills or corrupted_skills):
|
|
737
|
+
self.logger.info(
|
|
738
|
+
f"Auto-repairing PM skills: {len(missing_skills)} missing, "
|
|
739
|
+
f"{len(corrupted_skills)} corrupted"
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
# Deploy missing and corrupted skills
|
|
743
|
+
repair_result = self.deploy_pm_skills(project_dir, force=True)
|
|
744
|
+
|
|
745
|
+
if repair_result.success:
|
|
746
|
+
repaired_skills = repair_result.deployed
|
|
747
|
+
self.logger.info(f"Auto-repaired {len(repaired_skills)} PM skills")
|
|
748
|
+
|
|
749
|
+
# Remove repaired skills from missing/corrupted lists
|
|
750
|
+
missing_skills = [s for s in missing_skills if s not in repaired_skills]
|
|
751
|
+
corrupted_skills = [
|
|
752
|
+
s for s in corrupted_skills if s not in repaired_skills
|
|
753
|
+
]
|
|
754
|
+
|
|
755
|
+
# Update warnings
|
|
756
|
+
if repaired_skills:
|
|
757
|
+
warnings.append(
|
|
758
|
+
f"Auto-repaired {len(repaired_skills)} PM skills: {', '.join(repaired_skills)}"
|
|
759
|
+
)
|
|
760
|
+
else:
|
|
761
|
+
warnings.append(
|
|
762
|
+
f"Auto-repair failed: {len(repair_result.errors)} errors"
|
|
763
|
+
)
|
|
764
|
+
self.logger.error(
|
|
765
|
+
f"Auto-repair failed with errors: {repair_result.errors}"
|
|
766
|
+
)
|
|
602
767
|
|
|
603
|
-
|
|
768
|
+
# Determine verification status
|
|
769
|
+
verified = len(missing_skills) == 0 and len(corrupted_skills) == 0
|
|
604
770
|
|
|
771
|
+
# Build message
|
|
605
772
|
if verified:
|
|
606
|
-
|
|
773
|
+
if repaired_skills:
|
|
774
|
+
message = f"All PM skills verified (auto-repaired {len(repaired_skills)} skills)"
|
|
775
|
+
else:
|
|
776
|
+
message = "All PM skills verified and up-to-date"
|
|
607
777
|
else:
|
|
608
|
-
|
|
778
|
+
issue_count = len(missing_skills) + len(corrupted_skills)
|
|
779
|
+
message = f"{issue_count} PM skill issues found"
|
|
609
780
|
|
|
610
781
|
return VerificationResult(
|
|
611
782
|
verified=verified,
|
|
612
783
|
warnings=warnings,
|
|
613
784
|
missing_skills=missing_skills,
|
|
785
|
+
corrupted_skills=corrupted_skills,
|
|
614
786
|
outdated_skills=outdated_skills,
|
|
615
787
|
message=message,
|
|
616
|
-
skill_count=len(
|
|
788
|
+
skill_count=len(deployed_skills_data),
|
|
617
789
|
)
|
|
618
790
|
|
|
619
791
|
def get_deployed_skills(self, project_dir: Path) -> List[PMSkillInfo]:
|
|
@@ -636,10 +808,10 @@ class PMSkillsDeployerService(LoggerMixin):
|
|
|
636
808
|
skills = []
|
|
637
809
|
for skill_data in registry.get("skills", []):
|
|
638
810
|
skill_name = skill_data["name"]
|
|
639
|
-
deployed_path = deployment_dir /
|
|
811
|
+
deployed_path = deployment_dir / skill_name / "SKILL.md"
|
|
640
812
|
|
|
641
813
|
# Find source path (may not exist if bundled skills changed)
|
|
642
|
-
source_path = self.bundled_pm_skills_path /
|
|
814
|
+
source_path = self.bundled_pm_skills_path / skill_name / "SKILL.md"
|
|
643
815
|
|
|
644
816
|
skills.append(
|
|
645
817
|
PMSkillInfo(
|
|
@@ -682,7 +682,7 @@ class GitSkillSourceManager:
|
|
|
682
682
|
try:
|
|
683
683
|
with open(etag_cache_file, encoding="utf-8") as f:
|
|
684
684
|
etag_cache = json.load(f)
|
|
685
|
-
except Exception:
|
|
685
|
+
except Exception: # nosec B110 - intentional: proceed without cache on read failure
|
|
686
686
|
pass
|
|
687
687
|
|
|
688
688
|
cached_etag = etag_cache.get(str(local_path))
|
|
@@ -1163,6 +1163,10 @@ class GitSkillSourceManager:
|
|
|
1163
1163
|
) -> List[str]:
|
|
1164
1164
|
"""Remove skills from target directory that aren't in the filtered skill list.
|
|
1165
1165
|
|
|
1166
|
+
CRITICAL: Only removes MPM-managed skills (those in our cache). Custom user skills
|
|
1167
|
+
are preserved. This prevents accidental deletion of user-created skills that were
|
|
1168
|
+
never part of MPM's skill repository.
|
|
1169
|
+
|
|
1166
1170
|
Uses fuzzy matching to handle both exact deployment names and short skill names.
|
|
1167
1171
|
For example:
|
|
1168
1172
|
- "toolchains-python-frameworks-flask" (deployed dir) matches "flask" (filter)
|
|
@@ -1213,6 +1217,40 @@ class GitSkillSourceManager:
|
|
|
1213
1217
|
|
|
1214
1218
|
return False
|
|
1215
1219
|
|
|
1220
|
+
def is_mpm_managed_skill(skill_dir_name: str) -> bool:
|
|
1221
|
+
"""Check if skill is managed by MPM (exists in our cache).
|
|
1222
|
+
|
|
1223
|
+
Custom user skills (not in cache) are NEVER deleted, even if not in filter.
|
|
1224
|
+
Only MPM-managed skills (in cache but not in filter) are candidates for removal.
|
|
1225
|
+
|
|
1226
|
+
Args:
|
|
1227
|
+
skill_dir_name: Name of deployed skill directory
|
|
1228
|
+
|
|
1229
|
+
Returns:
|
|
1230
|
+
True if skill exists in MPM cache (MPM-managed), False if custom user skill
|
|
1231
|
+
"""
|
|
1232
|
+
# Check all configured skill sources for this skill
|
|
1233
|
+
for source in self.config.get_enabled_sources():
|
|
1234
|
+
cache_path = self._get_source_cache_path(source)
|
|
1235
|
+
if not cache_path.exists():
|
|
1236
|
+
continue
|
|
1237
|
+
|
|
1238
|
+
# Check if this skill directory exists anywhere in the cache
|
|
1239
|
+
# Use glob to find matching directories recursively
|
|
1240
|
+
matches = list(cache_path.rglob(f"*{skill_dir_name}*"))
|
|
1241
|
+
if matches:
|
|
1242
|
+
# Found in cache - this is MPM-managed
|
|
1243
|
+
self.logger.debug(
|
|
1244
|
+
f"Skill '{skill_dir_name}' found in cache at {matches[0]} - MPM-managed"
|
|
1245
|
+
)
|
|
1246
|
+
return True
|
|
1247
|
+
|
|
1248
|
+
# Not found in any cache - this is a custom user skill
|
|
1249
|
+
self.logger.debug(
|
|
1250
|
+
f"Skill '{skill_dir_name}' not found in cache - custom user skill, preserving"
|
|
1251
|
+
)
|
|
1252
|
+
return False
|
|
1253
|
+
|
|
1216
1254
|
# Check each directory in target_dir
|
|
1217
1255
|
if not target_dir.exists():
|
|
1218
1256
|
return removed_skills
|
|
@@ -1229,6 +1267,15 @@ class GitSkillSourceManager:
|
|
|
1229
1267
|
|
|
1230
1268
|
# Check if this skill directory should be kept (fuzzy matching)
|
|
1231
1269
|
if not should_keep_skill(item.name):
|
|
1270
|
+
# CRITICAL: Check if this is an MPM-managed skill before deletion
|
|
1271
|
+
if not is_mpm_managed_skill(item.name):
|
|
1272
|
+
# This is a custom user skill - NEVER delete
|
|
1273
|
+
self.logger.debug(
|
|
1274
|
+
f"Preserving custom user skill (not in MPM cache): {item.name}"
|
|
1275
|
+
)
|
|
1276
|
+
continue
|
|
1277
|
+
|
|
1278
|
+
# It's MPM-managed but not in filter - safe to remove
|
|
1232
1279
|
try:
|
|
1233
1280
|
# Security: Validate path is within target_dir
|
|
1234
1281
|
if not self._validate_safe_path(target_dir, item):
|
|
@@ -1244,7 +1291,9 @@ class GitSkillSourceManager:
|
|
|
1244
1291
|
shutil.rmtree(item)
|
|
1245
1292
|
|
|
1246
1293
|
removed_skills.append(item.name)
|
|
1247
|
-
self.logger.info(
|
|
1294
|
+
self.logger.info(
|
|
1295
|
+
f"Removed orphaned MPM-managed skill: {item.name}"
|
|
1296
|
+
)
|
|
1248
1297
|
|
|
1249
1298
|
except Exception as e:
|
|
1250
1299
|
self.logger.warning(
|