claude-mpm 5.4.55__py3-none-any.whl → 5.4.85__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_FOUNDERS_OUTPUT_STYLE.md +405 -0
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +63 -241
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +109 -1925
- claude_mpm/agents/PM_INSTRUCTIONS.md +36 -9
- claude_mpm/cli/__init__.py +5 -1
- claude_mpm/cli/commands/agents.py +2 -4
- claude_mpm/cli/commands/agents_reconcile.py +197 -0
- claude_mpm/cli/commands/configure.py +620 -21
- claude_mpm/cli/commands/skills.py +166 -14
- claude_mpm/cli/executor.py +1 -0
- claude_mpm/cli/interactive/__init__.py +10 -0
- claude_mpm/cli/interactive/agent_wizard.py +30 -50
- claude_mpm/cli/interactive/questionary_styles.py +65 -0
- claude_mpm/cli/interactive/skill_selector.py +481 -0
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/startup.py +223 -388
- claude_mpm/constants.py +1 -0
- claude_mpm/core/claude_runner.py +2 -2
- claude_mpm/core/interactive_session.py +7 -7
- claude_mpm/core/output_style_manager.py +21 -13
- claude_mpm/core/unified_config.py +50 -8
- claude_mpm/core/unified_paths.py +30 -13
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +8 -0
- claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
- claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
- claude_mpm/services/agents/sources/git_source_sync_service.py +7 -4
- claude_mpm/services/agents/startup_sync.py +5 -2
- claude_mpm/services/pm_skills_deployer.py +4 -0
- claude_mpm/services/skills/git_skill_source_manager.py +24 -8
- claude_mpm/services/skills/selective_skill_deployer.py +82 -83
- claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
- claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
- claude_mpm/skills/bundled/collaboration/git-worktrees.md +317 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
- claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
- claude_mpm/skills/bundled/collaboration/stacked-prs.md +251 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
- claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
- claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
- claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
- claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/INTEGRATION.md +611 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/README.md +596 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/SKILL.md +260 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/examples/nextjs-env-structure.md +315 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/frameworks.md +436 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/security.md +433 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/synchronization.md +452 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/troubleshooting.md +404 -0
- claude_mpm/skills/bundled/infrastructure/env-manager/references/validation.md +420 -0
- claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
- claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
- claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
- claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
- claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
- claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
- claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
- claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
- claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
- claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
- claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
- claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
- claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
- claude_mpm/skills/bundled/pm/pm-bug-reporting/pm-bug-reporting.md +248 -0
- claude_mpm/skills/bundled/pm/pm-delegation-patterns/SKILL.md +167 -0
- claude_mpm/skills/bundled/pm/pm-git-file-tracking/SKILL.md +113 -0
- claude_mpm/skills/bundled/pm/pm-pr-workflow/SKILL.md +124 -0
- claude_mpm/skills/bundled/pm/pm-teaching-mode/SKILL.md +657 -0
- claude_mpm/skills/bundled/pm/pm-ticketing-integration/SKILL.md +154 -0
- claude_mpm/skills/bundled/pm/pm-verification-protocols/SKILL.md +198 -0
- claude_mpm/skills/bundled/react/flexlayout-react.md +742 -0
- claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
- claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
- claude_mpm/skills/bundled/tauri/tauri-async-patterns.md +495 -0
- claude_mpm/skills/bundled/tauri/tauri-build-deploy.md +599 -0
- claude_mpm/skills/bundled/tauri/tauri-command-patterns.md +535 -0
- claude_mpm/skills/bundled/tauri/tauri-error-handling.md +613 -0
- claude_mpm/skills/bundled/tauri/tauri-event-system.md +648 -0
- claude_mpm/skills/bundled/tauri/tauri-file-system.md +673 -0
- claude_mpm/skills/bundled/tauri/tauri-frontend-integration.md +767 -0
- claude_mpm/skills/bundled/tauri/tauri-performance.md +669 -0
- claude_mpm/skills/bundled/tauri/tauri-state-management.md +573 -0
- claude_mpm/skills/bundled/tauri/tauri-testing.md +384 -0
- claude_mpm/skills/bundled/tauri/tauri-window-management.md +628 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
- claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
- claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
- claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/SKILL.md +458 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/examples/example-inspection-report.md +411 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/assertion-quality.md +317 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/inspection-checklist.md +270 -0
- claude_mpm/skills/bundled/testing/test-quality-inspector/references/red-flags.md +436 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
- claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
- claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
- claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
- claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
- claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
- claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
- claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
- claude_mpm/utils/agent_dependency_loader.py +103 -4
- claude_mpm/utils/robust_installer.py +45 -24
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/METADATA +47 -23
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/RECORD +159 -47
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.55.dist-info → claude_mpm-5.4.85.dist-info}/top_level.txt +0 -0
claude_mpm/cli/startup.py
CHANGED
|
@@ -234,7 +234,7 @@ def deploy_bundled_skills():
|
|
|
234
234
|
if not skills_config.get("auto_deploy", True):
|
|
235
235
|
# Auto-deploy disabled, skip silently
|
|
236
236
|
return
|
|
237
|
-
except Exception:
|
|
237
|
+
except Exception: # nosec B110
|
|
238
238
|
# If config loading fails, assume auto-deploy is enabled (default)
|
|
239
239
|
pass
|
|
240
240
|
|
|
@@ -308,63 +308,60 @@ def deploy_output_style_on_startup():
|
|
|
308
308
|
communication without emojis and exclamation points. Styles are project-specific
|
|
309
309
|
to allow different projects to have different communication styles.
|
|
310
310
|
|
|
311
|
-
DESIGN DECISION: This is non-blocking and idempotent. Deploys to
|
|
312
|
-
directory (
|
|
313
|
-
|
|
311
|
+
DESIGN DECISION: This is non-blocking and idempotent. Deploys to user-level
|
|
312
|
+
directory (~/.claude/output-styles/) which is the official Claude Code location
|
|
313
|
+
for custom output styles.
|
|
314
314
|
|
|
315
|
-
Deploys
|
|
316
|
-
- claude-mpm
|
|
315
|
+
Deploys all styles:
|
|
316
|
+
- claude-mpm.md (professional mode)
|
|
317
317
|
- claude-mpm-teacher.md (teaching mode)
|
|
318
|
+
- claude-mpm-founders.md (founders mode)
|
|
318
319
|
"""
|
|
319
320
|
try:
|
|
320
|
-
import
|
|
321
|
-
from pathlib import Path
|
|
321
|
+
from ..core.output_style_manager import OutputStyleManager
|
|
322
322
|
|
|
323
|
-
#
|
|
324
|
-
|
|
325
|
-
professional_source = package_dir / "CLAUDE_MPM_OUTPUT_STYLE.md"
|
|
326
|
-
teacher_source = package_dir / "CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md"
|
|
323
|
+
# Initialize the output style manager
|
|
324
|
+
manager = OutputStyleManager()
|
|
327
325
|
|
|
328
|
-
#
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
# Create directory if it doesn't exist
|
|
335
|
-
output_styles_dir.mkdir(parents=True, exist_ok=True)
|
|
336
|
-
|
|
337
|
-
# Check if already deployed (both files exist and have content)
|
|
338
|
-
already_deployed = (
|
|
339
|
-
professional_target.exists()
|
|
340
|
-
and teacher_target.exists()
|
|
341
|
-
and professional_target.stat().st_size > 0
|
|
342
|
-
and teacher_target.stat().st_size > 0
|
|
343
|
-
)
|
|
326
|
+
# Check if Claude Code version supports output styles (>= 1.0.83)
|
|
327
|
+
if not manager.supports_output_styles():
|
|
328
|
+
# Skip deployment for older versions
|
|
329
|
+
# The manager will fall back to injecting content directly
|
|
330
|
+
return
|
|
344
331
|
|
|
345
|
-
if
|
|
332
|
+
# Check if all styles are already deployed and up-to-date
|
|
333
|
+
all_up_to_date = True
|
|
334
|
+
for style_config in manager.styles.values():
|
|
335
|
+
source_path = style_config["source"]
|
|
336
|
+
target_path = style_config["target"]
|
|
337
|
+
|
|
338
|
+
if not (
|
|
339
|
+
target_path.exists()
|
|
340
|
+
and source_path.exists()
|
|
341
|
+
and target_path.stat().st_size == source_path.stat().st_size
|
|
342
|
+
):
|
|
343
|
+
all_up_to_date = False
|
|
344
|
+
break
|
|
345
|
+
|
|
346
|
+
if all_up_to_date:
|
|
346
347
|
# Show feedback that output styles are ready
|
|
347
348
|
print("✓ Output styles ready", flush=True)
|
|
348
349
|
return
|
|
349
350
|
|
|
350
|
-
# Deploy
|
|
351
|
-
|
|
352
|
-
if professional_source.exists():
|
|
353
|
-
shutil.copy2(professional_source, professional_target)
|
|
354
|
-
deployed_count += 1
|
|
351
|
+
# Deploy all styles using the manager
|
|
352
|
+
results = manager.deploy_all_styles(activate_default=True)
|
|
355
353
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
deployed_count += 1
|
|
354
|
+
# Count successful deployments
|
|
355
|
+
deployed_count = sum(1 for success in results.values() if success)
|
|
359
356
|
|
|
360
357
|
if deployed_count > 0:
|
|
361
358
|
print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
|
|
362
359
|
else:
|
|
363
|
-
#
|
|
360
|
+
# Deployment failed - log but don't fail startup
|
|
364
361
|
from ..core.logger import get_logger
|
|
365
362
|
|
|
366
363
|
logger = get_logger("cli")
|
|
367
|
-
logger.debug("
|
|
364
|
+
logger.debug("Failed to deploy any output styles")
|
|
368
365
|
|
|
369
366
|
except Exception as e:
|
|
370
367
|
# Non-critical - log but don't fail startup
|
|
@@ -453,7 +450,7 @@ def _cleanup_orphaned_agents(deploy_target: Path, deployed_agents: list[str]) ->
|
|
|
453
450
|
return removed_count
|
|
454
451
|
|
|
455
452
|
|
|
456
|
-
def sync_remote_agents_on_startup():
|
|
453
|
+
def sync_remote_agents_on_startup(force_sync: bool = False):
|
|
457
454
|
"""
|
|
458
455
|
Synchronize agent templates from remote sources on startup.
|
|
459
456
|
|
|
@@ -466,16 +463,16 @@ def sync_remote_agents_on_startup():
|
|
|
466
463
|
block startup to ensure claude-mpm remains functional.
|
|
467
464
|
|
|
468
465
|
Workflow:
|
|
469
|
-
1.
|
|
470
|
-
2.
|
|
471
|
-
3.
|
|
472
|
-
4. Cleanup
|
|
466
|
+
1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
|
|
467
|
+
2. Deploy agents to ~/.claude/agents/ - Phase 2 progress bar
|
|
468
|
+
3. Cleanup orphaned agents (ours but no longer deployed) - Phase 3
|
|
469
|
+
4. Cleanup legacy agent cache directories (after sync/deployment) - Phase 4
|
|
473
470
|
5. Log deployment results
|
|
474
|
-
"""
|
|
475
|
-
# Cleanup legacy cache directories first (before syncing)
|
|
476
|
-
cleanup_legacy_agent_cache()
|
|
477
471
|
|
|
478
|
-
|
|
472
|
+
Args:
|
|
473
|
+
force_sync: Force download even if cache is fresh (bypasses ETag).
|
|
474
|
+
"""
|
|
475
|
+
# DEPRECATED: Legacy warning - no-op function, kept for compatibility
|
|
479
476
|
check_legacy_cache()
|
|
480
477
|
|
|
481
478
|
try:
|
|
@@ -484,7 +481,6 @@ def sync_remote_agents_on_startup():
|
|
|
484
481
|
from pathlib import Path
|
|
485
482
|
|
|
486
483
|
from ..core.shared.config_loader import ConfigLoader
|
|
487
|
-
from ..services.agents.deployment.agent_deployment import AgentDeploymentService
|
|
488
484
|
from ..services.agents.startup_sync import sync_agents_on_startup
|
|
489
485
|
from ..services.profile_manager import ProfileManager
|
|
490
486
|
from ..utils.progress import ProgressBar
|
|
@@ -509,7 +505,7 @@ def sync_remote_agents_on_startup():
|
|
|
509
505
|
)
|
|
510
506
|
|
|
511
507
|
# Phase 1: Sync files from Git sources
|
|
512
|
-
result = sync_agents_on_startup()
|
|
508
|
+
result = sync_agents_on_startup(force_refresh=force_sync)
|
|
513
509
|
|
|
514
510
|
# Only proceed with deployment if sync was enabled and ran
|
|
515
511
|
if result.get("enabled") and result.get("sources_synced", 0) > 0:
|
|
@@ -532,304 +528,89 @@ def sync_remote_agents_on_startup():
|
|
|
532
528
|
logger.warning(f"Agent sync completed with {len(errors)} errors")
|
|
533
529
|
|
|
534
530
|
# Phase 2: Deploy agents from cache to ~/.claude/agents/
|
|
535
|
-
#
|
|
531
|
+
# Use reconciliation service to respect configuration.yaml settings
|
|
536
532
|
try:
|
|
537
|
-
# Initialize deployment service with profile-filtered configuration
|
|
538
|
-
from ..core.config import Config
|
|
539
|
-
|
|
540
|
-
deploy_config = None
|
|
541
|
-
if active_profile and profile_manager.active_profile:
|
|
542
|
-
# Create config with excluded agents based on profile
|
|
543
|
-
# Get all agents that should be excluded (not in enabled list)
|
|
544
|
-
from pathlib import Path
|
|
545
|
-
|
|
546
|
-
cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
|
|
547
|
-
if cache_dir.exists():
|
|
548
|
-
# Find all agent files
|
|
549
|
-
# Supports both flat cache and {owner}/{repo}/agents/ structure
|
|
550
|
-
all_agent_files = [
|
|
551
|
-
f
|
|
552
|
-
for f in cache_dir.rglob("*.md")
|
|
553
|
-
if "/agents/" in str(f)
|
|
554
|
-
and f.stem.lower() != "base-agent"
|
|
555
|
-
and f.name.lower()
|
|
556
|
-
not in {"readme.md", "changelog.md", "contributing.md"}
|
|
557
|
-
]
|
|
558
|
-
|
|
559
|
-
# Build exclusion list for agents not in profile
|
|
560
|
-
excluded_agents = []
|
|
561
|
-
for agent_file in all_agent_files:
|
|
562
|
-
agent_name = agent_file.stem
|
|
563
|
-
if not profile_manager.is_agent_enabled(agent_name):
|
|
564
|
-
excluded_agents.append(agent_name)
|
|
565
|
-
|
|
566
|
-
if excluded_agents:
|
|
567
|
-
# Get singleton config and update with profile settings
|
|
568
|
-
# BUGFIX: Config is a singleton that ignores dict parameter if already initialized.
|
|
569
|
-
# Creating Config({...}) doesn't store excluded_agents - use set() instead.
|
|
570
|
-
deploy_config = Config()
|
|
571
|
-
deploy_config.set(
|
|
572
|
-
"agent_deployment.excluded_agents", excluded_agents
|
|
573
|
-
)
|
|
574
|
-
deploy_config.set(
|
|
575
|
-
"agent_deployment.filter_non_mpm_agents", False
|
|
576
|
-
)
|
|
577
|
-
deploy_config.set("agent_deployment.case_sensitive", False)
|
|
578
|
-
deploy_config.set(
|
|
579
|
-
"agent_deployment.exclude_dependencies", False
|
|
580
|
-
)
|
|
581
|
-
logger.info(
|
|
582
|
-
f"Profile '{active_profile}': Excluding {len(excluded_agents)} agents from deployment"
|
|
583
|
-
)
|
|
584
|
-
|
|
585
|
-
deployment_service = AgentDeploymentService(config=deploy_config)
|
|
586
|
-
|
|
587
|
-
# Count agents in cache to show accurate progress
|
|
588
533
|
from pathlib import Path
|
|
589
534
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
# BUGFIX (cache-count-inflation): Clean up stale cache files
|
|
595
|
-
# from old repositories before counting to prevent inflated counts.
|
|
596
|
-
# Issue: Old caches like bobmatnyc/claude-mpm-agents/agents/
|
|
597
|
-
# were counted alongside current agents, inflating count
|
|
598
|
-
# from 44 to 85.
|
|
599
|
-
#
|
|
600
|
-
# Solution: Remove files with nested /agents/ paths
|
|
601
|
-
# (e.g., cache/agents/user/repo/agents/...)
|
|
602
|
-
# Keep only current agents (e.g., cache/agents/engineer/...)
|
|
603
|
-
removed_count = 0
|
|
604
|
-
stale_dirs = set()
|
|
605
|
-
|
|
606
|
-
for md_file in cache_dir.rglob("*.md"):
|
|
607
|
-
# Stale cache files have multiple /agents/ in their path RELATIVE to cache_dir
|
|
608
|
-
# Current: cache/agents/bobmatnyc/claude-mpm-agents/agents/engineer/...
|
|
609
|
-
# (1 occurrence in relative path: /agents/)
|
|
610
|
-
# Old flat: cache/agents/engineer/...
|
|
611
|
-
# (0 occurrences in relative path - no repo structure)
|
|
612
|
-
# The issue: str(md_file).count("/agents/") counts BOTH cache/agents/ AND repo/agents/
|
|
613
|
-
# Fix: Count /agents/ in path RELATIVE to cache_dir (after cache/agents/)
|
|
614
|
-
relative_path = str(md_file.relative_to(cache_dir))
|
|
615
|
-
if relative_path.count("/agents/") > 1:
|
|
616
|
-
# Track parent directory for cleanup
|
|
617
|
-
# Extract subdirectory under cache/agents/
|
|
618
|
-
# (e.g., "bobmatnyc")
|
|
619
|
-
parts = md_file.parts
|
|
620
|
-
cache_agents_idx = parts.index("agents")
|
|
621
|
-
if cache_agents_idx + 1 < len(parts):
|
|
622
|
-
stale_subdir = parts[cache_agents_idx + 1]
|
|
623
|
-
# Only remove if it's not a known category directory
|
|
624
|
-
if stale_subdir not in [
|
|
625
|
-
"engineer",
|
|
626
|
-
"ops",
|
|
627
|
-
"qa",
|
|
628
|
-
"universal",
|
|
629
|
-
"documentation",
|
|
630
|
-
"claude-mpm",
|
|
631
|
-
"security",
|
|
632
|
-
]:
|
|
633
|
-
stale_dirs.add(cache_dir / stale_subdir)
|
|
634
|
-
|
|
635
|
-
md_file.unlink()
|
|
636
|
-
removed_count += 1
|
|
637
|
-
|
|
638
|
-
# Remove empty stale directories
|
|
639
|
-
for stale_dir in stale_dirs:
|
|
640
|
-
if stale_dir.exists() and stale_dir.is_dir():
|
|
641
|
-
try:
|
|
642
|
-
# Remove directory and all contents
|
|
643
|
-
import shutil
|
|
644
|
-
|
|
645
|
-
shutil.rmtree(stale_dir)
|
|
646
|
-
except Exception:
|
|
647
|
-
pass # Ignore cleanup errors
|
|
648
|
-
|
|
649
|
-
if removed_count > 0:
|
|
650
|
-
from loguru import logger
|
|
651
|
-
|
|
652
|
-
logger.info(
|
|
653
|
-
f"Cleaned up {removed_count} stale cache files "
|
|
654
|
-
f"from old repositories"
|
|
655
|
-
)
|
|
535
|
+
from ..core.unified_config import UnifiedConfig
|
|
536
|
+
from ..services.agents.deployment.startup_reconciliation import (
|
|
537
|
+
perform_startup_reconciliation,
|
|
538
|
+
)
|
|
656
539
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
# (
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
"
|
|
670
|
-
"response_format.md",
|
|
671
|
-
"ticket_completeness_examples.md",
|
|
672
|
-
"validation_templates.md",
|
|
673
|
-
"git_file_tracking.md",
|
|
674
|
-
}
|
|
675
|
-
# Documentation files to exclude (by filename)
|
|
676
|
-
doc_files = {
|
|
677
|
-
"readme.md",
|
|
678
|
-
"changelog.md",
|
|
679
|
-
"contributing.md",
|
|
680
|
-
"implementation-summary.md",
|
|
681
|
-
"reorganization-plan.md",
|
|
682
|
-
"auto-deploy-index.md",
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
# Find all markdown files (after cleanup)
|
|
686
|
-
all_md_files = list(cache_dir.rglob("*.md"))
|
|
687
|
-
|
|
688
|
-
# Filter to only agent files:
|
|
689
|
-
# 1. Must have "/agents/" in path (current structure supports
|
|
690
|
-
# both flat and {owner}/{repo}/agents/ patterns)
|
|
691
|
-
# 2. Must not be in PM templates or doc files
|
|
692
|
-
# 3. Exclude BASE-AGENT.md which is not a deployable agent
|
|
693
|
-
# 4. Exclude build artifacts (dist/, build/, .cache/)
|
|
694
|
-
# to prevent double-counting
|
|
695
|
-
agent_files = [
|
|
696
|
-
f
|
|
697
|
-
for f in all_md_files
|
|
698
|
-
if (
|
|
699
|
-
# Must be in an agent directory
|
|
700
|
-
# Supports: cache/agents/{category}/... (flat)
|
|
701
|
-
# Supports: cache/agents/{owner}/{repo}/agents/{category}/... (GitHub sync)
|
|
702
|
-
"/agents/" in str(f)
|
|
703
|
-
# Exclude PM templates, doc files, and BASE-AGENT
|
|
704
|
-
and f.name.lower() not in pm_templates
|
|
705
|
-
and f.name.lower() not in doc_files
|
|
706
|
-
and f.name.lower() != "base-agent.md"
|
|
707
|
-
# Exclude build artifacts (prevents double-counting
|
|
708
|
-
# source + built files)
|
|
709
|
-
and not any(
|
|
710
|
-
part in str(f).split("/")
|
|
711
|
-
for part in ["dist", "build", ".cache"]
|
|
712
|
-
)
|
|
713
|
-
)
|
|
714
|
-
]
|
|
715
|
-
agent_count = len(agent_files)
|
|
716
|
-
|
|
717
|
-
if agent_count > 0:
|
|
718
|
-
# Deploy agents to project-level directory where Claude Code expects them
|
|
719
|
-
deploy_target = Path.cwd() / ".claude" / "agents"
|
|
720
|
-
deployment_result = deployment_service.deploy_agents(
|
|
721
|
-
target_dir=deploy_target,
|
|
722
|
-
force_rebuild=False, # Only deploy if versions differ
|
|
723
|
-
deployment_mode="update", # Version-aware updates
|
|
724
|
-
config=deploy_config, # Pass config to respect profile filtering
|
|
540
|
+
# Load configuration
|
|
541
|
+
unified_config = UnifiedConfig()
|
|
542
|
+
|
|
543
|
+
# Override with profile settings if active
|
|
544
|
+
if active_profile and profile_manager.active_profile:
|
|
545
|
+
# Get enabled agents from profile (returns Set[str])
|
|
546
|
+
profile_enabled_agents = (
|
|
547
|
+
profile_manager.active_profile.get_enabled_agents()
|
|
548
|
+
)
|
|
549
|
+
# Update config with profile's enabled list (convert Set to List)
|
|
550
|
+
unified_config.agents.enabled = list(profile_enabled_agents)
|
|
551
|
+
logger.info(
|
|
552
|
+
f"Profile '{active_profile}': Using {len(profile_enabled_agents)} enabled agents"
|
|
725
553
|
)
|
|
726
554
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
agent_count_in_target = len(
|
|
740
|
-
[
|
|
741
|
-
f
|
|
742
|
-
for f in existing_agents
|
|
743
|
-
if not f.name.startswith(("README", "INSTRUCTIONS"))
|
|
744
|
-
]
|
|
745
|
-
)
|
|
746
|
-
if agent_count_in_target > 0:
|
|
747
|
-
# All agents already deployed - count them as skipped
|
|
748
|
-
skipped = agent_count_in_target
|
|
749
|
-
total_configured = agent_count_in_target
|
|
555
|
+
# Perform reconciliation to deploy configured agents
|
|
556
|
+
project_path = Path.cwd()
|
|
557
|
+
agent_result, _skill_result = perform_startup_reconciliation(
|
|
558
|
+
project_path=project_path, config=unified_config, silent=False
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
# Display results with progress bar
|
|
562
|
+
total_operations = (
|
|
563
|
+
len(agent_result.deployed)
|
|
564
|
+
+ len(agent_result.removed)
|
|
565
|
+
+ len(agent_result.unchanged)
|
|
566
|
+
)
|
|
750
567
|
|
|
751
|
-
|
|
568
|
+
if total_operations > 0:
|
|
752
569
|
deploy_progress = ProgressBar(
|
|
753
|
-
total=
|
|
570
|
+
total=total_operations,
|
|
754
571
|
prefix="Deploying agents",
|
|
755
572
|
show_percentage=True,
|
|
756
573
|
show_counter=True,
|
|
757
574
|
)
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
)
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
575
|
+
deploy_progress.update(total_operations)
|
|
576
|
+
|
|
577
|
+
# Build summary message
|
|
578
|
+
deployed = len(agent_result.deployed)
|
|
579
|
+
removed = len(agent_result.removed)
|
|
580
|
+
unchanged = len(agent_result.unchanged)
|
|
581
|
+
|
|
582
|
+
summary_parts = []
|
|
583
|
+
if deployed > 0:
|
|
584
|
+
summary_parts.append(f"{deployed} new")
|
|
585
|
+
if removed > 0:
|
|
586
|
+
summary_parts.append(f"{removed} removed")
|
|
587
|
+
if unchanged > 0:
|
|
588
|
+
summary_parts.append(f"{unchanged} unchanged")
|
|
589
|
+
|
|
590
|
+
summary = f"Complete: {', '.join(summary_parts)}"
|
|
591
|
+
deploy_progress.finish(summary)
|
|
592
|
+
|
|
593
|
+
# Display errors if any
|
|
594
|
+
if agent_result.errors:
|
|
595
|
+
logger.warning(
|
|
596
|
+
f"Agent deployment completed with {len(agent_result.errors)} errors"
|
|
777
597
|
)
|
|
598
|
+
print("\n⚠️ Agent Deployment Errors:")
|
|
599
|
+
max_errors_to_show = 10
|
|
600
|
+
errors_to_display = agent_result.errors[:max_errors_to_show]
|
|
778
601
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
if deployed > 0 or updated > 0:
|
|
782
|
-
if removed > 0:
|
|
783
|
-
deploy_progress.finish(
|
|
784
|
-
f"Complete: {deployed} new, {updated} updated, {skipped} unchanged, "
|
|
785
|
-
f"{removed} removed ({total_configured} configured from {agent_count} files in cache)"
|
|
786
|
-
)
|
|
787
|
-
else:
|
|
788
|
-
deploy_progress.finish(
|
|
789
|
-
f"Complete: {deployed} new, {updated} updated, {skipped} unchanged "
|
|
790
|
-
f"({total_configured} configured from {agent_count} files in cache)"
|
|
791
|
-
)
|
|
792
|
-
elif removed > 0:
|
|
793
|
-
deploy_progress.finish(
|
|
794
|
-
f"Complete: {total_configured} agents deployed, "
|
|
795
|
-
f"{removed} removed ({agent_count} files in cache)"
|
|
796
|
-
)
|
|
797
|
-
else:
|
|
798
|
-
deploy_progress.finish(
|
|
799
|
-
f"Complete: {total_configured} agents deployed "
|
|
800
|
-
f"({agent_count} files in cache)"
|
|
801
|
-
)
|
|
602
|
+
for error in errors_to_display:
|
|
603
|
+
print(f" - {error}")
|
|
802
604
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
# Log for debugging
|
|
807
|
-
logger.warning(
|
|
808
|
-
f"Agent deployment completed with {len(deploy_errors)} errors: {deploy_errors}"
|
|
809
|
-
)
|
|
605
|
+
if len(agent_result.errors) > max_errors_to_show:
|
|
606
|
+
remaining = len(agent_result.errors) - max_errors_to_show
|
|
607
|
+
print(f" ... and {remaining} more error(s)")
|
|
810
608
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
errors_to_display = deploy_errors[:max_errors_to_show]
|
|
817
|
-
|
|
818
|
-
for error in errors_to_display:
|
|
819
|
-
# Format error message for readability
|
|
820
|
-
# Errors typically come as strings like "agent.md: Error message"
|
|
821
|
-
print(f" - {error}")
|
|
822
|
-
|
|
823
|
-
# If more errors exist, show count
|
|
824
|
-
if len(deploy_errors) > max_errors_to_show:
|
|
825
|
-
remaining = len(deploy_errors) - max_errors_to_show
|
|
826
|
-
print(f" ... and {remaining} more error(s)")
|
|
827
|
-
|
|
828
|
-
# Show summary message
|
|
829
|
-
print(
|
|
830
|
-
f"\n❌ Failed to deploy {len(deploy_errors)} agent(s). Please check the error messages above."
|
|
831
|
-
)
|
|
832
|
-
print(" Run with --verbose for detailed error information.\n")
|
|
609
|
+
print(
|
|
610
|
+
f"\n❌ Failed to deploy {len(agent_result.errors)} agent(s). "
|
|
611
|
+
"Please check the error messages above."
|
|
612
|
+
)
|
|
613
|
+
print(" Run with --verbose for detailed error information.\n")
|
|
833
614
|
|
|
834
615
|
except Exception as e:
|
|
835
616
|
# Deployment failure shouldn't block startup
|
|
@@ -838,6 +619,11 @@ def sync_remote_agents_on_startup():
|
|
|
838
619
|
logger = get_logger("cli")
|
|
839
620
|
logger.warning(f"Failed to deploy agents from cache: {e}")
|
|
840
621
|
|
|
622
|
+
# Phase 4: Cleanup legacy agent cache directories (after sync/deployment)
|
|
623
|
+
# CRITICAL: This must run AFTER sync completes because sync may recreate
|
|
624
|
+
# legacy directories. Running cleanup here ensures they're removed.
|
|
625
|
+
cleanup_legacy_agent_cache()
|
|
626
|
+
|
|
841
627
|
except Exception as e:
|
|
842
628
|
# Non-critical - log but don't fail startup
|
|
843
629
|
from ..core.logger import get_logger
|
|
@@ -846,8 +632,14 @@ def sync_remote_agents_on_startup():
|
|
|
846
632
|
logger.debug(f"Failed to sync remote agents: {e}")
|
|
847
633
|
# Continue execution - agent sync failure shouldn't block startup
|
|
848
634
|
|
|
635
|
+
# Cleanup legacy cache even if sync failed
|
|
636
|
+
try:
|
|
637
|
+
cleanup_legacy_agent_cache()
|
|
638
|
+
except Exception: # nosec B110
|
|
639
|
+
pass # Ignore cleanup errors
|
|
640
|
+
|
|
849
641
|
|
|
850
|
-
def sync_remote_skills_on_startup():
|
|
642
|
+
def sync_remote_skills_on_startup(force_sync: bool = False):
|
|
851
643
|
"""
|
|
852
644
|
Synchronize skill templates from remote sources on startup.
|
|
853
645
|
|
|
@@ -865,6 +657,9 @@ def sync_remote_skills_on_startup():
|
|
|
865
657
|
4. Apply profile filtering if active
|
|
866
658
|
5. Deploy resolved skills to ~/.claude/skills/ - Phase 2 progress bar
|
|
867
659
|
6. Log deployment results with source indication
|
|
660
|
+
|
|
661
|
+
Args:
|
|
662
|
+
force_sync: Force download even if cache is fresh (bypasses ETag).
|
|
868
663
|
"""
|
|
869
664
|
try:
|
|
870
665
|
from pathlib import Path
|
|
@@ -970,7 +765,7 @@ def sync_remote_skills_on_startup():
|
|
|
970
765
|
|
|
971
766
|
# Sync all sources with progress callback
|
|
972
767
|
results = manager.sync_all_sources(
|
|
973
|
-
force=
|
|
768
|
+
force=force_sync, progress_callback=sync_progress.update
|
|
974
769
|
)
|
|
975
770
|
|
|
976
771
|
# Finish sync progress bar with clear breakdown
|
|
@@ -990,22 +785,37 @@ def sync_remote_skills_on_startup():
|
|
|
990
785
|
|
|
991
786
|
# Phase 2: Scan agents and save to configuration.yaml
|
|
992
787
|
# This step populates configuration.yaml with agent-referenced skills
|
|
993
|
-
#
|
|
994
|
-
#
|
|
995
|
-
# Previous behavior: If skills were cached, agent scan was skipped,
|
|
996
|
-
# leaving agent_referenced: [] empty, which prevented cleanup.
|
|
788
|
+
# CRITICAL: Always scan agents to populate agent_referenced, even when using cached skills.
|
|
789
|
+
# Without this, skill_filter=None causes ALL skills to deploy and NO cleanup to run.
|
|
997
790
|
agents_dir = Path.cwd() / ".claude" / "agents"
|
|
998
791
|
|
|
999
|
-
# Scan agents for skill requirements (
|
|
792
|
+
# Scan agents for skill requirements (ALWAYS run to ensure cleanup works)
|
|
1000
793
|
agent_skills = get_required_skills_from_agents(agents_dir)
|
|
794
|
+
logger.info(
|
|
795
|
+
f"Agent scan found {len(agent_skills)} unique skills across deployed agents"
|
|
796
|
+
)
|
|
1001
797
|
|
|
1002
798
|
# Save to project-level configuration.yaml
|
|
1003
799
|
project_config_path = Path.cwd() / ".claude-mpm" / "configuration.yaml"
|
|
1004
800
|
save_agent_skills_to_config(list(agent_skills), project_config_path)
|
|
801
|
+
logger.debug(
|
|
802
|
+
f"Saved {len(agent_skills)} agent-referenced skills to {project_config_path}"
|
|
803
|
+
)
|
|
1005
804
|
|
|
1006
805
|
# Phase 3: Resolve which skills to deploy (user_defined or agent_referenced)
|
|
1007
806
|
skills_to_deploy, skill_source = get_skills_to_deploy(project_config_path)
|
|
1008
807
|
|
|
808
|
+
# CRITICAL DEBUG: Log deployment resolution to diagnose cleanup issues
|
|
809
|
+
if skills_to_deploy:
|
|
810
|
+
logger.info(
|
|
811
|
+
f"Resolved {len(skills_to_deploy)} skills from {skill_source} (cleanup will run)"
|
|
812
|
+
)
|
|
813
|
+
else:
|
|
814
|
+
logger.warning(
|
|
815
|
+
f"No skills resolved from {skill_source} - will deploy ALL skills WITHOUT cleanup! "
|
|
816
|
+
f"This may indicate agent_referenced is empty in configuration.yaml."
|
|
817
|
+
)
|
|
818
|
+
|
|
1009
819
|
# Phase 4: Apply profile filtering if active
|
|
1010
820
|
if active_profile and profile_manager.active_profile:
|
|
1011
821
|
# Filter skills based on profile
|
|
@@ -1053,9 +863,7 @@ def sync_remote_skills_on_startup():
|
|
|
1053
863
|
total_skill_count = len(all_skills)
|
|
1054
864
|
|
|
1055
865
|
# Determine skill count based on resolution
|
|
1056
|
-
skill_count = (
|
|
1057
|
-
len(skills_to_deploy) if skills_to_deploy else total_skill_count
|
|
1058
|
-
)
|
|
866
|
+
skill_count = len(skills_to_deploy) if skills_to_deploy else total_skill_count
|
|
1059
867
|
|
|
1060
868
|
if skill_count > 0:
|
|
1061
869
|
# Deploy skills with resolved filter
|
|
@@ -1066,8 +874,13 @@ def sync_remote_skills_on_startup():
|
|
|
1066
874
|
# Deploy to project-local directory with cleanup
|
|
1067
875
|
deployment_result = manager.deploy_skills(
|
|
1068
876
|
target_dir=Path.cwd() / ".claude" / "skills",
|
|
1069
|
-
force=
|
|
1070
|
-
|
|
877
|
+
force=force_sync,
|
|
878
|
+
# CRITICAL FIX: Empty list should mean "deploy no skills", not "deploy all"
|
|
879
|
+
# When skills_to_deploy is [], we want skill_filter=set() NOT skill_filter=None
|
|
880
|
+
# None means "no filtering" (deploy all), empty set means "filter to nothing"
|
|
881
|
+
skill_filter=set(skills_to_deploy)
|
|
882
|
+
if skills_to_deploy is not None
|
|
883
|
+
else None,
|
|
1071
884
|
)
|
|
1072
885
|
|
|
1073
886
|
# REMOVED: User-level deployment (lines 1068-1074)
|
|
@@ -1078,6 +891,7 @@ def sync_remote_skills_on_startup():
|
|
|
1078
891
|
deployed = deployment_result.get("deployed_count", 0)
|
|
1079
892
|
skipped = deployment_result.get("skipped_count", 0)
|
|
1080
893
|
filtered = deployment_result.get("filtered_count", 0)
|
|
894
|
+
removed = deployment_result.get("removed_count", 0)
|
|
1081
895
|
total_available = deployed + skipped
|
|
1082
896
|
|
|
1083
897
|
# Only show progress bar if there are skills to deploy
|
|
@@ -1107,16 +921,25 @@ def sync_remote_skills_on_startup():
|
|
|
1107
921
|
"user override" if skill_source == "user_defined" else "from agents"
|
|
1108
922
|
)
|
|
1109
923
|
|
|
1110
|
-
|
|
924
|
+
# Build finish message with cleanup info
|
|
925
|
+
if deployed > 0 or removed > 0:
|
|
926
|
+
parts = []
|
|
927
|
+
if deployed > 0:
|
|
928
|
+
parts.append(f"{deployed} new")
|
|
929
|
+
if skipped > 0:
|
|
930
|
+
parts.append(f"{skipped} unchanged")
|
|
931
|
+
if removed > 0:
|
|
932
|
+
parts.append(f"{removed} removed")
|
|
933
|
+
|
|
934
|
+
status = ", ".join(parts)
|
|
935
|
+
|
|
1111
936
|
if filtered > 0:
|
|
1112
937
|
deploy_progress.finish(
|
|
1113
|
-
f"Complete: {
|
|
1114
|
-
f"({total_available} {source_label}, {filtered} files in cache)"
|
|
938
|
+
f"Complete: {status} ({total_available} {source_label}, {filtered} files in cache)"
|
|
1115
939
|
)
|
|
1116
940
|
else:
|
|
1117
941
|
deploy_progress.finish(
|
|
1118
|
-
f"Complete: {
|
|
1119
|
-
f"({total_available} skills {source_label} from {total_skill_count} files in cache)"
|
|
942
|
+
f"Complete: {status} ({total_available} skills {source_label} from {total_skill_count} files in cache)"
|
|
1120
943
|
)
|
|
1121
944
|
elif filtered > 0:
|
|
1122
945
|
# Skills filtered means agents require fewer skills than available
|
|
@@ -1124,10 +947,12 @@ def sync_remote_skills_on_startup():
|
|
|
1124
947
|
f"No skills needed ({source_label}, {total_skill_count} files in cache)"
|
|
1125
948
|
)
|
|
1126
949
|
else:
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
950
|
+
# No changes - all skills already deployed
|
|
951
|
+
msg = f"Complete: {total_available} skills {source_label}"
|
|
952
|
+
if removed > 0:
|
|
953
|
+
msg += f", {removed} removed"
|
|
954
|
+
msg += f" ({total_skill_count} files in cache)"
|
|
955
|
+
deploy_progress.finish(msg)
|
|
1131
956
|
|
|
1132
957
|
# Log deployment errors if any
|
|
1133
958
|
from ..core.logger import get_logger
|
|
@@ -1244,61 +1069,64 @@ def show_skill_summary():
|
|
|
1244
1069
|
Display skill availability summary on startup.
|
|
1245
1070
|
|
|
1246
1071
|
WHY: Users should see at a glance how many skills are deployed and available
|
|
1247
|
-
from
|
|
1072
|
+
from cache, similar to the agent summary showing "X deployed / Y cached".
|
|
1073
|
+
|
|
1074
|
+
DESIGN DECISION: Fast, non-blocking check that counts skills from:
|
|
1075
|
+
- Deployed skills: PROJECT-level .claude/skills/ directory
|
|
1076
|
+
- Cached skills: ~/.claude-mpm/cache/skills/ directory (from remote sources)
|
|
1248
1077
|
|
|
1249
|
-
|
|
1250
|
-
directory and collection repos. Shows "X installed (Y available)" format.
|
|
1078
|
+
Shows format: "✓ Skills: X deployed / Y cached"
|
|
1251
1079
|
Failures are silent to avoid blocking startup.
|
|
1252
1080
|
"""
|
|
1253
1081
|
try:
|
|
1254
1082
|
from pathlib import Path
|
|
1255
1083
|
|
|
1256
|
-
# Count deployed skills (
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
if
|
|
1084
|
+
# Count deployed skills (PROJECT-level, not user-level)
|
|
1085
|
+
project_skills_dir = Path.cwd() / ".claude" / "skills"
|
|
1086
|
+
deployed_count = 0
|
|
1087
|
+
if project_skills_dir.exists():
|
|
1260
1088
|
# Count directories with SKILL.md (excludes collection repos)
|
|
1261
1089
|
# Exclude collection directories (obra-superpowers, etc.)
|
|
1262
1090
|
skill_dirs = [
|
|
1263
1091
|
d
|
|
1264
|
-
for d in
|
|
1092
|
+
for d in project_skills_dir.iterdir()
|
|
1265
1093
|
if d.is_dir()
|
|
1266
1094
|
and (d / "SKILL.md").exists()
|
|
1267
1095
|
and not (d / ".git").exists() # Exclude collection repos
|
|
1268
1096
|
]
|
|
1269
|
-
|
|
1097
|
+
deployed_count = len(skill_dirs)
|
|
1270
1098
|
|
|
1271
|
-
# Count
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
):
|
|
1099
|
+
# Count cached skills (from remote sources, not deployed yet)
|
|
1100
|
+
# This matches the agent summary pattern: deployed vs cached
|
|
1101
|
+
cache_dir = Path.home() / ".claude-mpm" / "cache" / "skills"
|
|
1102
|
+
cached_count = 0
|
|
1103
|
+
if cache_dir.exists():
|
|
1104
|
+
# Scan all repository directories in cache
|
|
1105
|
+
# Cache structure: ~/.claude-mpm/cache/skills/{owner}/{repo}/...
|
|
1106
|
+
for repo_dir in cache_dir.rglob("*"):
|
|
1107
|
+
if not repo_dir.is_dir():
|
|
1280
1108
|
continue
|
|
1281
1109
|
|
|
1282
|
-
# Count skill directories
|
|
1110
|
+
# Count skill directories (those with SKILL.md)
|
|
1283
1111
|
# Skills can be nested in: skills/category/skill-name/SKILL.md
|
|
1284
1112
|
# or in flat structure: skill-name/SKILL.md
|
|
1285
|
-
for root, dirs, files in os.walk(
|
|
1113
|
+
for root, dirs, files in os.walk(repo_dir):
|
|
1286
1114
|
if "SKILL.md" in files:
|
|
1287
|
-
# Exclude build artifacts and hidden directories
|
|
1288
|
-
# Get relative path from collection_dir to avoid excluding based on .claude parent
|
|
1115
|
+
# Exclude build artifacts and hidden directories
|
|
1289
1116
|
root_path = Path(root)
|
|
1290
|
-
relative_parts = root_path.relative_to(collection_dir).parts
|
|
1291
1117
|
if not any(
|
|
1292
1118
|
part.startswith(".")
|
|
1293
1119
|
or part in ["dist", "build", "__pycache__"]
|
|
1294
|
-
for part in
|
|
1120
|
+
for part in root_path.parts
|
|
1295
1121
|
):
|
|
1296
|
-
|
|
1122
|
+
cached_count += 1
|
|
1297
1123
|
|
|
1298
|
-
# Display summary
|
|
1299
|
-
|
|
1124
|
+
# Display summary using agent summary format: "X deployed / Y cached"
|
|
1125
|
+
# Only show non-deployed cached skills (subtract deployed from cached)
|
|
1126
|
+
non_deployed_cached = max(0, cached_count - deployed_count)
|
|
1127
|
+
if deployed_count > 0 or non_deployed_cached > 0:
|
|
1300
1128
|
print(
|
|
1301
|
-
f"✓ Skills: {
|
|
1129
|
+
f"✓ Skills: {deployed_count} deployed / {non_deployed_cached} cached",
|
|
1302
1130
|
flush=True,
|
|
1303
1131
|
)
|
|
1304
1132
|
|
|
@@ -1372,7 +1200,7 @@ def auto_install_chrome_devtools_on_startup():
|
|
|
1372
1200
|
if not chrome_devtools_config.get("auto_install", True):
|
|
1373
1201
|
# Auto-install disabled, skip silently
|
|
1374
1202
|
return
|
|
1375
|
-
except Exception:
|
|
1203
|
+
except Exception: # nosec B110
|
|
1376
1204
|
# If config loading fails, assume auto-install is enabled (default)
|
|
1377
1205
|
pass
|
|
1378
1206
|
|
|
@@ -1390,7 +1218,7 @@ def auto_install_chrome_devtools_on_startup():
|
|
|
1390
1218
|
# Continue execution - chrome-devtools installation failure shouldn't block startup
|
|
1391
1219
|
|
|
1392
1220
|
|
|
1393
|
-
def run_background_services():
|
|
1221
|
+
def run_background_services(force_sync: bool = False):
|
|
1394
1222
|
"""
|
|
1395
1223
|
Initialize all background services on startup.
|
|
1396
1224
|
|
|
@@ -1401,6 +1229,9 @@ def run_background_services():
|
|
|
1401
1229
|
explicitly requests them via agent-manager commands. This prevents unwanted
|
|
1402
1230
|
file creation in project .claude/ directories.
|
|
1403
1231
|
See: SystemInstructionsDeployer and agent_deployment.py line 504-509
|
|
1232
|
+
|
|
1233
|
+
Args:
|
|
1234
|
+
force_sync: Force download even if cache is fresh (bypasses ETag).
|
|
1404
1235
|
"""
|
|
1405
1236
|
# Sync hooks early to ensure up-to-date configuration
|
|
1406
1237
|
# RATIONALE: Hooks should be synced before other services to fix stale configs
|
|
@@ -1411,7 +1242,9 @@ def run_background_services():
|
|
|
1411
1242
|
check_mcp_auto_configuration()
|
|
1412
1243
|
verify_mcp_gateway_startup()
|
|
1413
1244
|
check_for_updates_async()
|
|
1414
|
-
sync_remote_agents_on_startup(
|
|
1245
|
+
sync_remote_agents_on_startup(
|
|
1246
|
+
force_sync=force_sync
|
|
1247
|
+
) # Sync agents from remote sources
|
|
1415
1248
|
show_agent_summary() # Display agent counts after deployment
|
|
1416
1249
|
|
|
1417
1250
|
# Skills deployment order (precedence: remote > bundled)
|
|
@@ -1420,7 +1253,9 @@ def run_background_services():
|
|
|
1420
1253
|
# 3. Discover and link runtime skills (user-added skills)
|
|
1421
1254
|
# This ensures remote skills take precedence over bundled skills when names conflict
|
|
1422
1255
|
deploy_bundled_skills() # Base layer: package-bundled skills
|
|
1423
|
-
sync_remote_skills_on_startup(
|
|
1256
|
+
sync_remote_skills_on_startup(
|
|
1257
|
+
force_sync=force_sync
|
|
1258
|
+
) # Override layer: Git-based skills (takes precedence)
|
|
1424
1259
|
discover_and_link_runtime_skills() # Discovery: user-added skills
|
|
1425
1260
|
show_skill_summary() # Display skill counts after deployment
|
|
1426
1261
|
verify_and_show_pm_skills() # PM skills verification and status
|
|
@@ -1620,7 +1455,7 @@ def verify_mcp_gateway_startup():
|
|
|
1620
1455
|
loop.run_until_complete(
|
|
1621
1456
|
asyncio.gather(*pending, return_exceptions=True)
|
|
1622
1457
|
)
|
|
1623
|
-
except Exception:
|
|
1458
|
+
except Exception: # nosec B110
|
|
1624
1459
|
pass # Ignore cleanup errors
|
|
1625
1460
|
finally:
|
|
1626
1461
|
loop.close()
|
|
@@ -1714,7 +1549,7 @@ def check_for_updates_async():
|
|
|
1714
1549
|
|
|
1715
1550
|
logger = get_logger("upgrade_check")
|
|
1716
1551
|
logger.debug(f"Update check failed (non-critical): {e}")
|
|
1717
|
-
except Exception:
|
|
1552
|
+
except Exception: # nosec B110
|
|
1718
1553
|
pass # Avoid any errors in error handling
|
|
1719
1554
|
finally:
|
|
1720
1555
|
# Properly clean up event loop
|
|
@@ -1729,7 +1564,7 @@ def check_for_updates_async():
|
|
|
1729
1564
|
loop.run_until_complete(
|
|
1730
1565
|
asyncio.gather(*pending, return_exceptions=True)
|
|
1731
1566
|
)
|
|
1732
|
-
except Exception:
|
|
1567
|
+
except Exception: # nosec B110
|
|
1733
1568
|
pass # Ignore cleanup errors
|
|
1734
1569
|
finally:
|
|
1735
1570
|
loop.close()
|