claude-mpm 5.4.85__py3-none-any.whl → 5.6.76__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 +109 -706
- claude_mpm/agents/WORKFLOW.md +2 -0
- claude_mpm/agents/templates/circuit-breakers.md +26 -17
- claude_mpm/auth/__init__.py +35 -0
- claude_mpm/auth/callback_server.py +328 -0
- claude_mpm/auth/models.py +104 -0
- claude_mpm/auth/oauth_manager.py +266 -0
- claude_mpm/auth/providers/__init__.py +12 -0
- claude_mpm/auth/providers/base.py +165 -0
- claude_mpm/auth/providers/google.py +261 -0
- claude_mpm/auth/token_storage.py +252 -0
- claude_mpm/cli/commands/autotodos.py +566 -0
- claude_mpm/cli/commands/commander.py +216 -0
- claude_mpm/cli/commands/hook_errors.py +60 -60
- claude_mpm/cli/commands/mcp.py +29 -17
- claude_mpm/cli/commands/mcp_command_router.py +39 -0
- claude_mpm/cli/commands/mcp_service_commands.py +304 -0
- claude_mpm/cli/commands/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init/core.py +2 -2
- claude_mpm/cli/commands/oauth.py +481 -0
- claude_mpm/cli/commands/run.py +35 -3
- claude_mpm/cli/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +5 -3
- claude_mpm/cli/executor.py +128 -16
- claude_mpm/cli/helpers.py +1 -1
- claude_mpm/cli/parsers/base_parser.py +84 -1
- claude_mpm/cli/parsers/commander_parser.py +116 -0
- claude_mpm/cli/parsers/mcp_parser.py +79 -0
- claude_mpm/cli/parsers/oauth_parser.py +165 -0
- claude_mpm/cli/parsers/run_parser.py +10 -0
- claude_mpm/cli/parsers/skill_source_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +5 -0
- claude_mpm/cli/startup.py +345 -40
- claude_mpm/cli/startup_display.py +76 -7
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/cli/startup_migrations.py +236 -0
- claude_mpm/cli/utils.py +7 -3
- claude_mpm/commander/__init__.py +78 -0
- claude_mpm/commander/adapters/__init__.py +60 -0
- claude_mpm/commander/adapters/auggie.py +260 -0
- claude_mpm/commander/adapters/base.py +288 -0
- claude_mpm/commander/adapters/claude_code.py +392 -0
- claude_mpm/commander/adapters/codex.py +237 -0
- claude_mpm/commander/adapters/communication.py +366 -0
- claude_mpm/commander/adapters/example_usage.py +310 -0
- claude_mpm/commander/adapters/mpm.py +389 -0
- claude_mpm/commander/adapters/registry.py +204 -0
- claude_mpm/commander/api/__init__.py +16 -0
- claude_mpm/commander/api/app.py +121 -0
- claude_mpm/commander/api/errors.py +133 -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 +226 -0
- claude_mpm/commander/api/routes/work.py +296 -0
- claude_mpm/commander/api/schemas.py +186 -0
- claude_mpm/commander/chat/__init__.py +7 -0
- claude_mpm/commander/chat/cli.py +149 -0
- claude_mpm/commander/chat/commands.py +124 -0
- claude_mpm/commander/chat/repl.py +1957 -0
- claude_mpm/commander/config.py +51 -0
- claude_mpm/commander/config_loader.py +115 -0
- claude_mpm/commander/core/__init__.py +10 -0
- claude_mpm/commander/core/block_manager.py +325 -0
- claude_mpm/commander/core/response_manager.py +323 -0
- claude_mpm/commander/daemon.py +603 -0
- claude_mpm/commander/env_loader.py +59 -0
- claude_mpm/commander/events/__init__.py +26 -0
- claude_mpm/commander/events/manager.py +392 -0
- claude_mpm/commander/frameworks/__init__.py +12 -0
- claude_mpm/commander/frameworks/base.py +233 -0
- claude_mpm/commander/frameworks/claude_code.py +58 -0
- claude_mpm/commander/frameworks/mpm.py +57 -0
- claude_mpm/commander/git/__init__.py +5 -0
- claude_mpm/commander/git/worktree_manager.py +212 -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 +868 -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/memory/__init__.py +45 -0
- claude_mpm/commander/memory/compression.py +347 -0
- claude_mpm/commander/memory/embeddings.py +230 -0
- claude_mpm/commander/memory/entities.py +310 -0
- claude_mpm/commander/memory/example_usage.py +290 -0
- claude_mpm/commander/memory/integration.py +325 -0
- claude_mpm/commander/memory/search.py +381 -0
- claude_mpm/commander/memory/store.py +657 -0
- claude_mpm/commander/models/__init__.py +18 -0
- claude_mpm/commander/models/events.py +127 -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 +403 -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 +410 -0
- claude_mpm/commander/runtime/__init__.py +10 -0
- claude_mpm/commander/runtime/executor.py +191 -0
- claude_mpm/commander/runtime/monitor.py +346 -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 +362 -0
- claude_mpm/commander/web/__init__.py +1 -0
- claude_mpm/commander/work/__init__.py +30 -0
- claude_mpm/commander/work/executor.py +207 -0
- claude_mpm/commander/work/queue.py +405 -0
- claude_mpm/commander/workflow/__init__.py +27 -0
- claude_mpm/commander/workflow/event_handler.py +241 -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/config/skill_sources.py +16 -0
- claude_mpm/constants.py +5 -0
- claude_mpm/core/claude_runner.py +152 -0
- claude_mpm/core/config.py +35 -22
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/hook_manager.py +53 -4
- claude_mpm/core/interactive_session.py +5 -4
- claude_mpm/core/logger.py +26 -9
- claude_mpm/core/logging_utils.py +39 -13
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/output_style_manager.py +52 -12
- claude_mpm/core/socketio_pool.py +47 -15
- claude_mpm/core/unified_config.py +10 -6
- claude_mpm/core/unified_paths.py +68 -80
- 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 +485 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +466 -136
- claude_mpm/hooks/claude_hooks/hook_handler.py +204 -104
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
- claude_mpm/hooks/claude_hooks/installer.py +291 -59
- claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
- claude_mpm/hooks/claude_hooks/response_tracking.py +43 -60
- claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +41 -26
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +38 -105
- claude_mpm/hooks/claude_hooks/services/container.py +326 -0
- claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +75 -77
- claude_mpm/hooks/session_resume_hook.py +89 -1
- claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
- claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
- claude_mpm/init.py +22 -15
- claude_mpm/mcp/__init__.py +9 -0
- claude_mpm/mcp/google_workspace_server.py +610 -0
- claude_mpm/scripts/claude-hook-handler.sh +46 -19
- claude_mpm/services/agents/agent_recommendation_service.py +8 -8
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- 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/agents/single_tier_deployment_service.py +4 -4
- 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/command_deployment_service.py +44 -26
- 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/hook_installer_service.py +77 -8
- 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/mcp_config_manager.py +99 -19
- claude_mpm/services/mcp_service_registry.py +294 -0
- claude_mpm/services/monitor/daemon_manager.py +15 -4
- claude_mpm/services/monitor/management/lifecycle.py +8 -2
- claude_mpm/services/monitor/server.py +111 -16
- claude_mpm/services/pm_skills_deployer.py +261 -87
- claude_mpm/services/skills/git_skill_source_manager.py +130 -10
- claude_mpm/services/skills/selective_skill_deployer.py +142 -16
- claude_mpm/services/skills/skill_discovery_service.py +74 -4
- claude_mpm/services/skills_deployer.py +31 -5
- claude_mpm/services/socketio/handlers/hook.py +14 -7
- claude_mpm/services/socketio/server/main.py +12 -4
- claude_mpm/skills/__init__.py +2 -1
- 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-pause/SKILL.md +170 -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/registry.py +295 -90
- claude_mpm/skills/skill_manager.py +4 -4
- claude_mpm-5.6.76.dist-info/METADATA +416 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/RECORD +312 -175
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/WHEEL +1 -1
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/entry_points.txt +2 -0
- 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.76.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.76.dist-info}/top_level.txt +0 -0
|
@@ -16,6 +16,7 @@ Trade-offs:
|
|
|
16
16
|
- Flexibility: Easy to extend with skills-specific features
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
+
import os
|
|
19
20
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
20
21
|
from datetime import datetime, timezone
|
|
21
22
|
from pathlib import Path
|
|
@@ -32,6 +33,46 @@ from claude_mpm.services.skills.skill_discovery_service import SkillDiscoverySer
|
|
|
32
33
|
logger = get_logger(__name__)
|
|
33
34
|
|
|
34
35
|
|
|
36
|
+
def _get_github_token(source: Optional[SkillSource] = None) -> Optional[str]:
|
|
37
|
+
"""Get GitHub token with source-specific override support.
|
|
38
|
+
|
|
39
|
+
Priority: source.token > GITHUB_TOKEN > GH_TOKEN
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
source: Optional SkillSource to check for per-source token
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
GitHub token if found, None otherwise
|
|
46
|
+
|
|
47
|
+
Token Resolution:
|
|
48
|
+
1. If source has token starting with "$", resolve as env var
|
|
49
|
+
2. If source has direct token, use it (not recommended for security)
|
|
50
|
+
3. Fall back to GITHUB_TOKEN env var
|
|
51
|
+
4. Fall back to GH_TOKEN env var
|
|
52
|
+
5. Return None if no token found
|
|
53
|
+
|
|
54
|
+
Security Note:
|
|
55
|
+
Token is never logged or printed to avoid exposure.
|
|
56
|
+
Direct tokens in config are discouraged - use env var refs ($VAR_NAME).
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
>>> source = SkillSource(..., token="$PRIVATE_TOKEN")
|
|
60
|
+
>>> token = _get_github_token(source) # Resolves $PRIVATE_TOKEN from env
|
|
61
|
+
>>> token = _get_github_token() # Falls back to GITHUB_TOKEN
|
|
62
|
+
"""
|
|
63
|
+
# Priority 1: Per-source token (env var reference or direct)
|
|
64
|
+
if source and source.token:
|
|
65
|
+
if source.token.startswith("$"):
|
|
66
|
+
# Env var reference: $VAR_NAME -> os.environ.get("VAR_NAME")
|
|
67
|
+
env_var_name = source.token[1:]
|
|
68
|
+
return os.environ.get(env_var_name)
|
|
69
|
+
# Direct token (not recommended but supported)
|
|
70
|
+
return source.token
|
|
71
|
+
|
|
72
|
+
# Priority 2-3: Global environment variables
|
|
73
|
+
return os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
|
|
74
|
+
|
|
75
|
+
|
|
35
76
|
class GitSkillSourceManager:
|
|
36
77
|
"""Manages multiple Git-based skill sources with priority resolution.
|
|
37
78
|
|
|
@@ -217,9 +258,21 @@ class GitSkillSourceManager:
|
|
|
217
258
|
)
|
|
218
259
|
|
|
219
260
|
# Discover skills in cache
|
|
261
|
+
self.logger.debug(f"Scanning cache path for skills: {cache_path}")
|
|
220
262
|
discovery_service = SkillDiscoveryService(cache_path)
|
|
221
263
|
discovered_skills = discovery_service.discover_skills()
|
|
222
264
|
|
|
265
|
+
# Log discovery results
|
|
266
|
+
if len(discovered_skills) == 0:
|
|
267
|
+
self.logger.info(
|
|
268
|
+
f"No SKILL.md files found in {cache_path}. "
|
|
269
|
+
"Ensure your skill source has SKILL.md files with valid frontmatter."
|
|
270
|
+
)
|
|
271
|
+
else:
|
|
272
|
+
self.logger.debug(
|
|
273
|
+
f"Successfully parsed {len(discovered_skills)} skills from {cache_path}"
|
|
274
|
+
)
|
|
275
|
+
|
|
223
276
|
# Build result
|
|
224
277
|
result = {
|
|
225
278
|
"synced": True,
|
|
@@ -469,7 +522,7 @@ class GitSkillSourceManager:
|
|
|
469
522
|
# Step 1: Discover all files via GitHub Tree API (single request)
|
|
470
523
|
# This discovers the COMPLETE repository structure (272 files for skills)
|
|
471
524
|
all_files = self._discover_repository_files_via_tree_api(
|
|
472
|
-
owner_repo, source.branch
|
|
525
|
+
owner_repo, source.branch, source
|
|
473
526
|
)
|
|
474
527
|
|
|
475
528
|
if not all_files:
|
|
@@ -504,7 +557,7 @@ class GitSkillSourceManager:
|
|
|
504
557
|
raw_url = f"https://raw.githubusercontent.com/{owner_repo}/{source.branch}/{file_path}"
|
|
505
558
|
cache_file = cache_path / file_path
|
|
506
559
|
future = executor.submit(
|
|
507
|
-
self._download_file_with_etag, raw_url, cache_file, force
|
|
560
|
+
self._download_file_with_etag, raw_url, cache_file, force, source
|
|
508
561
|
)
|
|
509
562
|
future_to_file[future] = file_path
|
|
510
563
|
|
|
@@ -533,7 +586,7 @@ class GitSkillSourceManager:
|
|
|
533
586
|
return files_updated, files_cached
|
|
534
587
|
|
|
535
588
|
def _discover_repository_files_via_tree_api(
|
|
536
|
-
self, owner_repo: str, branch: str
|
|
589
|
+
self, owner_repo: str, branch: str, source: Optional[SkillSource] = None
|
|
537
590
|
) -> List[str]:
|
|
538
591
|
"""Discover all files in repository using GitHub Git Tree API.
|
|
539
592
|
|
|
@@ -596,9 +649,17 @@ class GitSkillSourceManager:
|
|
|
596
649
|
)
|
|
597
650
|
self.logger.debug(f"Fetching commit SHA from {refs_url}")
|
|
598
651
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
)
|
|
652
|
+
# Build headers with authentication if token available
|
|
653
|
+
headers = {"Accept": "application/vnd.github+json"}
|
|
654
|
+
token = _get_github_token(source)
|
|
655
|
+
if token:
|
|
656
|
+
headers["Authorization"] = f"token {token}"
|
|
657
|
+
if source and source.token:
|
|
658
|
+
self.logger.debug(f"Using source-specific token for {source.id}")
|
|
659
|
+
else:
|
|
660
|
+
self.logger.debug("Using GitHub token for authentication")
|
|
661
|
+
|
|
662
|
+
refs_response = requests.get(refs_url, headers=headers, timeout=30)
|
|
602
663
|
|
|
603
664
|
# Check for rate limiting
|
|
604
665
|
if refs_response.status_code == 403:
|
|
@@ -621,7 +682,7 @@ class GitSkillSourceManager:
|
|
|
621
682
|
self.logger.debug(f"Fetching recursive tree from {tree_url}")
|
|
622
683
|
tree_response = requests.get(
|
|
623
684
|
tree_url,
|
|
624
|
-
headers=
|
|
685
|
+
headers=headers, # Reuse headers with auth from Step 1
|
|
625
686
|
params=params,
|
|
626
687
|
timeout=30,
|
|
627
688
|
)
|
|
@@ -652,7 +713,11 @@ class GitSkillSourceManager:
|
|
|
652
713
|
return all_files
|
|
653
714
|
|
|
654
715
|
def _download_file_with_etag(
|
|
655
|
-
self,
|
|
716
|
+
self,
|
|
717
|
+
url: str,
|
|
718
|
+
local_path: Path,
|
|
719
|
+
force: bool = False,
|
|
720
|
+
source: Optional[SkillSource] = None,
|
|
656
721
|
) -> bool:
|
|
657
722
|
"""Download file from URL with ETag caching (thread-safe).
|
|
658
723
|
|
|
@@ -660,6 +725,7 @@ class GitSkillSourceManager:
|
|
|
660
725
|
url: Raw GitHub URL
|
|
661
726
|
local_path: Local file path to save to
|
|
662
727
|
force: Force download even if cached
|
|
728
|
+
source: Optional SkillSource for token resolution
|
|
663
729
|
|
|
664
730
|
Returns:
|
|
665
731
|
True if file was updated, False if cached
|
|
@@ -682,7 +748,7 @@ class GitSkillSourceManager:
|
|
|
682
748
|
try:
|
|
683
749
|
with open(etag_cache_file, encoding="utf-8") as f:
|
|
684
750
|
etag_cache = json.load(f)
|
|
685
|
-
except Exception:
|
|
751
|
+
except Exception: # nosec B110 - intentional: proceed without cache on read failure
|
|
686
752
|
pass
|
|
687
753
|
|
|
688
754
|
cached_etag = etag_cache.get(str(local_path))
|
|
@@ -692,6 +758,11 @@ class GitSkillSourceManager:
|
|
|
692
758
|
if cached_etag and not force:
|
|
693
759
|
headers["If-None-Match"] = cached_etag
|
|
694
760
|
|
|
761
|
+
# Add GitHub authentication if token available
|
|
762
|
+
token = _get_github_token(source)
|
|
763
|
+
if token:
|
|
764
|
+
headers["Authorization"] = f"token {token}"
|
|
765
|
+
|
|
695
766
|
try:
|
|
696
767
|
response = requests.get(url, headers=headers, timeout=30)
|
|
697
768
|
|
|
@@ -1163,6 +1234,10 @@ class GitSkillSourceManager:
|
|
|
1163
1234
|
) -> List[str]:
|
|
1164
1235
|
"""Remove skills from target directory that aren't in the filtered skill list.
|
|
1165
1236
|
|
|
1237
|
+
CRITICAL: Only removes MPM-managed skills (those in our cache). Custom user skills
|
|
1238
|
+
are preserved. This prevents accidental deletion of user-created skills that were
|
|
1239
|
+
never part of MPM's skill repository.
|
|
1240
|
+
|
|
1166
1241
|
Uses fuzzy matching to handle both exact deployment names and short skill names.
|
|
1167
1242
|
For example:
|
|
1168
1243
|
- "toolchains-python-frameworks-flask" (deployed dir) matches "flask" (filter)
|
|
@@ -1213,6 +1288,40 @@ class GitSkillSourceManager:
|
|
|
1213
1288
|
|
|
1214
1289
|
return False
|
|
1215
1290
|
|
|
1291
|
+
def is_mpm_managed_skill(skill_dir_name: str) -> bool:
|
|
1292
|
+
"""Check if skill is managed by MPM (exists in our cache).
|
|
1293
|
+
|
|
1294
|
+
Custom user skills (not in cache) are NEVER deleted, even if not in filter.
|
|
1295
|
+
Only MPM-managed skills (in cache but not in filter) are candidates for removal.
|
|
1296
|
+
|
|
1297
|
+
Args:
|
|
1298
|
+
skill_dir_name: Name of deployed skill directory
|
|
1299
|
+
|
|
1300
|
+
Returns:
|
|
1301
|
+
True if skill exists in MPM cache (MPM-managed), False if custom user skill
|
|
1302
|
+
"""
|
|
1303
|
+
# Check all configured skill sources for this skill
|
|
1304
|
+
for source in self.config.get_enabled_sources():
|
|
1305
|
+
cache_path = self._get_source_cache_path(source)
|
|
1306
|
+
if not cache_path.exists():
|
|
1307
|
+
continue
|
|
1308
|
+
|
|
1309
|
+
# Check if this skill directory exists anywhere in the cache
|
|
1310
|
+
# Use glob to find matching directories recursively
|
|
1311
|
+
matches = list(cache_path.rglob(f"*{skill_dir_name}*"))
|
|
1312
|
+
if matches:
|
|
1313
|
+
# Found in cache - this is MPM-managed
|
|
1314
|
+
self.logger.debug(
|
|
1315
|
+
f"Skill '{skill_dir_name}' found in cache at {matches[0]} - MPM-managed"
|
|
1316
|
+
)
|
|
1317
|
+
return True
|
|
1318
|
+
|
|
1319
|
+
# Not found in any cache - this is a custom user skill
|
|
1320
|
+
self.logger.debug(
|
|
1321
|
+
f"Skill '{skill_dir_name}' not found in cache - custom user skill, preserving"
|
|
1322
|
+
)
|
|
1323
|
+
return False
|
|
1324
|
+
|
|
1216
1325
|
# Check each directory in target_dir
|
|
1217
1326
|
if not target_dir.exists():
|
|
1218
1327
|
return removed_skills
|
|
@@ -1229,6 +1338,15 @@ class GitSkillSourceManager:
|
|
|
1229
1338
|
|
|
1230
1339
|
# Check if this skill directory should be kept (fuzzy matching)
|
|
1231
1340
|
if not should_keep_skill(item.name):
|
|
1341
|
+
# CRITICAL: Check if this is an MPM-managed skill before deletion
|
|
1342
|
+
if not is_mpm_managed_skill(item.name):
|
|
1343
|
+
# This is a custom user skill - NEVER delete
|
|
1344
|
+
self.logger.debug(
|
|
1345
|
+
f"Preserving custom user skill (not in MPM cache): {item.name}"
|
|
1346
|
+
)
|
|
1347
|
+
continue
|
|
1348
|
+
|
|
1349
|
+
# It's MPM-managed but not in filter - safe to remove
|
|
1232
1350
|
try:
|
|
1233
1351
|
# Security: Validate path is within target_dir
|
|
1234
1352
|
if not self._validate_safe_path(target_dir, item):
|
|
@@ -1244,7 +1362,9 @@ class GitSkillSourceManager:
|
|
|
1244
1362
|
shutil.rmtree(item)
|
|
1245
1363
|
|
|
1246
1364
|
removed_skills.append(item.name)
|
|
1247
|
-
self.logger.info(
|
|
1365
|
+
self.logger.info(
|
|
1366
|
+
f"Removed orphaned MPM-managed skill: {item.name}"
|
|
1367
|
+
)
|
|
1248
1368
|
|
|
1249
1369
|
except Exception as e:
|
|
1250
1370
|
self.logger.warning(
|
|
@@ -50,6 +50,22 @@ logger = get_logger(__name__)
|
|
|
50
50
|
# Deployment tracking index file
|
|
51
51
|
DEPLOYED_INDEX_FILE = ".mpm-deployed-skills.json"
|
|
52
52
|
|
|
53
|
+
# Core PM skills that should always be deployed
|
|
54
|
+
# These are referenced in PM_INSTRUCTIONS.md with [SKILL: name] markers
|
|
55
|
+
# Without these skills, PM only sees placeholders, not actual content
|
|
56
|
+
PM_CORE_SKILLS = {
|
|
57
|
+
"mpm-delegation-patterns",
|
|
58
|
+
"mpm-verification-protocols",
|
|
59
|
+
"mpm-tool-usage-guide",
|
|
60
|
+
"mpm-git-file-tracking",
|
|
61
|
+
"mpm-pr-workflow",
|
|
62
|
+
"mpm-ticketing-integration",
|
|
63
|
+
"mpm-teaching-mode",
|
|
64
|
+
"mpm-bug-reporting",
|
|
65
|
+
"mpm-circuit-breaker-enforcement",
|
|
66
|
+
"mpm-session-management",
|
|
67
|
+
}
|
|
68
|
+
|
|
53
69
|
# Core skills that are universally useful across all projects
|
|
54
70
|
# These are deployed when skill mapping returns too many skills (>60)
|
|
55
71
|
# Target: ~25-30 core skills for balanced functionality
|
|
@@ -214,18 +230,90 @@ def get_skills_from_mapping(agent_ids: List[str]) -> Set[str]:
|
|
|
214
230
|
return set()
|
|
215
231
|
|
|
216
232
|
|
|
233
|
+
def extract_skills_from_content(agent_file: Path) -> Set[str]:
|
|
234
|
+
"""Extract skill names from [SKILL: skill-name] markers in agent file content.
|
|
235
|
+
|
|
236
|
+
This function complements frontmatter skill extraction by finding inline
|
|
237
|
+
skill references in the agent's markdown content body.
|
|
238
|
+
|
|
239
|
+
Supports multiple formats:
|
|
240
|
+
- Bold marker: **[SKILL: skill-name]**
|
|
241
|
+
- Plain marker: [SKILL: skill-name]
|
|
242
|
+
- Backtick list: - `skill-name` - Description
|
|
243
|
+
- With spaces: [SKILL: skill-name ]
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
agent_file: Path to agent markdown file
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Set of skill names found in content body
|
|
250
|
+
|
|
251
|
+
Example:
|
|
252
|
+
>>> skills = extract_skills_from_content(Path("pm.md"))
|
|
253
|
+
>>> # Finds skills from markers like **[SKILL: mpm-delegation-patterns]**
|
|
254
|
+
>>> # Also finds from lists like - `mpm-teaching-mode` - Description
|
|
255
|
+
>>> print(f"Found {len(skills)} skills in content")
|
|
256
|
+
"""
|
|
257
|
+
try:
|
|
258
|
+
content = agent_file.read_text(encoding="utf-8")
|
|
259
|
+
except Exception as e:
|
|
260
|
+
logger.warning(f"Failed to read {agent_file}: {e}")
|
|
261
|
+
return set()
|
|
262
|
+
|
|
263
|
+
skills = set()
|
|
264
|
+
|
|
265
|
+
# Pattern 1: [SKILL: skill-name] markers (with optional markdown bold)
|
|
266
|
+
# Handles: **[SKILL: skill-name]** or [SKILL: skill-name]
|
|
267
|
+
# Pattern breakdown:
|
|
268
|
+
# - \*{0,2}: Optional bold markdown (0-2 asterisks)
|
|
269
|
+
# - \[SKILL:\s*: Opening bracket with optional whitespace
|
|
270
|
+
# - ([a-zA-Z0-9_-]+): Skill name (capture group)
|
|
271
|
+
# - \s*\]: Closing bracket with optional whitespace
|
|
272
|
+
# - \*{0,2}: Optional closing bold markdown
|
|
273
|
+
pattern1 = r"\*{0,2}\[SKILL:\s*([a-zA-Z0-9_-]+)\s*\]\*{0,2}"
|
|
274
|
+
matches1 = re.findall(pattern1, content, re.IGNORECASE)
|
|
275
|
+
skills.update(matches1)
|
|
276
|
+
|
|
277
|
+
# Pattern 2: Backtick list items with mpm-* or toolchains-* skills
|
|
278
|
+
# Handles: - `mpm-skill-name` - Description
|
|
279
|
+
# Pattern breakdown:
|
|
280
|
+
# - ^-\s+: Start with dash and whitespace (list item)
|
|
281
|
+
# - `: Opening backtick
|
|
282
|
+
# - ((?:mpm-|toolchains-|universal-)[a-zA-Z0-9_-]+): Skill name starting with prefix
|
|
283
|
+
# - `: Closing backtick
|
|
284
|
+
# - \s+-: Followed by whitespace and dash (description separator)
|
|
285
|
+
pattern2 = r"^-\s+`((?:mpm-|toolchains-|universal-)[a-zA-Z0-9_-]+)`\s+-"
|
|
286
|
+
matches2 = re.findall(pattern2, content, re.MULTILINE | re.IGNORECASE)
|
|
287
|
+
skills.update(matches2)
|
|
288
|
+
|
|
289
|
+
if skills:
|
|
290
|
+
logger.debug(
|
|
291
|
+
f"Found {len(skills)} skills from content markers in {agent_file.name}"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
return skills
|
|
295
|
+
|
|
296
|
+
|
|
217
297
|
def get_required_skills_from_agents(agents_dir: Path) -> Set[str]:
|
|
218
298
|
"""Extract all skills referenced by deployed agents.
|
|
219
299
|
|
|
220
|
-
MAJOR CHANGE (Phase 3): Now
|
|
300
|
+
MAJOR CHANGE (Phase 3): Now uses TWO sources for skill discovery:
|
|
301
|
+
1. Frontmatter-declared skills (skills: field)
|
|
302
|
+
2. Content body markers ([SKILL: skill-name])
|
|
303
|
+
|
|
221
304
|
The static skill_to_agent_mapping.yaml is DEPRECATED. Each agent must
|
|
222
|
-
declare its skills
|
|
305
|
+
declare its skills via frontmatter OR inline markers.
|
|
223
306
|
|
|
224
307
|
This change:
|
|
225
308
|
- Eliminates dual-source complexity (frontmatter + mapping)
|
|
226
309
|
- Makes skill requirements explicit per agent
|
|
227
|
-
- Enables per-agent customization via frontmatter
|
|
310
|
+
- Enables per-agent customization via frontmatter or inline markers
|
|
228
311
|
- Removes dependency on static YAML mapping
|
|
312
|
+
- Fixes PM skills being removed as orphaned (they use inline markers)
|
|
313
|
+
|
|
314
|
+
Special handling for PM_INSTRUCTIONS.md:
|
|
315
|
+
- Also scans .claude-mpm/PM_INSTRUCTIONS.md for skill markers
|
|
316
|
+
- PM instructions are not in agents_dir but contain [SKILL: ...] references
|
|
229
317
|
|
|
230
318
|
Args:
|
|
231
319
|
agents_dir: Path to deployed agents directory (e.g., .claude/agents/)
|
|
@@ -244,40 +332,78 @@ def get_required_skills_from_agents(agents_dir: Path) -> Set[str]:
|
|
|
244
332
|
|
|
245
333
|
# Scan all agent markdown files
|
|
246
334
|
agent_files = list(agents_dir.glob("*.md"))
|
|
247
|
-
logger.debug(f"Scanning {len(agent_files)} agent files in {agents_dir}")
|
|
248
335
|
|
|
249
|
-
#
|
|
336
|
+
# Special case: Add PM_INSTRUCTIONS.md if it exists
|
|
337
|
+
# PM instructions live in .claude-mpm/ not .claude/agents/
|
|
338
|
+
pm_instructions = agents_dir.parent.parent / ".claude-mpm" / "PM_INSTRUCTIONS.md"
|
|
339
|
+
if pm_instructions.exists():
|
|
340
|
+
agent_files.append(pm_instructions)
|
|
341
|
+
logger.debug("Added PM_INSTRUCTIONS.md for skill scanning")
|
|
342
|
+
|
|
343
|
+
logger.debug(f"Scanning {len(agent_files)} agent files (including PM instructions)")
|
|
344
|
+
|
|
345
|
+
# Use TWO sources: frontmatter AND content markers
|
|
250
346
|
frontmatter_skills = set()
|
|
347
|
+
content_skills = set()
|
|
251
348
|
|
|
252
349
|
for agent_file in agent_files:
|
|
253
350
|
agent_id = agent_file.stem
|
|
254
351
|
|
|
352
|
+
# Source 1: Extract from frontmatter
|
|
255
353
|
frontmatter = parse_agent_frontmatter(agent_file)
|
|
256
|
-
|
|
354
|
+
agent_fm_skills = get_skills_from_agent(frontmatter)
|
|
257
355
|
|
|
258
|
-
if
|
|
259
|
-
frontmatter_skills.update(
|
|
356
|
+
if agent_fm_skills:
|
|
357
|
+
frontmatter_skills.update(agent_fm_skills)
|
|
260
358
|
logger.debug(
|
|
261
|
-
f"Agent {agent_id}: {len(
|
|
359
|
+
f"Agent {agent_id}: {len(agent_fm_skills)} skills from frontmatter"
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
# Source 2: Extract from content body [SKILL: ...] markers
|
|
363
|
+
agent_content_skills = extract_skills_from_content(agent_file)
|
|
364
|
+
|
|
365
|
+
if agent_content_skills:
|
|
366
|
+
content_skills.update(agent_content_skills)
|
|
367
|
+
logger.debug(
|
|
368
|
+
f"Agent {agent_id}: {len(agent_content_skills)} skills from content markers"
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
if not agent_fm_skills and not agent_content_skills:
|
|
372
|
+
logger.debug(
|
|
373
|
+
f"Agent {agent_id}: No skills declared (checked frontmatter + content)"
|
|
262
374
|
)
|
|
263
|
-
|
|
264
|
-
|
|
375
|
+
|
|
376
|
+
# Combine both sources
|
|
377
|
+
all_skills = frontmatter_skills | content_skills
|
|
265
378
|
|
|
266
379
|
logger.info(
|
|
267
|
-
f"Found {len(
|
|
268
|
-
f"(
|
|
380
|
+
f"Found {len(all_skills)} unique skills "
|
|
381
|
+
f"({len(frontmatter_skills)} from frontmatter, "
|
|
382
|
+
f"{len(content_skills)} from content markers)"
|
|
269
383
|
)
|
|
270
384
|
|
|
271
385
|
# Normalize skill paths: convert slashes to dashes for compatibility with deployment
|
|
272
386
|
# Some skills may use slash format, normalize to dashes
|
|
273
|
-
normalized_skills = {skill.replace("/", "-") for skill in
|
|
387
|
+
normalized_skills = {skill.replace("/", "-") for skill in all_skills}
|
|
274
388
|
|
|
275
|
-
if normalized_skills !=
|
|
389
|
+
if normalized_skills != all_skills:
|
|
276
390
|
logger.debug(
|
|
277
|
-
f"Normalized {len(
|
|
391
|
+
f"Normalized {len(all_skills)} skills to {len(normalized_skills)} "
|
|
278
392
|
"(converted slashes to dashes)"
|
|
279
393
|
)
|
|
280
394
|
|
|
395
|
+
# Always include PM core skills to ensure PM_INSTRUCTIONS.md markers are resolved
|
|
396
|
+
# These skills are referenced in PM_INSTRUCTIONS.md and must be deployed
|
|
397
|
+
# for PM to see actual content instead of [SKILL: name] placeholders
|
|
398
|
+
before_pm_skills = len(normalized_skills)
|
|
399
|
+
normalized_skills = normalized_skills | PM_CORE_SKILLS
|
|
400
|
+
pm_skills_added = len(normalized_skills) - before_pm_skills
|
|
401
|
+
|
|
402
|
+
if pm_skills_added > 0:
|
|
403
|
+
logger.info(
|
|
404
|
+
f"Added {pm_skills_added} PM core skills to ensure PM_INSTRUCTIONS.md markers resolve"
|
|
405
|
+
)
|
|
406
|
+
|
|
281
407
|
return normalized_skills
|
|
282
408
|
|
|
283
409
|
|
|
@@ -163,10 +163,22 @@ class SkillDiscoveryService:
|
|
|
163
163
|
skill_md_files = list(self.skills_dir.rglob("SKILL.md"))
|
|
164
164
|
|
|
165
165
|
# Also find legacy *.md files in top-level directory for backward compatibility
|
|
166
|
+
# Exclude common non-skill documentation files
|
|
167
|
+
excluded_filenames = {
|
|
168
|
+
"skill.md", # Case variations of SKILL.md
|
|
169
|
+
"readme.md",
|
|
170
|
+
"claude.md",
|
|
171
|
+
"contributing.md",
|
|
172
|
+
"changelog.md",
|
|
173
|
+
"license.md",
|
|
174
|
+
"authors.md",
|
|
175
|
+
"code_of_conduct.md",
|
|
176
|
+
}
|
|
177
|
+
|
|
166
178
|
legacy_md_files = [
|
|
167
179
|
f
|
|
168
180
|
for f in self.skills_dir.glob("*.md")
|
|
169
|
-
if f.name
|
|
181
|
+
if f.name.lower() not in excluded_filenames
|
|
170
182
|
]
|
|
171
183
|
|
|
172
184
|
all_skill_files = skill_md_files + legacy_md_files
|
|
@@ -176,6 +188,15 @@ class SkillDiscoveryService:
|
|
|
176
188
|
f"and {len(legacy_md_files)} legacy .md files in {self.skills_dir}"
|
|
177
189
|
)
|
|
178
190
|
|
|
191
|
+
# Log first few file paths for debugging
|
|
192
|
+
if all_skill_files:
|
|
193
|
+
sample_files = [
|
|
194
|
+
str(f.relative_to(self.skills_dir)) for f in all_skill_files[:5]
|
|
195
|
+
]
|
|
196
|
+
self.logger.debug(f"Sample skill files: {sample_files}")
|
|
197
|
+
else:
|
|
198
|
+
self.logger.debug(f"No SKILL.md or .md files found in {self.skills_dir}")
|
|
199
|
+
|
|
179
200
|
# Track deployment names to detect collisions
|
|
180
201
|
deployment_names = {}
|
|
181
202
|
|
|
@@ -214,7 +235,14 @@ class SkillDiscoveryService:
|
|
|
214
235
|
except Exception as e:
|
|
215
236
|
self.logger.warning(f"Failed to parse skill {skill_file}: {e}")
|
|
216
237
|
|
|
217
|
-
|
|
238
|
+
# Summary logging
|
|
239
|
+
parsed_count = len(skills)
|
|
240
|
+
failed_count = len(all_skill_files) - parsed_count
|
|
241
|
+
self.logger.info(
|
|
242
|
+
f"Discovered {parsed_count} skills from {self.skills_dir.name} "
|
|
243
|
+
f"({len(all_skill_files)} files found, {failed_count} failed to parse)"
|
|
244
|
+
)
|
|
245
|
+
|
|
218
246
|
return skills
|
|
219
247
|
|
|
220
248
|
def _parse_skill_file(self, skill_file: Path) -> Optional[Dict[str, Any]]:
|
|
@@ -255,7 +283,35 @@ class SkillDiscoveryService:
|
|
|
255
283
|
try:
|
|
256
284
|
frontmatter, body = self._extract_frontmatter(content)
|
|
257
285
|
except Exception as e:
|
|
258
|
-
|
|
286
|
+
# Only log as debug for documentation files to reduce noise
|
|
287
|
+
# Common documentation files (CLAUDE.md, README.md) are expected to lack skill frontmatter
|
|
288
|
+
relative_path = (
|
|
289
|
+
skill_file.relative_to(self.skills_dir)
|
|
290
|
+
if skill_file.is_relative_to(self.skills_dir)
|
|
291
|
+
else skill_file
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# Check if this looks like a documentation file
|
|
295
|
+
is_documentation = any(
|
|
296
|
+
doc_pattern in skill_file.name.lower()
|
|
297
|
+
for doc_pattern in [
|
|
298
|
+
"readme",
|
|
299
|
+
"claude",
|
|
300
|
+
"contributing",
|
|
301
|
+
"changelog",
|
|
302
|
+
"license",
|
|
303
|
+
]
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
if is_documentation:
|
|
307
|
+
self.logger.debug(
|
|
308
|
+
f"Skipping documentation file {relative_path} (no skill frontmatter): {e}"
|
|
309
|
+
)
|
|
310
|
+
else:
|
|
311
|
+
# For actual skill files with invalid YAML, use warning level
|
|
312
|
+
self.logger.warning(
|
|
313
|
+
f"Failed to parse skill frontmatter in {relative_path}: {e}"
|
|
314
|
+
)
|
|
259
315
|
return None
|
|
260
316
|
|
|
261
317
|
# Validate required fields
|
|
@@ -354,10 +410,24 @@ class SkillDiscoveryService:
|
|
|
354
410
|
frontmatter_text = match.group(1)
|
|
355
411
|
body = match.group(2)
|
|
356
412
|
|
|
357
|
-
# Parse YAML
|
|
413
|
+
# Parse YAML with improved error handling
|
|
358
414
|
try:
|
|
359
415
|
frontmatter = yaml.safe_load(frontmatter_text)
|
|
360
416
|
except yaml.YAMLError as e:
|
|
417
|
+
# Provide more specific error message with context
|
|
418
|
+
error_line = getattr(e, "problem_mark", None)
|
|
419
|
+
if error_line:
|
|
420
|
+
line_num = error_line.line + 1
|
|
421
|
+
col_num = error_line.column + 1
|
|
422
|
+
# Extract problematic line for context
|
|
423
|
+
lines = frontmatter_text.split("\n")
|
|
424
|
+
problem_line = (
|
|
425
|
+
lines[error_line.line] if error_line.line < len(lines) else ""
|
|
426
|
+
)
|
|
427
|
+
raise ValueError(
|
|
428
|
+
f"Invalid YAML in frontmatter at line {line_num}, column {col_num}: {e.problem}\n"
|
|
429
|
+
f" Problematic line: {problem_line.strip()}"
|
|
430
|
+
) from e
|
|
361
431
|
raise ValueError(f"Invalid YAML in frontmatter: {e}") from e
|
|
362
432
|
|
|
363
433
|
if not isinstance(frontmatter, dict):
|
|
@@ -28,7 +28,7 @@ References:
|
|
|
28
28
|
import json
|
|
29
29
|
import platform
|
|
30
30
|
import shutil
|
|
31
|
-
import subprocess
|
|
31
|
+
import subprocess # nosec B404 - subprocess needed for safe git operations
|
|
32
32
|
from pathlib import Path
|
|
33
33
|
from typing import Any, Dict, List, Optional
|
|
34
34
|
|
|
@@ -653,7 +653,7 @@ class SkillsDeployerService(LoggerMixin):
|
|
|
653
653
|
f"Updating existing collection '{collection_name}' at {target_dir}"
|
|
654
654
|
)
|
|
655
655
|
try:
|
|
656
|
-
result = subprocess.run(
|
|
656
|
+
result = subprocess.run( # nosec B603 B607 - Safe: hardcoded git command
|
|
657
657
|
["git", "pull"],
|
|
658
658
|
cwd=target_dir,
|
|
659
659
|
capture_output=True,
|
|
@@ -684,7 +684,7 @@ class SkillsDeployerService(LoggerMixin):
|
|
|
684
684
|
f"Installing new collection '{collection_name}' to {target_dir}"
|
|
685
685
|
)
|
|
686
686
|
try:
|
|
687
|
-
result = subprocess.run(
|
|
687
|
+
result = subprocess.run( # nosec B603 B607 - Safe: hardcoded git command
|
|
688
688
|
["git", "clone", repo_url, str(target_dir)],
|
|
689
689
|
capture_output=True,
|
|
690
690
|
text=True,
|
|
@@ -773,6 +773,32 @@ class SkillsDeployerService(LoggerMixin):
|
|
|
773
773
|
if isinstance(skills_data, dict):
|
|
774
774
|
flat_skills = []
|
|
775
775
|
|
|
776
|
+
# Define valid top-level categories
|
|
777
|
+
VALID_CATEGORIES = {"universal", "toolchains"}
|
|
778
|
+
|
|
779
|
+
# Check for unknown categories and warn user
|
|
780
|
+
unknown_categories = set(skills_data.keys()) - VALID_CATEGORIES
|
|
781
|
+
if unknown_categories:
|
|
782
|
+
# Count skills in unknown categories
|
|
783
|
+
skipped_count = 0
|
|
784
|
+
for cat in unknown_categories:
|
|
785
|
+
cat_data = skills_data.get(cat, [])
|
|
786
|
+
if isinstance(cat_data, list):
|
|
787
|
+
skipped_count += len(cat_data)
|
|
788
|
+
elif isinstance(cat_data, dict):
|
|
789
|
+
# If it's a dict like toolchains, count nested skills
|
|
790
|
+
for skills_list in cat_data.values():
|
|
791
|
+
if isinstance(skills_list, list):
|
|
792
|
+
skipped_count += len(skills_list)
|
|
793
|
+
|
|
794
|
+
self.logger.warning(
|
|
795
|
+
f"Unknown categories in manifest will be skipped: "
|
|
796
|
+
f"{', '.join(sorted(unknown_categories))} ({skipped_count} skills)"
|
|
797
|
+
)
|
|
798
|
+
self.logger.info(
|
|
799
|
+
f"Valid top-level categories: {', '.join(sorted(VALID_CATEGORIES))}"
|
|
800
|
+
)
|
|
801
|
+
|
|
776
802
|
# Add universal skills
|
|
777
803
|
universal_skills = skills_data.get("universal", [])
|
|
778
804
|
if isinstance(universal_skills, list):
|
|
@@ -1022,12 +1048,12 @@ class SkillsDeployerService(LoggerMixin):
|
|
|
1022
1048
|
"""
|
|
1023
1049
|
try:
|
|
1024
1050
|
if platform.system() == "Windows":
|
|
1025
|
-
result = subprocess.run(
|
|
1051
|
+
result = subprocess.run( # nosec B603 B607 - Safe: hardcoded tasklist command
|
|
1026
1052
|
["tasklist"], check=False, capture_output=True, text=True, timeout=5
|
|
1027
1053
|
)
|
|
1028
1054
|
return "claude" in result.stdout.lower()
|
|
1029
1055
|
# macOS and Linux
|
|
1030
|
-
result = subprocess.run(
|
|
1056
|
+
result = subprocess.run( # nosec B603 B607 - Safe: hardcoded ps command
|
|
1031
1057
|
["ps", "aux"], check=False, capture_output=True, text=True, timeout=5
|
|
1032
1058
|
)
|
|
1033
1059
|
# Look for "Claude Code" or "claude-code" process
|