claude-mpm 4.1.7__py3-none-any.whl → 4.1.10__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/INSTRUCTIONS.md +26 -1
- claude_mpm/agents/OUTPUT_STYLE.md +73 -0
- claude_mpm/agents/agents_metadata.py +57 -0
- claude_mpm/agents/templates/.claude-mpm/memories/README.md +17 -0
- claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +3 -0
- claude_mpm/agents/templates/agent-manager.json +263 -17
- claude_mpm/agents/templates/agent-manager.md +248 -10
- claude_mpm/agents/templates/agentic_coder_optimizer.json +222 -0
- claude_mpm/agents/templates/code_analyzer.json +18 -8
- claude_mpm/agents/templates/engineer.json +1 -1
- claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +39 -0
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/research.json +1 -1
- claude_mpm/cli/__init__.py +4 -0
- claude_mpm/cli/commands/__init__.py +6 -0
- claude_mpm/cli/commands/analyze.py +547 -0
- claude_mpm/cli/commands/analyze_code.py +524 -0
- claude_mpm/cli/commands/configure.py +223 -25
- claude_mpm/cli/commands/configure_tui.py +65 -61
- claude_mpm/cli/commands/debug.py +1387 -0
- claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
- claude_mpm/cli/parsers/analyze_parser.py +135 -0
- claude_mpm/cli/parsers/base_parser.py +29 -0
- claude_mpm/cli/parsers/configure_parser.py +23 -0
- claude_mpm/cli/parsers/debug_parser.py +319 -0
- claude_mpm/config/socketio_config.py +21 -21
- claude_mpm/constants.py +3 -1
- claude_mpm/core/framework_loader.py +148 -6
- claude_mpm/core/log_manager.py +16 -13
- claude_mpm/core/logger.py +1 -1
- claude_mpm/core/unified_agent_registry.py +1 -1
- claude_mpm/dashboard/.claude-mpm/socketio-instances.json +1 -0
- claude_mpm/dashboard/analysis_runner.py +428 -0
- claude_mpm/dashboard/static/built/components/activity-tree.js +2 -0
- claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
- claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
- claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
- claude_mpm/dashboard/static/built/dashboard.js +1 -1
- claude_mpm/dashboard/static/built/socket-client.js +1 -1
- claude_mpm/dashboard/static/css/activity.css +549 -0
- claude_mpm/dashboard/static/css/code-tree.css +846 -0
- claude_mpm/dashboard/static/css/dashboard.css +245 -0
- claude_mpm/dashboard/static/dist/components/activity-tree.js +2 -0
- claude_mpm/dashboard/static/dist/components/code-tree.js +2 -0
- claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/activity-tree.js +1139 -0
- claude_mpm/dashboard/static/js/components/code-tree.js +1357 -0
- claude_mpm/dashboard/static/js/components/code-viewer.js +480 -0
- claude_mpm/dashboard/static/js/components/event-viewer.js +11 -0
- claude_mpm/dashboard/static/js/components/session-manager.js +40 -4
- claude_mpm/dashboard/static/js/components/socket-manager.js +12 -0
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +4 -0
- claude_mpm/dashboard/static/js/components/working-directory.js +17 -1
- claude_mpm/dashboard/static/js/dashboard.js +39 -0
- claude_mpm/dashboard/static/js/socket-client.js +414 -20
- claude_mpm/dashboard/templates/index.html +184 -4
- claude_mpm/hooks/claude_hooks/hook_handler.py +182 -5
- claude_mpm/hooks/claude_hooks/installer.py +728 -0
- claude_mpm/scripts/claude-hook-handler.sh +161 -0
- claude_mpm/scripts/socketio_daemon.py +121 -8
- claude_mpm/services/agents/deployment/agent_config_provider.py +127 -27
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +2 -2
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -2
- claude_mpm/services/agents/memory/memory_format_service.py +1 -5
- claude_mpm/services/cli/agent_cleanup_service.py +1 -2
- claude_mpm/services/cli/agent_dependency_service.py +1 -1
- claude_mpm/services/cli/agent_validation_service.py +3 -4
- claude_mpm/services/cli/dashboard_launcher.py +2 -3
- claude_mpm/services/cli/startup_checker.py +0 -10
- claude_mpm/services/core/cache_manager.py +1 -2
- claude_mpm/services/core/path_resolver.py +1 -4
- claude_mpm/services/core/service_container.py +2 -2
- claude_mpm/services/diagnostics/checks/instructions_check.py +2 -5
- claude_mpm/services/event_bus/direct_relay.py +98 -20
- claude_mpm/services/infrastructure/monitoring/__init__.py +11 -11
- claude_mpm/services/infrastructure/monitoring.py +11 -11
- claude_mpm/services/project/architecture_analyzer.py +1 -1
- claude_mpm/services/project/dependency_analyzer.py +4 -4
- claude_mpm/services/project/language_analyzer.py +3 -3
- claude_mpm/services/project/metrics_collector.py +3 -6
- claude_mpm/services/socketio/handlers/__init__.py +2 -0
- claude_mpm/services/socketio/handlers/code_analysis.py +170 -0
- claude_mpm/services/socketio/handlers/registry.py +2 -0
- claude_mpm/services/socketio/server/connection_manager.py +95 -65
- claude_mpm/services/socketio/server/core.py +125 -17
- claude_mpm/services/socketio/server/main.py +44 -5
- claude_mpm/services/visualization/__init__.py +19 -0
- claude_mpm/services/visualization/mermaid_generator.py +938 -0
- claude_mpm/tools/__main__.py +208 -0
- claude_mpm/tools/code_tree_analyzer.py +778 -0
- claude_mpm/tools/code_tree_builder.py +632 -0
- claude_mpm/tools/code_tree_events.py +318 -0
- claude_mpm/tools/socketio_debug.py +671 -0
- {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/METADATA +1 -1
- {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/RECORD +108 -77
- claude_mpm/agents/schema/agent_schema.json +0 -314
- {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.7.dist-info → claude_mpm-4.1.10.dist-info}/top_level.txt +0 -0
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
<!-- External Dependencies -->
|
|
17
17
|
<script src="https://cdn.socket.io/4.7.5/socket.io.min.js"></script>
|
|
18
|
+
|
|
19
|
+
<!-- D3.js for Activity Tree Visualization -->
|
|
20
|
+
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
18
21
|
|
|
19
22
|
<!-- Syntax Highlighting - Prism.js -->
|
|
20
23
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet">
|
|
@@ -26,6 +29,8 @@
|
|
|
26
29
|
<!-- Stylesheets -->
|
|
27
30
|
<link rel="stylesheet" href="/static/css/dashboard.css">
|
|
28
31
|
<link rel="stylesheet" href="/static/css/connection-status.css">
|
|
32
|
+
<link rel="stylesheet" href="/static/css/activity.css">
|
|
33
|
+
<link rel="stylesheet" href="/static/css/code-tree.css">
|
|
29
34
|
|
|
30
35
|
<!-- Additional styles for file operations -->
|
|
31
36
|
<style>
|
|
@@ -236,10 +241,12 @@
|
|
|
236
241
|
<div class="events-container">
|
|
237
242
|
<!-- Tab Navigation -->
|
|
238
243
|
<div class="tab-nav">
|
|
239
|
-
<button class="tab-button active">📊 Events</button>
|
|
240
|
-
<button class="tab-button">🤖 Agents</button>
|
|
241
|
-
<button class="tab-button">🔧 Tools</button>
|
|
242
|
-
<button class="tab-button">📁 Files</button>
|
|
244
|
+
<button class="tab-button active" data-tab="events">📊 Events</button>
|
|
245
|
+
<button class="tab-button" data-tab="agents">🤖 Agents</button>
|
|
246
|
+
<button class="tab-button" data-tab="tools">🔧 Tools</button>
|
|
247
|
+
<button class="tab-button" data-tab="files">📁 Files</button>
|
|
248
|
+
<button class="tab-button" data-tab="activity">🌳 Activity</button>
|
|
249
|
+
<button class="tab-button" data-tab="code">🧬 Code</button>
|
|
243
250
|
</div>
|
|
244
251
|
|
|
245
252
|
<!-- Events Tab -->
|
|
@@ -316,7 +323,165 @@
|
|
|
316
323
|
</div>
|
|
317
324
|
</div>
|
|
318
325
|
</div>
|
|
326
|
+
|
|
327
|
+
<!-- Activity Tab -->
|
|
328
|
+
<div class="tab-content" id="activity-tab">
|
|
329
|
+
<div class="activity-container">
|
|
330
|
+
<div class="activity-header">
|
|
331
|
+
<div class="activity-controls">
|
|
332
|
+
<button id="expand-all" class="btn-sm">Expand All</button>
|
|
333
|
+
<button id="collapse-all" class="btn-sm">Collapse All</button>
|
|
334
|
+
<button id="reset-zoom" class="btn-sm">Reset Zoom</button>
|
|
335
|
+
<select id="time-range">
|
|
336
|
+
<option value="all">All Time</option>
|
|
337
|
+
<option value="hour">Last Hour</option>
|
|
338
|
+
<option value="30min" selected>Last 30 Minutes</option>
|
|
339
|
+
<option value="10min">Last 10 Minutes</option>
|
|
340
|
+
</select>
|
|
341
|
+
<input type="text" id="activity-search" placeholder="Search nodes...">
|
|
342
|
+
</div>
|
|
343
|
+
<div class="activity-stats">
|
|
344
|
+
<span class="stat-item">
|
|
345
|
+
<span class="stat-label">Nodes:</span>
|
|
346
|
+
<span id="node-count" class="stat-value">0</span>
|
|
347
|
+
</span>
|
|
348
|
+
<span class="stat-item">
|
|
349
|
+
<span class="stat-label">Active:</span>
|
|
350
|
+
<span id="active-count" class="stat-value">0</span>
|
|
351
|
+
</span>
|
|
352
|
+
<span class="stat-item">
|
|
353
|
+
<span class="stat-label">Depth:</span>
|
|
354
|
+
<span id="tree-depth" class="stat-value">0</span>
|
|
355
|
+
</span>
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|
|
358
|
+
<div id="activity-tree-container" class="activity-tree-container">
|
|
359
|
+
<div id="activity-tree"></div>
|
|
360
|
+
<div class="tree-legend">
|
|
361
|
+
<div class="legend-item">
|
|
362
|
+
<span class="legend-icon pm">●</span>
|
|
363
|
+
<span>PM</span>
|
|
364
|
+
</div>
|
|
365
|
+
<div class="legend-item">
|
|
366
|
+
<span class="legend-icon todowrite">■</span>
|
|
367
|
+
<span>TodoWrite</span>
|
|
368
|
+
</div>
|
|
369
|
+
<div class="legend-item">
|
|
370
|
+
<span class="legend-icon agent">●</span>
|
|
371
|
+
<span>Agent</span>
|
|
372
|
+
</div>
|
|
373
|
+
<div class="legend-item">
|
|
374
|
+
<span class="legend-icon tool">●</span>
|
|
375
|
+
<span>Tool</span>
|
|
376
|
+
</div>
|
|
377
|
+
<div class="legend-item">
|
|
378
|
+
<span class="legend-icon file">●</span>
|
|
379
|
+
<span>File/Command</span>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
</div>
|
|
383
|
+
<div class="activity-breadcrumb" id="activity-breadcrumb">
|
|
384
|
+
PM > Click a node to see path
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
</div>
|
|
388
|
+
|
|
389
|
+
<!-- Code Tab -->
|
|
390
|
+
<div class="tab-content" id="code-tab">
|
|
391
|
+
<div class="code-container">
|
|
392
|
+
<!-- Compact header with all controls in one line -->
|
|
393
|
+
<div class="code-header-compact">
|
|
394
|
+
<div class="header-left">
|
|
395
|
+
<input type="text" id="analysis-path" placeholder="Path" value="." class="path-input-compact">
|
|
396
|
+
<button id="analyze-code" class="btn-compact btn-primary">🔍</button>
|
|
397
|
+
<button id="cancel-analysis" class="btn-compact btn-danger" style="display: none;">✕</button>
|
|
398
|
+
<button id="code-expand-all" class="btn-compact" title="Expand All">⊕</button>
|
|
399
|
+
<button id="code-collapse-all" class="btn-compact" title="Collapse All">⊖</button>
|
|
400
|
+
<button id="code-reset-zoom" class="btn-compact" title="Reset Zoom">⟲</button>
|
|
401
|
+
<button id="code-toggle-legend" class="btn-compact" title="Toggle Legend">ℹ️</button>
|
|
402
|
+
</div>
|
|
403
|
+
<div class="header-center">
|
|
404
|
+
<span class="stat-compact">📁 <span id="file-count">0</span></span>
|
|
405
|
+
<span class="stat-compact">🏛️ <span id="class-count">0</span></span>
|
|
406
|
+
<span class="stat-compact">⚡ <span id="function-count">0</span></span>
|
|
407
|
+
<span class="stat-compact">📝 <span id="line-count">0</span></span>
|
|
408
|
+
</div>
|
|
409
|
+
<div class="header-right">
|
|
410
|
+
<select id="language-filter" class="select-compact">
|
|
411
|
+
<option value="all">All</option>
|
|
412
|
+
<option value="python">Python</option>
|
|
413
|
+
<option value="javascript">JS</option>
|
|
414
|
+
<option value="typescript">TS</option>
|
|
415
|
+
</select>
|
|
416
|
+
<input type="text" id="code-search" placeholder="Search..." class="search-compact">
|
|
417
|
+
</div>
|
|
418
|
+
</div>
|
|
419
|
+
<!-- Collapsible advanced options -->
|
|
420
|
+
<details class="code-advanced-options">
|
|
421
|
+
<summary>Advanced Options</summary>
|
|
422
|
+
<div class="advanced-content">
|
|
423
|
+
<div class="option-group">
|
|
424
|
+
<label>Languages:</label>
|
|
425
|
+
<label><input type="checkbox" class="language-checkbox" value="python" checked> Python</label>
|
|
426
|
+
<label><input type="checkbox" class="language-checkbox" value="javascript" checked> JS</label>
|
|
427
|
+
<label><input type="checkbox" class="language-checkbox" value="typescript"> TS</label>
|
|
428
|
+
</div>
|
|
429
|
+
<div class="option-group">
|
|
430
|
+
<label>Depth: <input type="number" id="max-depth" min="1" max="10" value="5" class="input-compact"></label>
|
|
431
|
+
<label>Ignore: <input type="text" id="ignore-patterns" placeholder="test*, *.spec.js" class="input-compact"></label>
|
|
432
|
+
</div>
|
|
433
|
+
</div>
|
|
434
|
+
</details>
|
|
435
|
+
<div id="code-tree-container" class="code-tree-container">
|
|
436
|
+
<div id="code-tree"></div>
|
|
437
|
+
<!-- Collapsible legend -->
|
|
438
|
+
<div class="tree-legend collapsed" id="tree-legend" style="display: none;">
|
|
439
|
+
<button class="legend-close" onclick="document.getElementById('tree-legend').style.display='none'">✕</button>
|
|
440
|
+
<div class="legend-content">
|
|
441
|
+
<div class="legend-column">
|
|
442
|
+
<div class="legend-item"><span class="legend-icon">📦</span> Module</div>
|
|
443
|
+
<div class="legend-item"><span class="legend-icon">🏛️</span> Class</div>
|
|
444
|
+
<div class="legend-item"><span class="legend-icon">⚡</span> Function</div>
|
|
445
|
+
<div class="legend-item"><span class="legend-icon">🔧</span> Method</div>
|
|
446
|
+
</div>
|
|
447
|
+
<div class="legend-column">
|
|
448
|
+
<div class="legend-item"><span class="legend-icon complexity-low">●</span> Low</div>
|
|
449
|
+
<div class="legend-item"><span class="legend-icon complexity-medium">●</span> Med</div>
|
|
450
|
+
<div class="legend-item"><span class="legend-icon complexity-high">●</span> High</div>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
</div>
|
|
454
|
+
</div>
|
|
455
|
+
<div class="code-breadcrumb" id="code-breadcrumb">
|
|
456
|
+
<div class="breadcrumb-ticker" id="breadcrumb-ticker">
|
|
457
|
+
<span id="breadcrumb-content">Ready to analyze...</span>
|
|
458
|
+
</div>
|
|
459
|
+
</div>
|
|
460
|
+
</div>
|
|
461
|
+
</div>
|
|
462
|
+
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
|
|
468
|
+
<!-- Code Viewer Modal -->
|
|
469
|
+
<div id="code-viewer-modal" class="modal" style="display: none;">
|
|
470
|
+
<div class="modal-content">
|
|
471
|
+
<div class="modal-header">
|
|
472
|
+
<h2 id="code-viewer-title">Code Viewer</h2>
|
|
473
|
+
<button class="close" onclick="closeCodeViewer()">×</button>
|
|
474
|
+
</div>
|
|
475
|
+
<div class="modal-body">
|
|
476
|
+
<pre><code id="code-viewer-content"></code></pre>
|
|
477
|
+
</div>
|
|
478
|
+
<div class="modal-footer">
|
|
479
|
+
<div class="code-viewer-info">
|
|
480
|
+
<span id="code-viewer-path"></span>
|
|
481
|
+
<span id="code-viewer-lines"></span>
|
|
482
|
+
<span id="code-viewer-language"></span>
|
|
319
483
|
</div>
|
|
484
|
+
<button onclick="closeCodeViewer()">Close</button>
|
|
320
485
|
</div>
|
|
321
486
|
</div>
|
|
322
487
|
</div>
|
|
@@ -338,11 +503,26 @@
|
|
|
338
503
|
<span class="footer-label">Branch:</span>
|
|
339
504
|
<span class="footer-value" id="footer-git-branch">Unknown</span>
|
|
340
505
|
</div>
|
|
506
|
+
<!-- Analysis Status (hidden by default) -->
|
|
507
|
+
<div class="footer-item" id="footer-analysis-container" style="display: none;">
|
|
508
|
+
<span class="footer-divider">|</span>
|
|
509
|
+
<span class="footer-label">Analysis:</span>
|
|
510
|
+
<span class="footer-value" id="footer-analysis-status">
|
|
511
|
+
<span class="analysis-progress" id="footer-analysis-progress"></span>
|
|
512
|
+
</span>
|
|
513
|
+
</div>
|
|
341
514
|
</div>
|
|
342
515
|
</div>
|
|
343
516
|
|
|
344
517
|
<!-- JavaScript Modules -->
|
|
345
518
|
<!-- Load bundled dashboard assets (built with Vite) -->
|
|
346
519
|
<script type="module" src="/static/dist/dashboard.js"></script>
|
|
520
|
+
|
|
521
|
+
<!-- Activity Tree Module (from Vite build) -->
|
|
522
|
+
<script type="module" src="/static/dist/components/activity-tree.js"></script>
|
|
523
|
+
|
|
524
|
+
<!-- Code Tree Module (from Vite build) -->
|
|
525
|
+
<script type="module" src="/static/dist/components/code-tree.js"></script>
|
|
526
|
+
<script type="module" src="/static/dist/components/code-viewer.js"></script>
|
|
347
527
|
</body>
|
|
348
528
|
</html>
|
|
@@ -12,15 +12,21 @@ WHY service-oriented approach:
|
|
|
12
12
|
- Easier testing and maintenance
|
|
13
13
|
- Reduced file size from 1040 to ~400 lines
|
|
14
14
|
- Clear service boundaries and responsibilities
|
|
15
|
+
|
|
16
|
+
NOTE: Requires Claude Code version 1.0.92 or higher for proper hook support.
|
|
17
|
+
Earlier versions do not support matcher-based hook configuration.
|
|
15
18
|
"""
|
|
16
19
|
|
|
17
20
|
import json
|
|
18
21
|
import os
|
|
22
|
+
import re
|
|
19
23
|
import select
|
|
20
24
|
import signal
|
|
25
|
+
import subprocess
|
|
21
26
|
import sys
|
|
22
27
|
import threading
|
|
23
28
|
from datetime import datetime
|
|
29
|
+
from typing import Optional, Tuple
|
|
24
30
|
|
|
25
31
|
# Import extracted modules with fallback for direct execution
|
|
26
32
|
try:
|
|
@@ -51,10 +57,27 @@ except ImportError:
|
|
|
51
57
|
SubagentResponseProcessor,
|
|
52
58
|
)
|
|
53
59
|
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
"""
|
|
61
|
+
Debug mode configuration for hook processing.
|
|
62
|
+
|
|
63
|
+
WHY enabled by default: Hook processing can be complex and hard to debug.
|
|
64
|
+
Having debug output available by default helps diagnose issues during development.
|
|
65
|
+
Production deployments can disable via environment variable.
|
|
66
|
+
|
|
67
|
+
Performance Impact: Debug logging adds ~5-10% overhead but provides crucial
|
|
68
|
+
visibility into event flow, timing, and error conditions.
|
|
69
|
+
"""
|
|
56
70
|
DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
|
|
57
71
|
|
|
72
|
+
"""
|
|
73
|
+
Conditional imports with graceful fallbacks for testing and modularity.
|
|
74
|
+
|
|
75
|
+
WHY conditional imports:
|
|
76
|
+
- EventBus is optional for basic hook functionality
|
|
77
|
+
- Tests may not have full environment setup
|
|
78
|
+
- Allows hooks to work in minimal configurations
|
|
79
|
+
- Graceful degradation when dependencies unavailable
|
|
80
|
+
"""
|
|
58
81
|
# Import EventBus availability flag for backward compatibility with tests
|
|
59
82
|
try:
|
|
60
83
|
from claude_mpm.services.event_bus import EventBus
|
|
@@ -70,10 +93,125 @@ try:
|
|
|
70
93
|
except ImportError:
|
|
71
94
|
get_connection_pool = None
|
|
72
95
|
|
|
73
|
-
|
|
96
|
+
"""
|
|
97
|
+
Global singleton pattern for hook handler.
|
|
98
|
+
|
|
99
|
+
WHY singleton:
|
|
100
|
+
- Only one handler should process Claude Code events
|
|
101
|
+
- Maintains consistent state across all hook invocations
|
|
102
|
+
- Prevents duplicate event processing
|
|
103
|
+
- Thread-safe initialization with lock
|
|
104
|
+
|
|
105
|
+
GOTCHA: Must use get_global_handler() not direct access to avoid race conditions.
|
|
106
|
+
"""
|
|
74
107
|
_global_handler = None
|
|
75
108
|
_handler_lock = threading.Lock()
|
|
76
109
|
|
|
110
|
+
"""
|
|
111
|
+
Version compatibility checking.
|
|
112
|
+
|
|
113
|
+
WHY version checking:
|
|
114
|
+
- Claude Code hook support was added in v1.0.92
|
|
115
|
+
- Earlier versions don't support matcher-based configuration
|
|
116
|
+
- Prevents confusing errors with unsupported versions
|
|
117
|
+
|
|
118
|
+
Security: Version checking prevents execution on incompatible environments.
|
|
119
|
+
"""
|
|
120
|
+
MIN_CLAUDE_VERSION = "1.0.92"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def check_claude_version() -> Tuple[bool, Optional[str]]:
|
|
124
|
+
"""
|
|
125
|
+
Verify Claude Code version compatibility for hook support.
|
|
126
|
+
|
|
127
|
+
Executes 'claude --version' command to detect installed version and
|
|
128
|
+
compares against minimum required version for hook functionality.
|
|
129
|
+
|
|
130
|
+
Version Checking Logic:
|
|
131
|
+
1. Execute 'claude --version' with timeout
|
|
132
|
+
2. Parse version string using regex
|
|
133
|
+
3. Compare against MIN_CLAUDE_VERSION (1.0.92)
|
|
134
|
+
4. Return compatibility status and detected version
|
|
135
|
+
|
|
136
|
+
WHY this check is critical:
|
|
137
|
+
- Hook support was added in Claude Code v1.0.92
|
|
138
|
+
- Earlier versions don't understand matcher-based hooks
|
|
139
|
+
- Prevents cryptic errors from unsupported configurations
|
|
140
|
+
- Allows graceful fallback or user notification
|
|
141
|
+
|
|
142
|
+
Error Handling:
|
|
143
|
+
- Command timeout after 5 seconds
|
|
144
|
+
- Subprocess errors caught and logged
|
|
145
|
+
- Invalid version formats handled gracefully
|
|
146
|
+
- Returns (False, None) on any failure
|
|
147
|
+
|
|
148
|
+
Performance Notes:
|
|
149
|
+
- Subprocess call has ~100ms overhead
|
|
150
|
+
- Result should be cached by caller
|
|
151
|
+
- Only called during initialization
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Tuple[bool, Optional[str]]:
|
|
155
|
+
- bool: True if version is compatible
|
|
156
|
+
- str|None: Detected version string, None if detection failed
|
|
157
|
+
|
|
158
|
+
Examples:
|
|
159
|
+
>>> is_compatible, version = check_claude_version()
|
|
160
|
+
>>> if not is_compatible:
|
|
161
|
+
... print(f"Claude Code {version or 'unknown'} is not supported")
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
# Try to detect Claude Code version
|
|
165
|
+
result = subprocess.run(
|
|
166
|
+
["claude", "--version"],
|
|
167
|
+
capture_output=True,
|
|
168
|
+
text=True,
|
|
169
|
+
timeout=5,
|
|
170
|
+
check=False,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
if result.returncode == 0:
|
|
174
|
+
version_text = result.stdout.strip()
|
|
175
|
+
# Extract version number (e.g., "1.0.92 (Claude Code)" -> "1.0.92")
|
|
176
|
+
match = re.match(r"^([\d\.]+)", version_text)
|
|
177
|
+
if match:
|
|
178
|
+
version = match.group(1)
|
|
179
|
+
|
|
180
|
+
# Compare versions
|
|
181
|
+
def parse_version(v: str):
|
|
182
|
+
try:
|
|
183
|
+
return [int(x) for x in v.split(".")]
|
|
184
|
+
except (ValueError, AttributeError):
|
|
185
|
+
return [0]
|
|
186
|
+
|
|
187
|
+
current = parse_version(version)
|
|
188
|
+
required = parse_version(MIN_CLAUDE_VERSION)
|
|
189
|
+
|
|
190
|
+
# Check if current version meets minimum
|
|
191
|
+
for i in range(max(len(current), len(required))):
|
|
192
|
+
curr_part = current[i] if i < len(current) else 0
|
|
193
|
+
req_part = required[i] if i < len(required) else 0
|
|
194
|
+
|
|
195
|
+
if curr_part < req_part:
|
|
196
|
+
if DEBUG:
|
|
197
|
+
print(
|
|
198
|
+
f"⚠️ Claude Code {version} does not support matcher-based hooks "
|
|
199
|
+
f"(requires {MIN_CLAUDE_VERSION}+). Hook monitoring disabled.",
|
|
200
|
+
file=sys.stderr,
|
|
201
|
+
)
|
|
202
|
+
return False, version
|
|
203
|
+
if curr_part > req_part:
|
|
204
|
+
return True, version
|
|
205
|
+
|
|
206
|
+
return True, version
|
|
207
|
+
except Exception as e:
|
|
208
|
+
if DEBUG:
|
|
209
|
+
print(
|
|
210
|
+
f"Warning: Could not detect Claude Code version: {e}", file=sys.stderr
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return False, None
|
|
214
|
+
|
|
77
215
|
|
|
78
216
|
class ClaudeHookHandler:
|
|
79
217
|
"""Refactored hook handler with service-oriented architecture.
|
|
@@ -111,6 +249,10 @@ class ClaudeHookHandler:
|
|
|
111
249
|
self.delegation_requests = self.state_manager.delegation_requests
|
|
112
250
|
self.pending_prompts = self.state_manager.pending_prompts
|
|
113
251
|
|
|
252
|
+
# Initialize git branch cache (used by event_handlers)
|
|
253
|
+
self._git_branch_cache = {}
|
|
254
|
+
self._git_branch_cache_time = {}
|
|
255
|
+
|
|
114
256
|
def handle(self):
|
|
115
257
|
"""Process hook event with minimal overhead and timeout protection.
|
|
116
258
|
|
|
@@ -225,7 +367,16 @@ class ClaudeHookHandler:
|
|
|
225
367
|
# Empty or whitespace-only data
|
|
226
368
|
return None
|
|
227
369
|
|
|
228
|
-
|
|
370
|
+
parsed = json.loads(event_data)
|
|
371
|
+
# Debug: Log the actual event format we receive
|
|
372
|
+
if DEBUG:
|
|
373
|
+
print(
|
|
374
|
+
f"Received event with keys: {list(parsed.keys())}", file=sys.stderr
|
|
375
|
+
)
|
|
376
|
+
for key in ["hook_event_name", "event", "type", "event_type"]:
|
|
377
|
+
if key in parsed:
|
|
378
|
+
print(f" {key} = '{parsed[key]}'", file=sys.stderr)
|
|
379
|
+
return parsed
|
|
229
380
|
except (json.JSONDecodeError, ValueError) as e:
|
|
230
381
|
if DEBUG:
|
|
231
382
|
print(f"Failed to parse hook event: {e}", file=sys.stderr)
|
|
@@ -245,7 +396,20 @@ class ClaudeHookHandler:
|
|
|
245
396
|
Args:
|
|
246
397
|
event: Hook event dictionary
|
|
247
398
|
"""
|
|
248
|
-
|
|
399
|
+
# Try multiple field names for compatibility
|
|
400
|
+
hook_type = (
|
|
401
|
+
event.get("hook_event_name")
|
|
402
|
+
or event.get("event")
|
|
403
|
+
or event.get("type")
|
|
404
|
+
or event.get("event_type")
|
|
405
|
+
or event.get("hook_event_type")
|
|
406
|
+
or "unknown"
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
# Log the actual event structure for debugging
|
|
410
|
+
if DEBUG and hook_type == "unknown":
|
|
411
|
+
print(f"Unknown event format, keys: {list(event.keys())}", file=sys.stderr)
|
|
412
|
+
print(f"Event sample: {str(event)[:200]}", file=sys.stderr)
|
|
249
413
|
|
|
250
414
|
# Map event types to handlers
|
|
251
415
|
event_handlers = {
|
|
@@ -316,6 +480,19 @@ def main():
|
|
|
316
480
|
global _global_handler
|
|
317
481
|
_continue_printed = False # Track if we've already printed continue
|
|
318
482
|
|
|
483
|
+
# Check Claude Code version compatibility first
|
|
484
|
+
is_compatible, version = check_claude_version()
|
|
485
|
+
if not is_compatible:
|
|
486
|
+
# Version incompatible - just continue without processing
|
|
487
|
+
# This prevents errors on older Claude Code versions
|
|
488
|
+
if DEBUG and version:
|
|
489
|
+
print(
|
|
490
|
+
f"Skipping hook processing due to version incompatibility ({version})",
|
|
491
|
+
file=sys.stderr,
|
|
492
|
+
)
|
|
493
|
+
print(json.dumps({"action": "continue"}))
|
|
494
|
+
sys.exit(0)
|
|
495
|
+
|
|
319
496
|
def cleanup_handler(signum=None, frame=None):
|
|
320
497
|
"""Cleanup handler for signals and exit."""
|
|
321
498
|
nonlocal _continue_printed
|