claude-mpm 5.1.9__py3-none-any.whl → 5.4.14__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/__init__.py +4 -0
- claude_mpm/agents/PM_INSTRUCTIONS.md +85 -0
- claude_mpm/agents/agent_loader.py +13 -44
- claude_mpm/agents/templates/circuit-breakers.md +138 -1
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/commands/agent_state_manager.py +8 -17
- claude_mpm/cli/commands/auto_configure.py +210 -25
- claude_mpm/cli/commands/config.py +88 -2
- claude_mpm/cli/commands/configure.py +1097 -158
- claude_mpm/cli/commands/configure_agent_display.py +15 -6
- claude_mpm/cli/commands/mpm_init/core.py +160 -46
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
- claude_mpm/cli/commands/skills.py +21 -2
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +11 -3
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/parsers/config_parser.py +153 -83
- claude_mpm/cli/parsers/skills_parser.py +3 -2
- claude_mpm/cli/startup.py +333 -89
- claude_mpm/commands/mpm-config.md +266 -0
- claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/core/framework/formatters/content_formatter.py +3 -13
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/framework_loader.py +4 -2
- claude_mpm/core/logger.py +13 -0
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/unified_agent_registry.py +5 -15
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +206 -78
- claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
- claude_mpm/hooks/claude_hooks/installer.py +33 -10
- claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
- claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
- claude_mpm/hooks/memory_integration_hook.py +46 -1
- claude_mpm/init.py +0 -19
- claude_mpm/scripts/claude-hook-handler.sh +58 -18
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/services/agents/agent_recommendation_service.py +278 -0
- claude_mpm/services/agents/agent_review_service.py +280 -0
- claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
- claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +335 -53
- claude_mpm/services/agents/git_source_manager.py +34 -0
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
- claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
- claude_mpm/services/agents/toolchain_detector.py +10 -6
- claude_mpm/services/analysis/__init__.py +11 -1
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/command_deployment_service.py +71 -10
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/git/git_operations_service.py +93 -8
- claude_mpm/services/monitor/daemon.py +9 -2
- claude_mpm/services/monitor/daemon_manager.py +39 -3
- claude_mpm/services/monitor/server.py +225 -19
- claude_mpm/services/self_upgrade_service.py +120 -12
- claude_mpm/services/skills/__init__.py +3 -0
- claude_mpm/services/skills/git_skill_source_manager.py +32 -2
- claude_mpm/services/skills/selective_skill_deployer.py +230 -0
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_deployer.py +64 -3
- claude_mpm/services/socketio/event_normalizer.py +15 -1
- claude_mpm/services/socketio/server/core.py +160 -21
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/utils/agent_filters.py +17 -44
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/METADATA +47 -84
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/RECORD +76 -150
- claude_mpm-5.4.14.dist-info/entry_points.txt +5 -0
- claude_mpm-5.4.14.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.14.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
- claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
- claude_mpm/agents/BASE_ENGINEER.md +0 -658
- claude_mpm/agents/BASE_OPS.md +0 -219
- claude_mpm/agents/BASE_PM.md +0 -480
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
- claude_mpm/agents/BASE_QA.md +0 -167
- claude_mpm/agents/BASE_RESEARCH.md +0 -53
- claude_mpm/agents/base_agent.json +0 -31
- claude_mpm/agents/base_agent_loader.py +0 -601
- claude_mpm/cli/ticket_cli.py +0 -35
- claude_mpm/commands/mpm-config-view.md +0 -150
- claude_mpm/dashboard/analysis_runner.py +0 -455
- claude_mpm/dashboard/index.html +0 -13
- claude_mpm/dashboard/open_dashboard.py +0 -66
- claude_mpm/dashboard/static/css/activity.css +0 -1958
- claude_mpm/dashboard/static/css/connection-status.css +0 -370
- claude_mpm/dashboard/static/css/dashboard.css +0 -4701
- claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
- claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
- claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
- claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
- claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
- claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
- claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
- claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
- claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
- claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
- claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
- claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
- claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
- claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
- claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
- claude_mpm/dashboard/static/js/connection-manager.js +0 -536
- claude_mpm/dashboard/static/js/dashboard.js +0 -1914
- claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
- claude_mpm/dashboard/static/js/socket-client.js +0 -1474
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
- claude_mpm/dashboard/static/socket.io.min.js +0 -7
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
- claude_mpm/dashboard/templates/code_simple.html +0 -153
- claude_mpm/dashboard/templates/index.html +0 -606
- claude_mpm/dashboard/test_dashboard.html +0 -372
- claude_mpm/scripts/mcp_server.py +0 -75
- claude_mpm/scripts/mcp_wrapper.py +0 -39
- claude_mpm/services/mcp_gateway/__init__.py +0 -159
- claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
- claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
- claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
- claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
- claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
- claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
- claude_mpm/services/mcp_gateway/core/base.py +0 -312
- claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
- claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
- claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
- claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
- claude_mpm/services/mcp_gateway/main.py +0 -589
- claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
- claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
- claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
- claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
- claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
- claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
- claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
- claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
- claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/WHEEL +0 -0
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.14.dist-info}/top_level.txt +0 -0
claude_mpm/cli/startup.py
CHANGED
|
@@ -14,6 +14,52 @@ import warnings
|
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
def sync_hooks_on_startup(quiet: bool = False) -> bool:
|
|
18
|
+
"""Ensure hooks are up-to-date on startup.
|
|
19
|
+
|
|
20
|
+
WHY: Users can have stale hook configurations in settings.json that cause errors.
|
|
21
|
+
Reinstalling hooks ensures the hook format matches the current code.
|
|
22
|
+
|
|
23
|
+
DESIGN DECISION: Shows brief status message on success for user awareness.
|
|
24
|
+
Failures are logged but don't prevent startup to ensure claude-mpm remains functional.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
quiet: If True, suppress all output (used internally)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
bool: True if hooks were synced successfully, False otherwise
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
from ..hooks.claude_hooks.installer import HookInstaller
|
|
34
|
+
|
|
35
|
+
installer = HookInstaller()
|
|
36
|
+
|
|
37
|
+
# Show brief status (hooks sync is fast)
|
|
38
|
+
if not quiet:
|
|
39
|
+
print("Syncing Claude Code hooks...", end=" ", flush=True)
|
|
40
|
+
|
|
41
|
+
# Reinstall hooks (force=True ensures update)
|
|
42
|
+
success = installer.install_hooks(force=True)
|
|
43
|
+
|
|
44
|
+
if not quiet:
|
|
45
|
+
if success:
|
|
46
|
+
print("✓")
|
|
47
|
+
else:
|
|
48
|
+
print("(skipped)")
|
|
49
|
+
|
|
50
|
+
return success
|
|
51
|
+
|
|
52
|
+
except Exception as e:
|
|
53
|
+
if not quiet:
|
|
54
|
+
print("(error)")
|
|
55
|
+
# Log but don't fail startup
|
|
56
|
+
from ..core.logger import get_logger
|
|
57
|
+
|
|
58
|
+
logger = get_logger("startup")
|
|
59
|
+
logger.warning(f"Hook sync failed (non-fatal): {e}")
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
|
|
17
63
|
def check_legacy_cache() -> None:
|
|
18
64
|
"""Check for legacy cache/agents/ directory and warn user.
|
|
19
65
|
|
|
@@ -221,78 +267,157 @@ def discover_and_link_runtime_skills():
|
|
|
221
267
|
|
|
222
268
|
def deploy_output_style_on_startup():
|
|
223
269
|
"""
|
|
224
|
-
Deploy claude-mpm output
|
|
270
|
+
Deploy claude-mpm output styles to PROJECT-LEVEL directory on CLI startup.
|
|
225
271
|
|
|
226
|
-
WHY: Automatically deploy
|
|
227
|
-
|
|
228
|
-
|
|
272
|
+
WHY: Automatically deploy output styles to ensure consistent, professional
|
|
273
|
+
communication without emojis and exclamation points. Styles are project-specific
|
|
274
|
+
to allow different projects to have different communication styles.
|
|
229
275
|
|
|
230
|
-
DESIGN DECISION: This is non-blocking and idempotent.
|
|
231
|
-
|
|
232
|
-
|
|
276
|
+
DESIGN DECISION: This is non-blocking and idempotent. Deploys to project-level
|
|
277
|
+
directory (.claude/settings/output-styles/) instead of user-level to maintain
|
|
278
|
+
project isolation.
|
|
279
|
+
|
|
280
|
+
Deploys two styles:
|
|
281
|
+
- claude-mpm-style.md (professional mode)
|
|
282
|
+
- claude-mpm-teacher.md (teaching mode)
|
|
233
283
|
"""
|
|
234
284
|
try:
|
|
285
|
+
import shutil
|
|
235
286
|
from pathlib import Path
|
|
236
287
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
#
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
# File is empty, need to redeploy with content
|
|
259
|
-
pass # Fall through to deployment below
|
|
260
|
-
else:
|
|
261
|
-
# File has content, check if already active
|
|
262
|
-
settings = json.loads(settings_file.read_text())
|
|
263
|
-
if settings.get("activeOutputStyle") == "claude-mpm":
|
|
264
|
-
# Already deployed and active with content
|
|
265
|
-
already_configured = True
|
|
266
|
-
except Exception:
|
|
267
|
-
pass # Continue with deployment if we can't read settings
|
|
288
|
+
# Source files (in framework package)
|
|
289
|
+
package_dir = Path(__file__).parent.parent / "agents"
|
|
290
|
+
professional_source = package_dir / "CLAUDE_MPM_OUTPUT_STYLE.md"
|
|
291
|
+
teacher_source = package_dir / "CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md"
|
|
292
|
+
|
|
293
|
+
# Target directory (PROJECT-LEVEL, not user-level)
|
|
294
|
+
project_dir = Path.cwd()
|
|
295
|
+
output_styles_dir = project_dir / ".claude" / "settings" / "output-styles"
|
|
296
|
+
professional_target = output_styles_dir / "claude-mpm-style.md"
|
|
297
|
+
teacher_target = output_styles_dir / "claude-mpm-teacher.md"
|
|
298
|
+
|
|
299
|
+
# Create directory if it doesn't exist
|
|
300
|
+
output_styles_dir.mkdir(parents=True, exist_ok=True)
|
|
301
|
+
|
|
302
|
+
# Check if already deployed (both files exist and have content)
|
|
303
|
+
already_deployed = (
|
|
304
|
+
professional_target.exists()
|
|
305
|
+
and teacher_target.exists()
|
|
306
|
+
and professional_target.stat().st_size > 0
|
|
307
|
+
and teacher_target.stat().st_size > 0
|
|
308
|
+
)
|
|
268
309
|
|
|
269
|
-
if
|
|
270
|
-
# Show feedback that output
|
|
271
|
-
print("✓ Output
|
|
310
|
+
if already_deployed:
|
|
311
|
+
# Show feedback that output styles are ready
|
|
312
|
+
print("✓ Output styles ready", flush=True)
|
|
272
313
|
return
|
|
273
314
|
|
|
274
|
-
#
|
|
275
|
-
|
|
315
|
+
# Deploy both styles
|
|
316
|
+
deployed_count = 0
|
|
317
|
+
if professional_source.exists():
|
|
318
|
+
shutil.copy2(professional_source, professional_target)
|
|
319
|
+
deployed_count += 1
|
|
276
320
|
|
|
277
|
-
if
|
|
278
|
-
|
|
279
|
-
|
|
321
|
+
if teacher_source.exists():
|
|
322
|
+
shutil.copy2(teacher_source, teacher_target)
|
|
323
|
+
deployed_count += 1
|
|
280
324
|
|
|
281
|
-
|
|
325
|
+
if deployed_count > 0:
|
|
326
|
+
print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
|
|
327
|
+
else:
|
|
328
|
+
# Source files missing - log but don't fail
|
|
329
|
+
from ..core.logger import get_logger
|
|
282
330
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
print("✓ Output style configured", flush=True)
|
|
331
|
+
logger = get_logger("cli")
|
|
332
|
+
logger.debug("Output style source files not found")
|
|
286
333
|
|
|
287
334
|
except Exception as e:
|
|
288
335
|
# Non-critical - log but don't fail startup
|
|
289
336
|
from ..core.logger import get_logger
|
|
290
337
|
|
|
291
338
|
logger = get_logger("cli")
|
|
292
|
-
logger.debug(f"Failed to deploy output
|
|
339
|
+
logger.debug(f"Failed to deploy output styles: {e}")
|
|
293
340
|
# Continue execution - output style deployment shouldn't block startup
|
|
294
341
|
|
|
295
342
|
|
|
343
|
+
def _cleanup_orphaned_agents(deploy_target: Path, deployed_agents: list[str]) -> int:
|
|
344
|
+
"""Remove agents that are managed by claude-mpm but no longer deployed.
|
|
345
|
+
|
|
346
|
+
WHY: When agent configurations change, old agents should be removed to avoid
|
|
347
|
+
confusion and stale agent references. Only removes claude-mpm managed agents,
|
|
348
|
+
leaving user-created agents untouched.
|
|
349
|
+
|
|
350
|
+
SAFETY: Only removes files with claude-mpm ownership markers in frontmatter.
|
|
351
|
+
Files without frontmatter or without ownership indicators are preserved.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
deploy_target: Path to .claude/agents/ directory
|
|
355
|
+
deployed_agents: List of agent filenames that should remain
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
Number of agents removed
|
|
359
|
+
"""
|
|
360
|
+
import re
|
|
361
|
+
|
|
362
|
+
import yaml
|
|
363
|
+
|
|
364
|
+
from ..core.logger import get_logger
|
|
365
|
+
|
|
366
|
+
logger = get_logger("cli")
|
|
367
|
+
removed_count = 0
|
|
368
|
+
deployed_set = set(deployed_agents)
|
|
369
|
+
|
|
370
|
+
if not deploy_target.exists():
|
|
371
|
+
return 0
|
|
372
|
+
|
|
373
|
+
# Scan all .md files in agents directory
|
|
374
|
+
for agent_file in deploy_target.glob("*.md"):
|
|
375
|
+
# Skip hidden files
|
|
376
|
+
if agent_file.name.startswith("."):
|
|
377
|
+
continue
|
|
378
|
+
|
|
379
|
+
# Skip if this agent should remain deployed
|
|
380
|
+
if agent_file.name in deployed_set:
|
|
381
|
+
continue
|
|
382
|
+
|
|
383
|
+
# Check if this is a claude-mpm managed agent
|
|
384
|
+
try:
|
|
385
|
+
content = agent_file.read_text(encoding="utf-8")
|
|
386
|
+
|
|
387
|
+
# Parse YAML frontmatter
|
|
388
|
+
if content.startswith("---"):
|
|
389
|
+
match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
|
|
390
|
+
if match:
|
|
391
|
+
frontmatter = yaml.safe_load(match.group(1))
|
|
392
|
+
|
|
393
|
+
# Check ownership indicators
|
|
394
|
+
is_ours = False
|
|
395
|
+
if frontmatter:
|
|
396
|
+
author = frontmatter.get("author", "")
|
|
397
|
+
source = frontmatter.get("source", "")
|
|
398
|
+
agent_id = frontmatter.get("agent_id", "")
|
|
399
|
+
|
|
400
|
+
# It's ours if it has any of these markers
|
|
401
|
+
if (
|
|
402
|
+
"Claude MPM" in str(author)
|
|
403
|
+
or source == "remote"
|
|
404
|
+
or agent_id
|
|
405
|
+
):
|
|
406
|
+
is_ours = True
|
|
407
|
+
|
|
408
|
+
if is_ours:
|
|
409
|
+
# Safe to remove - it's our agent but not deployed
|
|
410
|
+
agent_file.unlink()
|
|
411
|
+
removed_count += 1
|
|
412
|
+
logger.info(f"Removed orphaned agent: {agent_file.name}")
|
|
413
|
+
|
|
414
|
+
except Exception as e:
|
|
415
|
+
logger.debug(f"Could not check agent {agent_file.name}: {e}")
|
|
416
|
+
# Don't remove if we can't verify ownership
|
|
417
|
+
|
|
418
|
+
return removed_count
|
|
419
|
+
|
|
420
|
+
|
|
296
421
|
def sync_remote_agents_on_startup():
|
|
297
422
|
"""
|
|
298
423
|
Synchronize agent templates from remote sources on startup.
|
|
@@ -308,7 +433,8 @@ def sync_remote_agents_on_startup():
|
|
|
308
433
|
Workflow:
|
|
309
434
|
1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
|
|
310
435
|
2. Deploy agents to ~/.claude/agents/ - Phase 2 progress bar
|
|
311
|
-
3.
|
|
436
|
+
3. Cleanup orphaned agents (ours but no longer deployed) - Phase 3
|
|
437
|
+
4. Log deployment results
|
|
312
438
|
"""
|
|
313
439
|
# Check for legacy cache and warn user if found
|
|
314
440
|
check_legacy_cache()
|
|
@@ -386,6 +512,7 @@ def sync_remote_agents_on_startup():
|
|
|
386
512
|
# 1. Must have "/agents/" in path (from git repos)
|
|
387
513
|
# 2. Must not be in PM templates or doc files
|
|
388
514
|
# 3. Exclude BASE-AGENT.md which is not a deployable agent
|
|
515
|
+
# 4. Exclude build artifacts (dist/, build/, .cache/) to prevent double-counting
|
|
389
516
|
agent_files = [
|
|
390
517
|
f
|
|
391
518
|
for f in all_md_files
|
|
@@ -396,44 +523,98 @@ def sync_remote_agents_on_startup():
|
|
|
396
523
|
and f.name.lower() not in pm_templates
|
|
397
524
|
and f.name.lower() not in doc_files
|
|
398
525
|
and f.name.lower() != "base-agent.md"
|
|
526
|
+
# Exclude build artifacts (prevents double-counting source + built files)
|
|
527
|
+
and not any(
|
|
528
|
+
part in str(f).split("/")
|
|
529
|
+
for part in ["dist", "build", ".cache"]
|
|
530
|
+
)
|
|
399
531
|
)
|
|
400
532
|
]
|
|
401
533
|
agent_count = len(agent_files)
|
|
402
534
|
|
|
403
535
|
if agent_count > 0:
|
|
404
|
-
#
|
|
405
|
-
|
|
406
|
-
total=agent_count,
|
|
407
|
-
prefix="Deploying agents",
|
|
408
|
-
show_percentage=True,
|
|
409
|
-
show_counter=True,
|
|
410
|
-
)
|
|
411
|
-
|
|
412
|
-
# Deploy agents with progress callback
|
|
413
|
-
deploy_target = Path.home() / ".claude" / "agents"
|
|
536
|
+
# Deploy agents to project-level directory where Claude Code expects them
|
|
537
|
+
deploy_target = Path.cwd() / ".claude" / "agents"
|
|
414
538
|
deployment_result = deployment_service.deploy_agents(
|
|
415
539
|
target_dir=deploy_target,
|
|
416
540
|
force_rebuild=False, # Only deploy if versions differ
|
|
417
541
|
deployment_mode="update", # Version-aware updates
|
|
418
542
|
)
|
|
419
543
|
|
|
420
|
-
#
|
|
421
|
-
deploy_progress.update(agent_count)
|
|
422
|
-
|
|
423
|
-
# Finish deployment progress bar
|
|
544
|
+
# Get actual counts from deployment result (reflects configured agents)
|
|
424
545
|
deployed = len(deployment_result.get("deployed", []))
|
|
425
546
|
updated = len(deployment_result.get("updated", []))
|
|
426
547
|
skipped = len(deployment_result.get("skipped", []))
|
|
427
|
-
|
|
548
|
+
total_configured = deployed + updated + skipped
|
|
549
|
+
|
|
550
|
+
# FALLBACK: If deployment result doesn't track skipped agents (async path),
|
|
551
|
+
# count existing agents in target directory as "already deployed"
|
|
552
|
+
# This ensures accurate reporting when agents are already up-to-date
|
|
553
|
+
if total_configured == 0 and deploy_target.exists():
|
|
554
|
+
existing_agents = list(deploy_target.glob("*.md"))
|
|
555
|
+
# Filter out non-agent files (e.g., README.md, INSTRUCTIONS.md)
|
|
556
|
+
agent_count_in_target = len(
|
|
557
|
+
[
|
|
558
|
+
f
|
|
559
|
+
for f in existing_agents
|
|
560
|
+
if not f.name.startswith(("README", "INSTRUCTIONS"))
|
|
561
|
+
]
|
|
562
|
+
)
|
|
563
|
+
if agent_count_in_target > 0:
|
|
564
|
+
# All agents already deployed - count them as skipped
|
|
565
|
+
skipped = agent_count_in_target
|
|
566
|
+
total_configured = agent_count_in_target
|
|
567
|
+
|
|
568
|
+
# Create progress bar with actual configured agent count (not raw file count)
|
|
569
|
+
deploy_progress = ProgressBar(
|
|
570
|
+
total=total_configured if total_configured > 0 else 1,
|
|
571
|
+
prefix="Deploying agents",
|
|
572
|
+
show_percentage=True,
|
|
573
|
+
show_counter=True,
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
# Update progress bar to completion
|
|
577
|
+
deploy_progress.update(
|
|
578
|
+
total_configured if total_configured > 0 else 1
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
# Cleanup orphaned agents (ours but no longer deployed)
|
|
582
|
+
# Get list of deployed agent filenames (what should remain)
|
|
583
|
+
deployed_filenames = []
|
|
584
|
+
for agent_name in deployment_result.get("deployed", []):
|
|
585
|
+
deployed_filenames.append(f"{agent_name}.md")
|
|
586
|
+
for agent_name in deployment_result.get("updated", []):
|
|
587
|
+
deployed_filenames.append(f"{agent_name}.md")
|
|
588
|
+
for agent_name in deployment_result.get("skipped", []):
|
|
589
|
+
deployed_filenames.append(f"{agent_name}.md")
|
|
590
|
+
|
|
591
|
+
# Run cleanup and get count of removed agents
|
|
592
|
+
removed = _cleanup_orphaned_agents(
|
|
593
|
+
deploy_target, deployed_filenames
|
|
594
|
+
)
|
|
428
595
|
|
|
429
|
-
# Show total
|
|
596
|
+
# Show total configured agents (deployed + updated + already existing)
|
|
597
|
+
# Include repo count for context and removed count if any
|
|
430
598
|
if deployed > 0 or updated > 0:
|
|
599
|
+
if removed > 0:
|
|
600
|
+
deploy_progress.finish(
|
|
601
|
+
f"Complete: {deployed} new, {updated} updated, {skipped} unchanged, "
|
|
602
|
+
f"{removed} removed ({total_configured} configured from {agent_count} in repo)"
|
|
603
|
+
)
|
|
604
|
+
else:
|
|
605
|
+
deploy_progress.finish(
|
|
606
|
+
f"Complete: {deployed} new, {updated} updated, {skipped} unchanged "
|
|
607
|
+
f"({total_configured} configured from {agent_count} in repo)"
|
|
608
|
+
)
|
|
609
|
+
elif removed > 0:
|
|
431
610
|
deploy_progress.finish(
|
|
432
|
-
f"Complete: {
|
|
611
|
+
f"Complete: {total_configured} agents ready - all unchanged, "
|
|
612
|
+
f"{removed} removed ({agent_count} available in repo)"
|
|
433
613
|
)
|
|
434
614
|
else:
|
|
435
615
|
deploy_progress.finish(
|
|
436
|
-
f"Complete: {
|
|
616
|
+
f"Complete: {total_configured} agents ready - all unchanged "
|
|
617
|
+
f"({agent_count} available in repo)"
|
|
437
618
|
)
|
|
438
619
|
|
|
439
620
|
# Display deployment errors to user (not just logs)
|
|
@@ -523,6 +704,8 @@ def sync_remote_skills_on_startup():
|
|
|
523
704
|
|
|
524
705
|
# Discover total file count across all sources
|
|
525
706
|
total_file_count = 0
|
|
707
|
+
total_skill_dirs = 0 # Count actual skill directories (folders with SKILL.md)
|
|
708
|
+
|
|
526
709
|
for source in enabled_sources:
|
|
527
710
|
try:
|
|
528
711
|
# Parse GitHub URL
|
|
@@ -546,15 +729,26 @@ def sync_remote_skills_on_startup():
|
|
|
546
729
|
]
|
|
547
730
|
total_file_count += len(relevant_files)
|
|
548
731
|
|
|
732
|
+
# Count skill directories (unique directories containing SKILL.md)
|
|
733
|
+
skill_dirs = set()
|
|
734
|
+
for f in all_files:
|
|
735
|
+
if f.endswith("/SKILL.md"):
|
|
736
|
+
# Extract directory path
|
|
737
|
+
skill_dir = "/".join(f.split("/")[:-1])
|
|
738
|
+
skill_dirs.add(skill_dir)
|
|
739
|
+
total_skill_dirs += len(skill_dirs)
|
|
740
|
+
|
|
549
741
|
except Exception as e:
|
|
550
742
|
logger.debug(f"Failed to discover files for {source.id}: {e}")
|
|
551
743
|
# Use estimate if discovery fails
|
|
552
744
|
total_file_count += 150
|
|
745
|
+
total_skill_dirs += 50 # Estimate ~50 skills
|
|
553
746
|
|
|
554
747
|
# Create progress bar for sync phase with actual file count
|
|
748
|
+
# Note: We sync files (md, json, etc.), but will deploy skill directories
|
|
555
749
|
sync_progress = ProgressBar(
|
|
556
750
|
total=total_file_count if total_file_count > 0 else 1,
|
|
557
|
-
prefix="Syncing
|
|
751
|
+
prefix="Syncing skill files",
|
|
558
752
|
show_percentage=True,
|
|
559
753
|
show_counter=True,
|
|
560
754
|
)
|
|
@@ -571,51 +765,96 @@ def sync_remote_skills_on_startup():
|
|
|
571
765
|
|
|
572
766
|
if cached > 0:
|
|
573
767
|
sync_progress.finish(
|
|
574
|
-
f"Complete: {downloaded} downloaded, {cached} cached ({total_files}
|
|
768
|
+
f"Complete: {downloaded} downloaded, {cached} cached ({total_files} files, {total_skill_dirs} skills)"
|
|
575
769
|
)
|
|
576
770
|
else:
|
|
577
771
|
# All new downloads (first sync)
|
|
578
|
-
sync_progress.finish(
|
|
772
|
+
sync_progress.finish(
|
|
773
|
+
f"Complete: {downloaded} files downloaded ({total_skill_dirs} skills)"
|
|
774
|
+
)
|
|
579
775
|
|
|
580
776
|
# Phase 2: Deploy skills to ~/.claude/skills/
|
|
581
777
|
# This flattens nested Git structure (e.g., collaboration/parallel-agents/SKILL.md)
|
|
582
778
|
# into flat deployment (e.g., collaboration-dispatching-parallel-agents/SKILL.md)
|
|
583
779
|
if results["synced_count"] > 0:
|
|
584
|
-
# Get
|
|
780
|
+
# Get required skills from deployed agents (selective deployment)
|
|
781
|
+
from ..services.skills.selective_skill_deployer import (
|
|
782
|
+
get_required_skills_from_agents,
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
agents_dir = Path.cwd() / ".claude" / "agents"
|
|
786
|
+
required_skills = get_required_skills_from_agents(agents_dir)
|
|
787
|
+
|
|
788
|
+
# Get all skills to determine counts
|
|
585
789
|
all_skills = manager.get_all_skills()
|
|
586
|
-
|
|
790
|
+
total_skill_count = len(all_skills)
|
|
587
791
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
)
|
|
792
|
+
# Determine skill count based on whether we have agent requirements
|
|
793
|
+
if required_skills:
|
|
794
|
+
# Selective deployment: only skills required by deployed agents
|
|
795
|
+
skill_count = len(required_skills)
|
|
796
|
+
else:
|
|
797
|
+
# No agent requirements found - deploy all skills
|
|
798
|
+
skill_count = total_skill_count
|
|
596
799
|
|
|
597
|
-
|
|
800
|
+
if skill_count > 0:
|
|
801
|
+
# Deploy skills with selective filter (if agent requirements exist)
|
|
598
802
|
# Deploy to project directory (like agents), not user directory
|
|
599
803
|
deployment_result = manager.deploy_skills(
|
|
600
804
|
target_dir=Path.cwd() / ".claude" / "skills",
|
|
601
805
|
force=False,
|
|
602
|
-
|
|
806
|
+
skill_filter=required_skills if required_skills else None,
|
|
603
807
|
)
|
|
604
808
|
|
|
605
|
-
#
|
|
809
|
+
# Get actual counts from deployment result
|
|
606
810
|
deployed = deployment_result.get("deployed_count", 0)
|
|
607
811
|
skipped = deployment_result.get("skipped_count", 0)
|
|
812
|
+
filtered = deployment_result.get("filtered_count", 0)
|
|
608
813
|
total_available = deployed + skipped
|
|
609
814
|
|
|
815
|
+
# Only show progress bar if there are skills to deploy
|
|
816
|
+
if total_available > 0:
|
|
817
|
+
deploy_progress = ProgressBar(
|
|
818
|
+
total=total_available,
|
|
819
|
+
prefix="Deploying skill directories",
|
|
820
|
+
show_percentage=True,
|
|
821
|
+
show_counter=True,
|
|
822
|
+
)
|
|
823
|
+
# Update progress bar to completion
|
|
824
|
+
deploy_progress.update(total_available)
|
|
825
|
+
else:
|
|
826
|
+
# No skills to deploy - create dummy progress for message only
|
|
827
|
+
deploy_progress = ProgressBar(
|
|
828
|
+
total=1,
|
|
829
|
+
prefix="Deploying skill directories",
|
|
830
|
+
show_percentage=False,
|
|
831
|
+
show_counter=False,
|
|
832
|
+
)
|
|
833
|
+
deploy_progress.update(1)
|
|
834
|
+
|
|
610
835
|
# Show total available skills (deployed + already existing)
|
|
611
|
-
#
|
|
836
|
+
# Include filtered count if selective deployment was used
|
|
837
|
+
# Note: total_skill_count is from the repo, total_available is what's deployed/needed
|
|
612
838
|
if deployed > 0:
|
|
839
|
+
if filtered > 0:
|
|
840
|
+
deploy_progress.finish(
|
|
841
|
+
f"Complete: {deployed} new, {skipped} unchanged "
|
|
842
|
+
f"({total_available} required by agents, {filtered} available in repo)"
|
|
843
|
+
)
|
|
844
|
+
else:
|
|
845
|
+
deploy_progress.finish(
|
|
846
|
+
f"Complete: {deployed} new, {skipped} unchanged "
|
|
847
|
+
f"({total_available} skills deployed from {total_skill_count} in repo)"
|
|
848
|
+
)
|
|
849
|
+
elif filtered > 0:
|
|
850
|
+
# Skills filtered means agents require fewer skills than available
|
|
613
851
|
deploy_progress.finish(
|
|
614
|
-
f"
|
|
852
|
+
f"Agents require no skills ({total_skill_count} available in repo)"
|
|
615
853
|
)
|
|
616
854
|
else:
|
|
617
855
|
deploy_progress.finish(
|
|
618
|
-
f"Complete: {total_available} skills
|
|
856
|
+
f"Complete: {total_available} skills deployed for agents "
|
|
857
|
+
f"({total_skill_count} available in repo)"
|
|
619
858
|
)
|
|
620
859
|
|
|
621
860
|
# Log deployment errors if any
|
|
@@ -656,6 +895,11 @@ def run_background_services():
|
|
|
656
895
|
file creation in project .claude/ directories.
|
|
657
896
|
See: SystemInstructionsDeployer and agent_deployment.py line 504-509
|
|
658
897
|
"""
|
|
898
|
+
# Sync hooks early to ensure up-to-date configuration
|
|
899
|
+
# RATIONALE: Hooks should be synced before other services to fix stale configs
|
|
900
|
+
# This is fast (<100ms) and non-blocking, so it doesn't delay startup
|
|
901
|
+
sync_hooks_on_startup() # Shows "Syncing Claude Code hooks... ✓"
|
|
902
|
+
|
|
659
903
|
initialize_project_registry()
|
|
660
904
|
check_mcp_auto_configuration()
|
|
661
905
|
verify_mcp_gateway_startup()
|