claude-mpm 5.4.64__py3-none-any.whl → 5.4.96__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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md +405 -0
- claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +66 -241
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +107 -1928
- claude_mpm/agents/PM_INSTRUCTIONS.md +82 -686
- 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/autotodos.py +526 -0
- claude_mpm/cli/commands/configure.py +620 -21
- claude_mpm/cli/commands/monitor.py +2 -2
- claude_mpm/cli/commands/mpm_init/core.py +2 -2
- claude_mpm/cli/commands/skills.py +166 -14
- claude_mpm/cli/executor.py +89 -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 +59 -1
- claude_mpm/cli/startup.py +202 -367
- claude_mpm/cli/startup_display.py +72 -5
- claude_mpm/cli/startup_logging.py +2 -2
- claude_mpm/commands/mpm-session-resume.md +1 -1
- claude_mpm/constants.py +1 -0
- claude_mpm/core/claude_runner.py +2 -2
- claude_mpm/core/hook_manager.py +51 -3
- 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/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/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
- claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.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__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +486 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +216 -11
- claude_mpm/hooks/claude_hooks/hook_handler.py +28 -4
- claude_mpm/hooks/claude_hooks/response_tracking.py +3 -1
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +20 -0
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +30 -6
- claude_mpm/hooks/session_resume_hook.py +85 -1
- claude_mpm/init.py +1 -1
- claude_mpm/services/agents/cache_git_manager.py +1 -1
- claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
- claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
- claude_mpm/services/agents/startup_sync.py +5 -2
- claude_mpm/services/cli/__init__.py +3 -0
- claude_mpm/services/cli/incremental_pause_manager.py +561 -0
- claude_mpm/services/cli/session_resume_helper.py +10 -2
- claude_mpm/services/delegation_detector.py +175 -0
- claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
- claude_mpm/services/diagnostics/models.py +14 -1
- claude_mpm/services/event_log.py +317 -0
- claude_mpm/services/infrastructure/__init__.py +4 -0
- claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
- claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
- claude_mpm/services/monitor/daemon_manager.py +15 -4
- claude_mpm/services/monitor/management/lifecycle.py +8 -2
- claude_mpm/services/monitor/server.py +106 -16
- claude_mpm/services/pm_skills_deployer.py +177 -83
- claude_mpm/services/skills/git_skill_source_manager.py +5 -1
- claude_mpm/services/skills/selective_skill_deployer.py +114 -26
- claude_mpm/services/socketio/handlers/hook.py +14 -7
- claude_mpm/services/socketio/server/main.py +12 -4
- claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
- claude_mpm/skills/bundled/pm/mpm-bug-reporting/SKILL.md +248 -0
- claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
- claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
- claude_mpm/skills/bundled/pm/mpm-teaching-mode/SKILL.md +657 -0
- claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
- claude_mpm/skills/skill_manager.py +4 -4
- claude_mpm/utils/agent_dependency_loader.py +103 -4
- claude_mpm/utils/robust_installer.py +45 -24
- claude_mpm-5.4.96.dist-info/METADATA +377 -0
- {claude_mpm-5.4.64.dist-info → claude_mpm-5.4.96.dist-info}/RECORD +153 -131
- 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.64.dist-info/METADATA +0 -999
- /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.64.dist-info → claude_mpm-5.4.96.dist-info}/WHEEL +0 -0
- {claude_mpm-5.4.64.dist-info → claude_mpm-5.4.96.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.4.64.dist-info → claude_mpm-5.4.96.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.4.64.dist-info → claude_mpm-5.4.96.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.4.64.dist-info → claude_mpm-5.4.96.dist-info}/top_level.txt +0 -0
claude_mpm/cli/startup.py
CHANGED
|
@@ -191,7 +191,8 @@ def should_skip_background_services(args, processed_argv):
|
|
|
191
191
|
skip_commands = ["--version", "-v", "--help", "-h"]
|
|
192
192
|
return any(cmd in (processed_argv or sys.argv[1:]) for cmd in skip_commands) or (
|
|
193
193
|
hasattr(args, "command")
|
|
194
|
-
and args.command
|
|
194
|
+
and args.command
|
|
195
|
+
in ["info", "doctor", "config", "mcp", "configure", "hook-errors", "autotodos"]
|
|
195
196
|
)
|
|
196
197
|
|
|
197
198
|
|
|
@@ -234,7 +235,7 @@ def deploy_bundled_skills():
|
|
|
234
235
|
if not skills_config.get("auto_deploy", True):
|
|
235
236
|
# Auto-deploy disabled, skip silently
|
|
236
237
|
return
|
|
237
|
-
except Exception:
|
|
238
|
+
except Exception: # nosec B110
|
|
238
239
|
# If config loading fails, assume auto-deploy is enabled (default)
|
|
239
240
|
pass
|
|
240
241
|
|
|
@@ -308,68 +309,60 @@ def deploy_output_style_on_startup():
|
|
|
308
309
|
communication without emojis and exclamation points. Styles are project-specific
|
|
309
310
|
to allow different projects to have different communication styles.
|
|
310
311
|
|
|
311
|
-
DESIGN DECISION: This is non-blocking and idempotent. Deploys to
|
|
312
|
-
directory (
|
|
313
|
-
|
|
312
|
+
DESIGN DECISION: This is non-blocking and idempotent. Deploys to user-level
|
|
313
|
+
directory (~/.claude/output-styles/) which is the official Claude Code location
|
|
314
|
+
for custom output styles.
|
|
314
315
|
|
|
315
|
-
Deploys
|
|
316
|
-
- claude-mpm
|
|
316
|
+
Deploys all styles:
|
|
317
|
+
- claude-mpm.md (professional mode)
|
|
317
318
|
- claude-mpm-teacher.md (teaching mode)
|
|
319
|
+
- claude-mpm-founders.md (founders mode)
|
|
318
320
|
"""
|
|
319
321
|
try:
|
|
320
|
-
import
|
|
321
|
-
from pathlib import Path
|
|
322
|
+
from ..core.output_style_manager import OutputStyleManager
|
|
322
323
|
|
|
323
|
-
#
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
output_styles_dir = user_home / ".claude" / "settings" / "output-styles"
|
|
332
|
-
professional_target = output_styles_dir / "claude-mpm-style.md"
|
|
333
|
-
teacher_target = output_styles_dir / "claude-mpm-teacher.md"
|
|
334
|
-
|
|
335
|
-
# Create directory if it doesn't exist
|
|
336
|
-
output_styles_dir.mkdir(parents=True, exist_ok=True)
|
|
337
|
-
|
|
338
|
-
# Check if already deployed AND up-to-date (compare sizes to detect changes)
|
|
339
|
-
professional_up_to_date = (
|
|
340
|
-
professional_target.exists()
|
|
341
|
-
and professional_source.exists()
|
|
342
|
-
and professional_target.stat().st_size == professional_source.stat().st_size
|
|
343
|
-
)
|
|
344
|
-
teacher_up_to_date = (
|
|
345
|
-
teacher_target.exists()
|
|
346
|
-
and teacher_source.exists()
|
|
347
|
-
and teacher_target.stat().st_size == teacher_source.stat().st_size
|
|
348
|
-
)
|
|
324
|
+
# Initialize the output style manager
|
|
325
|
+
manager = OutputStyleManager()
|
|
326
|
+
|
|
327
|
+
# Check if Claude Code version supports output styles (>= 1.0.83)
|
|
328
|
+
if not manager.supports_output_styles():
|
|
329
|
+
# Skip deployment for older versions
|
|
330
|
+
# The manager will fall back to injecting content directly
|
|
331
|
+
return
|
|
349
332
|
|
|
350
|
-
if
|
|
333
|
+
# Check if all styles are already deployed and up-to-date
|
|
334
|
+
all_up_to_date = True
|
|
335
|
+
for style_config in manager.styles.values():
|
|
336
|
+
source_path = style_config["source"]
|
|
337
|
+
target_path = style_config["target"]
|
|
338
|
+
|
|
339
|
+
if not (
|
|
340
|
+
target_path.exists()
|
|
341
|
+
and source_path.exists()
|
|
342
|
+
and target_path.stat().st_size == source_path.stat().st_size
|
|
343
|
+
):
|
|
344
|
+
all_up_to_date = False
|
|
345
|
+
break
|
|
346
|
+
|
|
347
|
+
if all_up_to_date:
|
|
351
348
|
# Show feedback that output styles are ready
|
|
352
349
|
print("✓ Output styles ready", flush=True)
|
|
353
350
|
return
|
|
354
351
|
|
|
355
|
-
# Deploy
|
|
356
|
-
|
|
357
|
-
if professional_source.exists():
|
|
358
|
-
shutil.copy2(professional_source, professional_target)
|
|
359
|
-
deployed_count += 1
|
|
352
|
+
# Deploy all styles using the manager
|
|
353
|
+
results = manager.deploy_all_styles(activate_default=True)
|
|
360
354
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
deployed_count += 1
|
|
355
|
+
# Count successful deployments
|
|
356
|
+
deployed_count = sum(1 for success in results.values() if success)
|
|
364
357
|
|
|
365
358
|
if deployed_count > 0:
|
|
366
359
|
print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
|
|
367
360
|
else:
|
|
368
|
-
#
|
|
361
|
+
# Deployment failed - log but don't fail startup
|
|
369
362
|
from ..core.logger import get_logger
|
|
370
363
|
|
|
371
364
|
logger = get_logger("cli")
|
|
372
|
-
logger.debug("
|
|
365
|
+
logger.debug("Failed to deploy any output styles")
|
|
373
366
|
|
|
374
367
|
except Exception as e:
|
|
375
368
|
# Non-critical - log but don't fail startup
|
|
@@ -458,7 +451,7 @@ def _cleanup_orphaned_agents(deploy_target: Path, deployed_agents: list[str]) ->
|
|
|
458
451
|
return removed_count
|
|
459
452
|
|
|
460
453
|
|
|
461
|
-
def sync_remote_agents_on_startup():
|
|
454
|
+
def sync_remote_agents_on_startup(force_sync: bool = False):
|
|
462
455
|
"""
|
|
463
456
|
Synchronize agent templates from remote sources on startup.
|
|
464
457
|
|
|
@@ -471,16 +464,16 @@ def sync_remote_agents_on_startup():
|
|
|
471
464
|
block startup to ensure claude-mpm remains functional.
|
|
472
465
|
|
|
473
466
|
Workflow:
|
|
474
|
-
1.
|
|
475
|
-
2.
|
|
476
|
-
3.
|
|
477
|
-
4. Cleanup
|
|
467
|
+
1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
|
|
468
|
+
2. Deploy agents to ~/.claude/agents/ - Phase 2 progress bar
|
|
469
|
+
3. Cleanup orphaned agents (ours but no longer deployed) - Phase 3
|
|
470
|
+
4. Cleanup legacy agent cache directories (after sync/deployment) - Phase 4
|
|
478
471
|
5. Log deployment results
|
|
479
|
-
"""
|
|
480
|
-
# Cleanup legacy cache directories first (before syncing)
|
|
481
|
-
cleanup_legacy_agent_cache()
|
|
482
472
|
|
|
483
|
-
|
|
473
|
+
Args:
|
|
474
|
+
force_sync: Force download even if cache is fresh (bypasses ETag).
|
|
475
|
+
"""
|
|
476
|
+
# DEPRECATED: Legacy warning - no-op function, kept for compatibility
|
|
484
477
|
check_legacy_cache()
|
|
485
478
|
|
|
486
479
|
try:
|
|
@@ -489,7 +482,6 @@ def sync_remote_agents_on_startup():
|
|
|
489
482
|
from pathlib import Path
|
|
490
483
|
|
|
491
484
|
from ..core.shared.config_loader import ConfigLoader
|
|
492
|
-
from ..services.agents.deployment.agent_deployment import AgentDeploymentService
|
|
493
485
|
from ..services.agents.startup_sync import sync_agents_on_startup
|
|
494
486
|
from ..services.profile_manager import ProfileManager
|
|
495
487
|
from ..utils.progress import ProgressBar
|
|
@@ -514,7 +506,7 @@ def sync_remote_agents_on_startup():
|
|
|
514
506
|
)
|
|
515
507
|
|
|
516
508
|
# Phase 1: Sync files from Git sources
|
|
517
|
-
result = sync_agents_on_startup()
|
|
509
|
+
result = sync_agents_on_startup(force_refresh=force_sync)
|
|
518
510
|
|
|
519
511
|
# Only proceed with deployment if sync was enabled and ran
|
|
520
512
|
if result.get("enabled") and result.get("sources_synced", 0) > 0:
|
|
@@ -537,304 +529,89 @@ def sync_remote_agents_on_startup():
|
|
|
537
529
|
logger.warning(f"Agent sync completed with {len(errors)} errors")
|
|
538
530
|
|
|
539
531
|
# Phase 2: Deploy agents from cache to ~/.claude/agents/
|
|
540
|
-
#
|
|
532
|
+
# Use reconciliation service to respect configuration.yaml settings
|
|
541
533
|
try:
|
|
542
|
-
# Initialize deployment service with profile-filtered configuration
|
|
543
|
-
from ..core.config import Config
|
|
544
|
-
|
|
545
|
-
deploy_config = None
|
|
546
|
-
if active_profile and profile_manager.active_profile:
|
|
547
|
-
# Create config with excluded agents based on profile
|
|
548
|
-
# Get all agents that should be excluded (not in enabled list)
|
|
549
|
-
from pathlib import Path
|
|
550
|
-
|
|
551
|
-
cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
|
|
552
|
-
if cache_dir.exists():
|
|
553
|
-
# Find all agent files
|
|
554
|
-
# Supports both flat cache and {owner}/{repo}/agents/ structure
|
|
555
|
-
all_agent_files = [
|
|
556
|
-
f
|
|
557
|
-
for f in cache_dir.rglob("*.md")
|
|
558
|
-
if "/agents/" in str(f)
|
|
559
|
-
and f.stem.lower() != "base-agent"
|
|
560
|
-
and f.name.lower()
|
|
561
|
-
not in {"readme.md", "changelog.md", "contributing.md"}
|
|
562
|
-
]
|
|
563
|
-
|
|
564
|
-
# Build exclusion list for agents not in profile
|
|
565
|
-
excluded_agents = []
|
|
566
|
-
for agent_file in all_agent_files:
|
|
567
|
-
agent_name = agent_file.stem
|
|
568
|
-
if not profile_manager.is_agent_enabled(agent_name):
|
|
569
|
-
excluded_agents.append(agent_name)
|
|
570
|
-
|
|
571
|
-
if excluded_agents:
|
|
572
|
-
# Get singleton config and update with profile settings
|
|
573
|
-
# BUGFIX: Config is a singleton that ignores dict parameter if already initialized.
|
|
574
|
-
# Creating Config({...}) doesn't store excluded_agents - use set() instead.
|
|
575
|
-
deploy_config = Config()
|
|
576
|
-
deploy_config.set(
|
|
577
|
-
"agent_deployment.excluded_agents", excluded_agents
|
|
578
|
-
)
|
|
579
|
-
deploy_config.set(
|
|
580
|
-
"agent_deployment.filter_non_mpm_agents", False
|
|
581
|
-
)
|
|
582
|
-
deploy_config.set("agent_deployment.case_sensitive", False)
|
|
583
|
-
deploy_config.set(
|
|
584
|
-
"agent_deployment.exclude_dependencies", False
|
|
585
|
-
)
|
|
586
|
-
logger.info(
|
|
587
|
-
f"Profile '{active_profile}': Excluding {len(excluded_agents)} agents from deployment"
|
|
588
|
-
)
|
|
589
|
-
|
|
590
|
-
deployment_service = AgentDeploymentService(config=deploy_config)
|
|
591
|
-
|
|
592
|
-
# Count agents in cache to show accurate progress
|
|
593
534
|
from pathlib import Path
|
|
594
535
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
# BUGFIX (cache-count-inflation): Clean up stale cache files
|
|
600
|
-
# from old repositories before counting to prevent inflated counts.
|
|
601
|
-
# Issue: Old caches like bobmatnyc/claude-mpm-agents/agents/
|
|
602
|
-
# were counted alongside current agents, inflating count
|
|
603
|
-
# from 44 to 85.
|
|
604
|
-
#
|
|
605
|
-
# Solution: Remove files with nested /agents/ paths
|
|
606
|
-
# (e.g., cache/agents/user/repo/agents/...)
|
|
607
|
-
# Keep only current agents (e.g., cache/agents/engineer/...)
|
|
608
|
-
removed_count = 0
|
|
609
|
-
stale_dirs = set()
|
|
610
|
-
|
|
611
|
-
for md_file in cache_dir.rglob("*.md"):
|
|
612
|
-
# Stale cache files have multiple /agents/ in their path RELATIVE to cache_dir
|
|
613
|
-
# Current: cache/agents/bobmatnyc/claude-mpm-agents/agents/engineer/...
|
|
614
|
-
# (1 occurrence in relative path: /agents/)
|
|
615
|
-
# Old flat: cache/agents/engineer/...
|
|
616
|
-
# (0 occurrences in relative path - no repo structure)
|
|
617
|
-
# The issue: str(md_file).count("/agents/") counts BOTH cache/agents/ AND repo/agents/
|
|
618
|
-
# Fix: Count /agents/ in path RELATIVE to cache_dir (after cache/agents/)
|
|
619
|
-
relative_path = str(md_file.relative_to(cache_dir))
|
|
620
|
-
if relative_path.count("/agents/") > 1:
|
|
621
|
-
# Track parent directory for cleanup
|
|
622
|
-
# Extract subdirectory under cache/agents/
|
|
623
|
-
# (e.g., "bobmatnyc")
|
|
624
|
-
parts = md_file.parts
|
|
625
|
-
cache_agents_idx = parts.index("agents")
|
|
626
|
-
if cache_agents_idx + 1 < len(parts):
|
|
627
|
-
stale_subdir = parts[cache_agents_idx + 1]
|
|
628
|
-
# Only remove if it's not a known category directory
|
|
629
|
-
if stale_subdir not in [
|
|
630
|
-
"engineer",
|
|
631
|
-
"ops",
|
|
632
|
-
"qa",
|
|
633
|
-
"universal",
|
|
634
|
-
"documentation",
|
|
635
|
-
"claude-mpm",
|
|
636
|
-
"security",
|
|
637
|
-
]:
|
|
638
|
-
stale_dirs.add(cache_dir / stale_subdir)
|
|
639
|
-
|
|
640
|
-
md_file.unlink()
|
|
641
|
-
removed_count += 1
|
|
642
|
-
|
|
643
|
-
# Remove empty stale directories
|
|
644
|
-
for stale_dir in stale_dirs:
|
|
645
|
-
if stale_dir.exists() and stale_dir.is_dir():
|
|
646
|
-
try:
|
|
647
|
-
# Remove directory and all contents
|
|
648
|
-
import shutil
|
|
649
|
-
|
|
650
|
-
shutil.rmtree(stale_dir)
|
|
651
|
-
except Exception:
|
|
652
|
-
pass # Ignore cleanup errors
|
|
653
|
-
|
|
654
|
-
if removed_count > 0:
|
|
655
|
-
from loguru import logger
|
|
656
|
-
|
|
657
|
-
logger.info(
|
|
658
|
-
f"Cleaned up {removed_count} stale cache files "
|
|
659
|
-
f"from old repositories"
|
|
660
|
-
)
|
|
536
|
+
from ..core.unified_config import UnifiedConfig
|
|
537
|
+
from ..services.agents.deployment.startup_reconciliation import (
|
|
538
|
+
perform_startup_reconciliation,
|
|
539
|
+
)
|
|
661
540
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
# (
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
"
|
|
675
|
-
"response_format.md",
|
|
676
|
-
"ticket_completeness_examples.md",
|
|
677
|
-
"validation_templates.md",
|
|
678
|
-
"git_file_tracking.md",
|
|
679
|
-
}
|
|
680
|
-
# Documentation files to exclude (by filename)
|
|
681
|
-
doc_files = {
|
|
682
|
-
"readme.md",
|
|
683
|
-
"changelog.md",
|
|
684
|
-
"contributing.md",
|
|
685
|
-
"implementation-summary.md",
|
|
686
|
-
"reorganization-plan.md",
|
|
687
|
-
"auto-deploy-index.md",
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
# Find all markdown files (after cleanup)
|
|
691
|
-
all_md_files = list(cache_dir.rglob("*.md"))
|
|
692
|
-
|
|
693
|
-
# Filter to only agent files:
|
|
694
|
-
# 1. Must have "/agents/" in path (current structure supports
|
|
695
|
-
# both flat and {owner}/{repo}/agents/ patterns)
|
|
696
|
-
# 2. Must not be in PM templates or doc files
|
|
697
|
-
# 3. Exclude BASE-AGENT.md which is not a deployable agent
|
|
698
|
-
# 4. Exclude build artifacts (dist/, build/, .cache/)
|
|
699
|
-
# to prevent double-counting
|
|
700
|
-
agent_files = [
|
|
701
|
-
f
|
|
702
|
-
for f in all_md_files
|
|
703
|
-
if (
|
|
704
|
-
# Must be in an agent directory
|
|
705
|
-
# Supports: cache/agents/{category}/... (flat)
|
|
706
|
-
# Supports: cache/agents/{owner}/{repo}/agents/{category}/... (GitHub sync)
|
|
707
|
-
"/agents/" in str(f)
|
|
708
|
-
# Exclude PM templates, doc files, and BASE-AGENT
|
|
709
|
-
and f.name.lower() not in pm_templates
|
|
710
|
-
and f.name.lower() not in doc_files
|
|
711
|
-
and f.name.lower() != "base-agent.md"
|
|
712
|
-
# Exclude build artifacts (prevents double-counting
|
|
713
|
-
# source + built files)
|
|
714
|
-
and not any(
|
|
715
|
-
part in str(f).split("/")
|
|
716
|
-
for part in ["dist", "build", ".cache"]
|
|
717
|
-
)
|
|
718
|
-
)
|
|
719
|
-
]
|
|
720
|
-
agent_count = len(agent_files)
|
|
721
|
-
|
|
722
|
-
if agent_count > 0:
|
|
723
|
-
# Deploy agents to project-level directory where Claude Code expects them
|
|
724
|
-
deploy_target = Path.cwd() / ".claude" / "agents"
|
|
725
|
-
deployment_result = deployment_service.deploy_agents(
|
|
726
|
-
target_dir=deploy_target,
|
|
727
|
-
force_rebuild=False, # Only deploy if versions differ
|
|
728
|
-
deployment_mode="update", # Version-aware updates
|
|
729
|
-
config=deploy_config, # Pass config to respect profile filtering
|
|
541
|
+
# Load configuration
|
|
542
|
+
unified_config = UnifiedConfig()
|
|
543
|
+
|
|
544
|
+
# Override with profile settings if active
|
|
545
|
+
if active_profile and profile_manager.active_profile:
|
|
546
|
+
# Get enabled agents from profile (returns Set[str])
|
|
547
|
+
profile_enabled_agents = (
|
|
548
|
+
profile_manager.active_profile.get_enabled_agents()
|
|
549
|
+
)
|
|
550
|
+
# Update config with profile's enabled list (convert Set to List)
|
|
551
|
+
unified_config.agents.enabled = list(profile_enabled_agents)
|
|
552
|
+
logger.info(
|
|
553
|
+
f"Profile '{active_profile}': Using {len(profile_enabled_agents)} enabled agents"
|
|
730
554
|
)
|
|
731
555
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
agent_count_in_target = len(
|
|
745
|
-
[
|
|
746
|
-
f
|
|
747
|
-
for f in existing_agents
|
|
748
|
-
if not f.name.startswith(("README", "INSTRUCTIONS"))
|
|
749
|
-
]
|
|
750
|
-
)
|
|
751
|
-
if agent_count_in_target > 0:
|
|
752
|
-
# All agents already deployed - count them as skipped
|
|
753
|
-
skipped = agent_count_in_target
|
|
754
|
-
total_configured = agent_count_in_target
|
|
556
|
+
# Perform reconciliation to deploy configured agents
|
|
557
|
+
project_path = Path.cwd()
|
|
558
|
+
agent_result, _skill_result = perform_startup_reconciliation(
|
|
559
|
+
project_path=project_path, config=unified_config, silent=False
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
# Display results with progress bar
|
|
563
|
+
total_operations = (
|
|
564
|
+
len(agent_result.deployed)
|
|
565
|
+
+ len(agent_result.removed)
|
|
566
|
+
+ len(agent_result.unchanged)
|
|
567
|
+
)
|
|
755
568
|
|
|
756
|
-
|
|
569
|
+
if total_operations > 0:
|
|
757
570
|
deploy_progress = ProgressBar(
|
|
758
|
-
total=
|
|
571
|
+
total=total_operations,
|
|
759
572
|
prefix="Deploying agents",
|
|
760
573
|
show_percentage=True,
|
|
761
574
|
show_counter=True,
|
|
762
575
|
)
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
)
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
576
|
+
deploy_progress.update(total_operations)
|
|
577
|
+
|
|
578
|
+
# Build summary message
|
|
579
|
+
deployed = len(agent_result.deployed)
|
|
580
|
+
removed = len(agent_result.removed)
|
|
581
|
+
unchanged = len(agent_result.unchanged)
|
|
582
|
+
|
|
583
|
+
summary_parts = []
|
|
584
|
+
if deployed > 0:
|
|
585
|
+
summary_parts.append(f"{deployed} new")
|
|
586
|
+
if removed > 0:
|
|
587
|
+
summary_parts.append(f"{removed} removed")
|
|
588
|
+
if unchanged > 0:
|
|
589
|
+
summary_parts.append(f"{unchanged} unchanged")
|
|
590
|
+
|
|
591
|
+
summary = f"Complete: {', '.join(summary_parts)}"
|
|
592
|
+
deploy_progress.finish(summary)
|
|
593
|
+
|
|
594
|
+
# Display errors if any
|
|
595
|
+
if agent_result.errors:
|
|
596
|
+
logger.warning(
|
|
597
|
+
f"Agent deployment completed with {len(agent_result.errors)} errors"
|
|
782
598
|
)
|
|
599
|
+
print("\n⚠️ Agent Deployment Errors:")
|
|
600
|
+
max_errors_to_show = 10
|
|
601
|
+
errors_to_display = agent_result.errors[:max_errors_to_show]
|
|
783
602
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
if deployed > 0 or updated > 0:
|
|
787
|
-
if removed > 0:
|
|
788
|
-
deploy_progress.finish(
|
|
789
|
-
f"Complete: {deployed} new, {updated} updated, {skipped} unchanged, "
|
|
790
|
-
f"{removed} removed ({total_configured} configured from {agent_count} files in cache)"
|
|
791
|
-
)
|
|
792
|
-
else:
|
|
793
|
-
deploy_progress.finish(
|
|
794
|
-
f"Complete: {deployed} new, {updated} updated, {skipped} unchanged "
|
|
795
|
-
f"({total_configured} configured from {agent_count} files in cache)"
|
|
796
|
-
)
|
|
797
|
-
elif removed > 0:
|
|
798
|
-
deploy_progress.finish(
|
|
799
|
-
f"Complete: {total_configured} agents deployed, "
|
|
800
|
-
f"{removed} removed ({agent_count} files in cache)"
|
|
801
|
-
)
|
|
802
|
-
else:
|
|
803
|
-
deploy_progress.finish(
|
|
804
|
-
f"Complete: {total_configured} agents deployed "
|
|
805
|
-
f"({agent_count} files in cache)"
|
|
806
|
-
)
|
|
807
|
-
|
|
808
|
-
# Display deployment errors to user (not just logs)
|
|
809
|
-
deploy_errors = deployment_result.get("errors", [])
|
|
810
|
-
if deploy_errors:
|
|
811
|
-
# Log for debugging
|
|
812
|
-
logger.warning(
|
|
813
|
-
f"Agent deployment completed with {len(deploy_errors)} errors: {deploy_errors}"
|
|
814
|
-
)
|
|
815
|
-
|
|
816
|
-
# Display errors to user with clear formatting
|
|
817
|
-
print("\n⚠️ Agent Deployment Errors:")
|
|
603
|
+
for error in errors_to_display:
|
|
604
|
+
print(f" - {error}")
|
|
818
605
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
606
|
+
if len(agent_result.errors) > max_errors_to_show:
|
|
607
|
+
remaining = len(agent_result.errors) - max_errors_to_show
|
|
608
|
+
print(f" ... and {remaining} more error(s)")
|
|
822
609
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
# If more errors exist, show count
|
|
829
|
-
if len(deploy_errors) > max_errors_to_show:
|
|
830
|
-
remaining = len(deploy_errors) - max_errors_to_show
|
|
831
|
-
print(f" ... and {remaining} more error(s)")
|
|
832
|
-
|
|
833
|
-
# Show summary message
|
|
834
|
-
print(
|
|
835
|
-
f"\n❌ Failed to deploy {len(deploy_errors)} agent(s). Please check the error messages above."
|
|
836
|
-
)
|
|
837
|
-
print(" Run with --verbose for detailed error information.\n")
|
|
610
|
+
print(
|
|
611
|
+
f"\n❌ Failed to deploy {len(agent_result.errors)} agent(s). "
|
|
612
|
+
"Please check the error messages above."
|
|
613
|
+
)
|
|
614
|
+
print(" Run with --verbose for detailed error information.\n")
|
|
838
615
|
|
|
839
616
|
except Exception as e:
|
|
840
617
|
# Deployment failure shouldn't block startup
|
|
@@ -843,6 +620,11 @@ def sync_remote_agents_on_startup():
|
|
|
843
620
|
logger = get_logger("cli")
|
|
844
621
|
logger.warning(f"Failed to deploy agents from cache: {e}")
|
|
845
622
|
|
|
623
|
+
# Phase 4: Cleanup legacy agent cache directories (after sync/deployment)
|
|
624
|
+
# CRITICAL: This must run AFTER sync completes because sync may recreate
|
|
625
|
+
# legacy directories. Running cleanup here ensures they're removed.
|
|
626
|
+
cleanup_legacy_agent_cache()
|
|
627
|
+
|
|
846
628
|
except Exception as e:
|
|
847
629
|
# Non-critical - log but don't fail startup
|
|
848
630
|
from ..core.logger import get_logger
|
|
@@ -851,8 +633,14 @@ def sync_remote_agents_on_startup():
|
|
|
851
633
|
logger.debug(f"Failed to sync remote agents: {e}")
|
|
852
634
|
# Continue execution - agent sync failure shouldn't block startup
|
|
853
635
|
|
|
636
|
+
# Cleanup legacy cache even if sync failed
|
|
637
|
+
try:
|
|
638
|
+
cleanup_legacy_agent_cache()
|
|
639
|
+
except Exception: # nosec B110
|
|
640
|
+
pass # Ignore cleanup errors
|
|
641
|
+
|
|
854
642
|
|
|
855
|
-
def sync_remote_skills_on_startup():
|
|
643
|
+
def sync_remote_skills_on_startup(force_sync: bool = False):
|
|
856
644
|
"""
|
|
857
645
|
Synchronize skill templates from remote sources on startup.
|
|
858
646
|
|
|
@@ -870,6 +658,9 @@ def sync_remote_skills_on_startup():
|
|
|
870
658
|
4. Apply profile filtering if active
|
|
871
659
|
5. Deploy resolved skills to ~/.claude/skills/ - Phase 2 progress bar
|
|
872
660
|
6. Log deployment results with source indication
|
|
661
|
+
|
|
662
|
+
Args:
|
|
663
|
+
force_sync: Force download even if cache is fresh (bypasses ETag).
|
|
873
664
|
"""
|
|
874
665
|
try:
|
|
875
666
|
from pathlib import Path
|
|
@@ -975,7 +766,7 @@ def sync_remote_skills_on_startup():
|
|
|
975
766
|
|
|
976
767
|
# Sync all sources with progress callback
|
|
977
768
|
results = manager.sync_all_sources(
|
|
978
|
-
force=
|
|
769
|
+
force=force_sync, progress_callback=sync_progress.update
|
|
979
770
|
)
|
|
980
771
|
|
|
981
772
|
# Finish sync progress bar with clear breakdown
|
|
@@ -1073,9 +864,7 @@ def sync_remote_skills_on_startup():
|
|
|
1073
864
|
total_skill_count = len(all_skills)
|
|
1074
865
|
|
|
1075
866
|
# Determine skill count based on resolution
|
|
1076
|
-
skill_count = (
|
|
1077
|
-
len(skills_to_deploy) if skills_to_deploy else total_skill_count
|
|
1078
|
-
)
|
|
867
|
+
skill_count = len(skills_to_deploy) if skills_to_deploy else total_skill_count
|
|
1079
868
|
|
|
1080
869
|
if skill_count > 0:
|
|
1081
870
|
# Deploy skills with resolved filter
|
|
@@ -1086,11 +875,13 @@ def sync_remote_skills_on_startup():
|
|
|
1086
875
|
# Deploy to project-local directory with cleanup
|
|
1087
876
|
deployment_result = manager.deploy_skills(
|
|
1088
877
|
target_dir=Path.cwd() / ".claude" / "skills",
|
|
1089
|
-
force=
|
|
878
|
+
force=force_sync,
|
|
1090
879
|
# CRITICAL FIX: Empty list should mean "deploy no skills", not "deploy all"
|
|
1091
880
|
# When skills_to_deploy is [], we want skill_filter=set() NOT skill_filter=None
|
|
1092
881
|
# None means "no filtering" (deploy all), empty set means "filter to nothing"
|
|
1093
|
-
skill_filter=set(skills_to_deploy)
|
|
882
|
+
skill_filter=set(skills_to_deploy)
|
|
883
|
+
if skills_to_deploy is not None
|
|
884
|
+
else None,
|
|
1094
885
|
)
|
|
1095
886
|
|
|
1096
887
|
# REMOVED: User-level deployment (lines 1068-1074)
|
|
@@ -1349,33 +1140,70 @@ def show_skill_summary():
|
|
|
1349
1140
|
|
|
1350
1141
|
|
|
1351
1142
|
def verify_and_show_pm_skills():
|
|
1352
|
-
"""Verify PM skills and display status.
|
|
1143
|
+
"""Verify PM skills and display status with enhanced validation.
|
|
1144
|
+
|
|
1145
|
+
WHY: PM skills are CRITICAL for PM agent operation. PM must KNOW if
|
|
1146
|
+
framework knowledge is unavailable at startup. Enhanced validation
|
|
1147
|
+
checks all required skills exist, are not corrupted, and auto-repairs
|
|
1148
|
+
if needed.
|
|
1353
1149
|
|
|
1354
|
-
|
|
1355
|
-
|
|
1150
|
+
Shows deployment status:
|
|
1151
|
+
- "✓ PM skills: 8/8 verified" if all required skills are valid
|
|
1152
|
+
- "⚠ PM skills: 2 missing, auto-repairing..." if issues detected
|
|
1153
|
+
- Non-blocking but visible warning if auto-repair fails
|
|
1356
1154
|
"""
|
|
1357
1155
|
try:
|
|
1358
1156
|
from pathlib import Path
|
|
1359
1157
|
|
|
1360
|
-
from ..services.pm_skills_deployer import
|
|
1158
|
+
from ..services.pm_skills_deployer import (
|
|
1159
|
+
REQUIRED_PM_SKILLS,
|
|
1160
|
+
PMSkillsDeployerService,
|
|
1161
|
+
)
|
|
1361
1162
|
|
|
1362
1163
|
deployer = PMSkillsDeployerService()
|
|
1363
1164
|
project_dir = Path.cwd()
|
|
1364
1165
|
|
|
1365
|
-
|
|
1166
|
+
# Verify with auto-repair enabled
|
|
1167
|
+
result = deployer.verify_pm_skills(project_dir, auto_repair=True)
|
|
1366
1168
|
|
|
1367
1169
|
if result.verified:
|
|
1368
|
-
# Show verified status
|
|
1369
|
-
|
|
1170
|
+
# Show verified status with count
|
|
1171
|
+
total_required = len(REQUIRED_PM_SKILLS)
|
|
1172
|
+
print(
|
|
1173
|
+
f"✓ PM skills: {total_required}/{total_required} verified", flush=True
|
|
1174
|
+
)
|
|
1370
1175
|
else:
|
|
1371
|
-
#
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1176
|
+
# Show warning with details
|
|
1177
|
+
missing_count = len(result.missing_skills)
|
|
1178
|
+
corrupted_count = len(result.corrupted_skills)
|
|
1179
|
+
|
|
1180
|
+
# Build status message
|
|
1181
|
+
issues = []
|
|
1182
|
+
if missing_count > 0:
|
|
1183
|
+
issues.append(f"{missing_count} missing")
|
|
1184
|
+
if corrupted_count > 0:
|
|
1185
|
+
issues.append(f"{corrupted_count} corrupted")
|
|
1186
|
+
|
|
1187
|
+
status = ", ".join(issues)
|
|
1188
|
+
|
|
1189
|
+
# Check if auto-repair was attempted
|
|
1190
|
+
if "Auto-repaired" in result.message:
|
|
1191
|
+
# Auto-repair succeeded
|
|
1192
|
+
total_required = len(REQUIRED_PM_SKILLS)
|
|
1193
|
+
print(
|
|
1194
|
+
f"✓ PM skills: {total_required}/{total_required} verified (auto-repaired)",
|
|
1195
|
+
flush=True,
|
|
1196
|
+
)
|
|
1377
1197
|
else:
|
|
1378
|
-
|
|
1198
|
+
# Auto-repair failed or not attempted
|
|
1199
|
+
print(f"⚠ PM skills: {status}", flush=True)
|
|
1200
|
+
|
|
1201
|
+
# Log warnings for debugging
|
|
1202
|
+
from ..core.logger import get_logger
|
|
1203
|
+
|
|
1204
|
+
logger = get_logger("cli")
|
|
1205
|
+
for warning in result.warnings:
|
|
1206
|
+
logger.warning(f"PM skills: {warning}")
|
|
1379
1207
|
|
|
1380
1208
|
except ImportError:
|
|
1381
1209
|
# PM skills deployer not available - skip silently
|
|
@@ -1410,7 +1238,7 @@ def auto_install_chrome_devtools_on_startup():
|
|
|
1410
1238
|
if not chrome_devtools_config.get("auto_install", True):
|
|
1411
1239
|
# Auto-install disabled, skip silently
|
|
1412
1240
|
return
|
|
1413
|
-
except Exception:
|
|
1241
|
+
except Exception: # nosec B110
|
|
1414
1242
|
# If config loading fails, assume auto-install is enabled (default)
|
|
1415
1243
|
pass
|
|
1416
1244
|
|
|
@@ -1428,7 +1256,7 @@ def auto_install_chrome_devtools_on_startup():
|
|
|
1428
1256
|
# Continue execution - chrome-devtools installation failure shouldn't block startup
|
|
1429
1257
|
|
|
1430
1258
|
|
|
1431
|
-
def run_background_services():
|
|
1259
|
+
def run_background_services(force_sync: bool = False):
|
|
1432
1260
|
"""
|
|
1433
1261
|
Initialize all background services on startup.
|
|
1434
1262
|
|
|
@@ -1439,6 +1267,9 @@ def run_background_services():
|
|
|
1439
1267
|
explicitly requests them via agent-manager commands. This prevents unwanted
|
|
1440
1268
|
file creation in project .claude/ directories.
|
|
1441
1269
|
See: SystemInstructionsDeployer and agent_deployment.py line 504-509
|
|
1270
|
+
|
|
1271
|
+
Args:
|
|
1272
|
+
force_sync: Force download even if cache is fresh (bypasses ETag).
|
|
1442
1273
|
"""
|
|
1443
1274
|
# Sync hooks early to ensure up-to-date configuration
|
|
1444
1275
|
# RATIONALE: Hooks should be synced before other services to fix stale configs
|
|
@@ -1449,7 +1280,9 @@ def run_background_services():
|
|
|
1449
1280
|
check_mcp_auto_configuration()
|
|
1450
1281
|
verify_mcp_gateway_startup()
|
|
1451
1282
|
check_for_updates_async()
|
|
1452
|
-
sync_remote_agents_on_startup(
|
|
1283
|
+
sync_remote_agents_on_startup(
|
|
1284
|
+
force_sync=force_sync
|
|
1285
|
+
) # Sync agents from remote sources
|
|
1453
1286
|
show_agent_summary() # Display agent counts after deployment
|
|
1454
1287
|
|
|
1455
1288
|
# Skills deployment order (precedence: remote > bundled)
|
|
@@ -1458,7 +1291,9 @@ def run_background_services():
|
|
|
1458
1291
|
# 3. Discover and link runtime skills (user-added skills)
|
|
1459
1292
|
# This ensures remote skills take precedence over bundled skills when names conflict
|
|
1460
1293
|
deploy_bundled_skills() # Base layer: package-bundled skills
|
|
1461
|
-
sync_remote_skills_on_startup(
|
|
1294
|
+
sync_remote_skills_on_startup(
|
|
1295
|
+
force_sync=force_sync
|
|
1296
|
+
) # Override layer: Git-based skills (takes precedence)
|
|
1462
1297
|
discover_and_link_runtime_skills() # Discovery: user-added skills
|
|
1463
1298
|
show_skill_summary() # Display skill counts after deployment
|
|
1464
1299
|
verify_and_show_pm_skills() # PM skills verification and status
|
|
@@ -1658,7 +1493,7 @@ def verify_mcp_gateway_startup():
|
|
|
1658
1493
|
loop.run_until_complete(
|
|
1659
1494
|
asyncio.gather(*pending, return_exceptions=True)
|
|
1660
1495
|
)
|
|
1661
|
-
except Exception:
|
|
1496
|
+
except Exception: # nosec B110
|
|
1662
1497
|
pass # Ignore cleanup errors
|
|
1663
1498
|
finally:
|
|
1664
1499
|
loop.close()
|
|
@@ -1752,7 +1587,7 @@ def check_for_updates_async():
|
|
|
1752
1587
|
|
|
1753
1588
|
logger = get_logger("upgrade_check")
|
|
1754
1589
|
logger.debug(f"Update check failed (non-critical): {e}")
|
|
1755
|
-
except Exception:
|
|
1590
|
+
except Exception: # nosec B110
|
|
1756
1591
|
pass # Avoid any errors in error handling
|
|
1757
1592
|
finally:
|
|
1758
1593
|
# Properly clean up event loop
|
|
@@ -1767,7 +1602,7 @@ def check_for_updates_async():
|
|
|
1767
1602
|
loop.run_until_complete(
|
|
1768
1603
|
asyncio.gather(*pending, return_exceptions=True)
|
|
1769
1604
|
)
|
|
1770
|
-
except Exception:
|
|
1605
|
+
except Exception: # nosec B110
|
|
1771
1606
|
pass # Ignore cleanup errors
|
|
1772
1607
|
finally:
|
|
1773
1608
|
loop.close()
|