claude-mpm 4.6.0__py3-none-any.whl → 4.7.0__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/BASE_ENGINEER.md +206 -48
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +787 -0
- claude_mpm/agents/base_agent_loader.py +3 -1
- claude_mpm/agents/templates/engineer.json +10 -4
- claude_mpm/agents/templates/prompt-engineer.json +517 -87
- claude_mpm/cli/commands/cleanup.py +1 -1
- claude_mpm/cli/commands/mcp_setup_external.py +2 -2
- claude_mpm/cli/commands/memory.py +1 -1
- claude_mpm/cli/commands/mpm_init.py +5 -4
- claude_mpm/cli/commands/run.py +4 -4
- claude_mpm/cli/shared/argument_patterns.py +18 -11
- claude_mpm/cli/shared/base_command.py +1 -1
- claude_mpm/config/experimental_features.py +3 -3
- claude_mpm/config/socketio_config.py +1 -1
- claude_mpm/core/cache.py +2 -2
- claude_mpm/core/claude_runner.py +5 -7
- claude_mpm/core/container.py +10 -4
- claude_mpm/core/file_utils.py +10 -8
- claude_mpm/core/framework/formatters/context_generator.py +3 -2
- claude_mpm/core/framework/loaders/agent_loader.py +11 -7
- claude_mpm/core/injectable_service.py +11 -8
- claude_mpm/core/interactive_session.py +5 -4
- claude_mpm/core/oneshot_session.py +3 -2
- claude_mpm/core/pm_hook_interceptor.py +15 -9
- claude_mpm/core/unified_paths.py +6 -5
- claude_mpm/dashboard/api/simple_directory.py +16 -17
- claude_mpm/hooks/claude_hooks/event_handlers.py +3 -2
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +2 -2
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +2 -2
- claude_mpm/hooks/claude_hooks/installer.py +10 -10
- claude_mpm/hooks/claude_hooks/response_tracking.py +3 -2
- claude_mpm/hooks/claude_hooks/services/state_manager.py +3 -2
- claude_mpm/hooks/tool_call_interceptor.py +6 -3
- claude_mpm/models/agent_session.py +3 -1
- claude_mpm/scripts/mcp_server.py +3 -5
- claude_mpm/services/agents/agent_builder.py +4 -4
- claude_mpm/services/agents/deployment/deployment_type_detector.py +10 -14
- claude_mpm/services/agents/deployment/local_template_deployment.py +6 -3
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +15 -11
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +9 -6
- claude_mpm/services/agents/loading/agent_profile_loader.py +1 -2
- claude_mpm/services/agents/memory/agent_memory_manager.py +27 -27
- claude_mpm/services/agents/memory/content_manager.py +9 -4
- claude_mpm/services/claude_session_logger.py +5 -8
- claude_mpm/services/cli/memory_crud_service.py +1 -1
- claude_mpm/services/cli/memory_output_formatter.py +1 -1
- claude_mpm/services/cli/startup_checker.py +13 -10
- claude_mpm/services/cli/unified_dashboard_manager.py +10 -6
- claude_mpm/services/command_deployment_service.py +9 -7
- claude_mpm/services/core/path_resolver.py +8 -5
- claude_mpm/services/diagnostics/checks/agent_check.py +4 -7
- claude_mpm/services/diagnostics/checks/installation_check.py +19 -16
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +30 -28
- claude_mpm/services/diagnostics/checks/startup_log_check.py +5 -3
- claude_mpm/services/events/core.py +2 -3
- claude_mpm/services/framework_claude_md_generator/content_validator.py +2 -2
- claude_mpm/services/hook_installer_service.py +2 -3
- claude_mpm/services/hook_service.py +5 -6
- claude_mpm/services/mcp_gateway/auto_configure.py +4 -5
- claude_mpm/services/mcp_gateway/main.py +7 -4
- claude_mpm/services/mcp_gateway/server/stdio_server.py +3 -4
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +1 -2
- claude_mpm/services/mcp_service_verifier.py +18 -17
- claude_mpm/services/memory/builder.py +1 -2
- claude_mpm/services/memory/indexed_memory.py +1 -1
- claude_mpm/services/memory/optimizer.py +1 -2
- claude_mpm/services/monitor/daemon_manager.py +3 -3
- claude_mpm/services/monitor/handlers/file.py +5 -4
- claude_mpm/services/monitor/management/lifecycle.py +1 -1
- claude_mpm/services/monitor/server.py +14 -12
- claude_mpm/services/project/architecture_analyzer.py +5 -5
- claude_mpm/services/project/metrics_collector.py +4 -4
- claude_mpm/services/project/project_organizer.py +4 -4
- claude_mpm/services/project/registry.py +9 -3
- claude_mpm/services/shared/config_service_base.py +2 -3
- claude_mpm/services/socketio/handlers/file.py +5 -4
- claude_mpm/services/socketio/handlers/git.py +7 -7
- claude_mpm/services/socketio/server/core.py +10 -10
- claude_mpm/services/subprocess_launcher_service.py +5 -10
- claude_mpm/services/ticket_services/formatter_service.py +1 -1
- claude_mpm/services/ticket_services/validation_service.py +5 -5
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +5 -5
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +4 -4
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +4 -4
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +4 -4
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +4 -4
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +6 -2
- claude_mpm/services/unified/config_strategies/unified_config_service.py +24 -13
- claude_mpm/services/version_control/conflict_resolution.py +6 -2
- claude_mpm/services/version_control/git_operations.py +1 -1
- claude_mpm/services/version_control/version_parser.py +1 -1
- claude_mpm/storage/state_storage.py +3 -3
- claude_mpm/tools/__main__.py +1 -1
- claude_mpm/tools/code_tree_analyzer.py +17 -14
- claude_mpm/tools/socketio_debug.py +7 -7
- claude_mpm/utils/common.py +6 -2
- claude_mpm/utils/config_manager.py +9 -3
- claude_mpm/utils/database_connector.py +4 -4
- claude_mpm/utils/dependency_strategies.py +1 -1
- claude_mpm/utils/environment_context.py +3 -2
- claude_mpm/utils/file_utils.py +1 -2
- claude_mpm/utils/path_operations.py +3 -1
- claude_mpm/utils/robust_installer.py +3 -4
- claude_mpm/validation/frontmatter_validator.py +4 -4
- {claude_mpm-4.6.0.dist-info → claude_mpm-4.7.0.dist-info}/METADATA +1 -1
- {claude_mpm-4.6.0.dist-info → claude_mpm-4.7.0.dist-info}/RECORD +111 -110
- {claude_mpm-4.6.0.dist-info → claude_mpm-4.7.0.dist-info}/WHEEL +0 -0
- {claude_mpm-4.6.0.dist-info → claude_mpm-4.7.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.6.0.dist-info → claude_mpm-4.7.0.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.6.0.dist-info → claude_mpm-4.7.0.dist-info}/top_level.txt +0 -0
@@ -12,7 +12,6 @@ DESIGN DECISIONS:
|
|
12
12
|
- Compatible with UnifiedMonitorServer architecture
|
13
13
|
"""
|
14
14
|
|
15
|
-
import os
|
16
15
|
from pathlib import Path
|
17
16
|
from typing import Any, Dict, Optional
|
18
17
|
|
@@ -56,7 +55,7 @@ class FileHandler:
|
|
56
55
|
|
57
56
|
try:
|
58
57
|
file_path = data.get("file_path")
|
59
|
-
working_dir = data.get("working_dir",
|
58
|
+
working_dir = data.get("working_dir", Path.cwd())
|
60
59
|
max_size = data.get("max_size", 1024 * 1024) # 1MB default limit
|
61
60
|
|
62
61
|
if not file_path:
|
@@ -126,7 +125,7 @@ class FileHandler:
|
|
126
125
|
"""
|
127
126
|
try:
|
128
127
|
if working_dir is None:
|
129
|
-
working_dir =
|
128
|
+
working_dir = Path.cwd()
|
130
129
|
|
131
130
|
# Resolve absolute path based on working directory
|
132
131
|
file_path_obj = Path(file_path)
|
@@ -192,7 +191,9 @@ class FileHandler:
|
|
192
191
|
|
193
192
|
# Read file content
|
194
193
|
try:
|
195
|
-
with
|
194
|
+
with Path(real_path).open(
|
195
|
+
encoding="utf-8",
|
196
|
+
) as f:
|
196
197
|
content = f.read()
|
197
198
|
|
198
199
|
# Get file extension for syntax highlighting hint
|
@@ -121,7 +121,7 @@ class DaemonLifecycle:
|
|
121
121
|
sys.stderr.flush()
|
122
122
|
|
123
123
|
# Redirect stdin to /dev/null
|
124
|
-
with
|
124
|
+
with Path("/dev/null").open() as null_in:
|
125
125
|
os.dup2(null_in.fileno(), sys.stdin.fileno())
|
126
126
|
|
127
127
|
# Redirect stdout and stderr
|
@@ -368,25 +368,24 @@ class UnifiedMonitorServer:
|
|
368
368
|
async def api_file_handler(request):
|
369
369
|
"""Handle file content requests."""
|
370
370
|
import json
|
371
|
-
import os
|
372
371
|
|
373
372
|
try:
|
374
373
|
data = await request.json()
|
375
374
|
file_path = data.get("path", "")
|
376
375
|
|
377
376
|
# Security check: ensure path is absolute and exists
|
378
|
-
if not file_path or not
|
377
|
+
if not file_path or not Path(file_path).is_absolute():
|
379
378
|
return web.json_response(
|
380
379
|
{"success": False, "error": "Invalid file path"}, status=400
|
381
380
|
)
|
382
381
|
|
383
382
|
# Check if file exists and is readable
|
384
|
-
if not
|
383
|
+
if not Path(file_path).exists():
|
385
384
|
return web.json_response(
|
386
385
|
{"success": False, "error": "File not found"}, status=404
|
387
386
|
)
|
388
387
|
|
389
|
-
if not
|
388
|
+
if not Path(file_path).is_file():
|
390
389
|
return web.json_response(
|
391
390
|
{"success": False, "error": "Path is not a file"},
|
392
391
|
status=400,
|
@@ -394,7 +393,7 @@ class UnifiedMonitorServer:
|
|
394
393
|
|
395
394
|
# Read file content (with size limit for safety)
|
396
395
|
max_size = 10 * 1024 * 1024 # 10MB limit
|
397
|
-
file_size =
|
396
|
+
file_size = Path(file_path).stat().st_size
|
398
397
|
|
399
398
|
if file_size > max_size:
|
400
399
|
return web.json_response(
|
@@ -406,7 +405,9 @@ class UnifiedMonitorServer:
|
|
406
405
|
)
|
407
406
|
|
408
407
|
try:
|
409
|
-
with
|
408
|
+
with Path(file_path).open(
|
409
|
+
encoding="utf-8",
|
410
|
+
) as f:
|
410
411
|
content = f.read()
|
411
412
|
lines = content.count("\n") + 1
|
412
413
|
except UnicodeDecodeError:
|
@@ -417,7 +418,7 @@ class UnifiedMonitorServer:
|
|
417
418
|
)
|
418
419
|
|
419
420
|
# Get file extension for type detection
|
420
|
-
file_ext =
|
421
|
+
file_ext = Path(file_path).suffix.lstrip(".")
|
421
422
|
|
422
423
|
return web.json_response(
|
423
424
|
{
|
@@ -475,11 +476,10 @@ class UnifiedMonitorServer:
|
|
475
476
|
# Configuration endpoint for dashboard initialization
|
476
477
|
async def config_handler(request):
|
477
478
|
"""Return configuration for dashboard initialization."""
|
478
|
-
import os
|
479
479
|
import subprocess
|
480
480
|
|
481
481
|
config = {
|
482
|
-
"workingDirectory":
|
482
|
+
"workingDirectory": Path.cwd(),
|
483
483
|
"gitBranch": "Unknown",
|
484
484
|
"serverTime": datetime.now(timezone.utc).isoformat() + "Z",
|
485
485
|
"service": "unified-monitor",
|
@@ -492,7 +492,7 @@ class UnifiedMonitorServer:
|
|
492
492
|
capture_output=True,
|
493
493
|
text=True,
|
494
494
|
timeout=2,
|
495
|
-
cwd=
|
495
|
+
cwd=Path.cwd(),
|
496
496
|
check=False,
|
497
497
|
)
|
498
498
|
if result.returncode == 0 and result.stdout.strip():
|
@@ -506,7 +506,7 @@ class UnifiedMonitorServer:
|
|
506
506
|
async def working_directory_handler(request):
|
507
507
|
"""Return the current working directory."""
|
508
508
|
return web.json_response(
|
509
|
-
{"working_directory":
|
509
|
+
{"working_directory": Path.cwd(), "success": True}
|
510
510
|
)
|
511
511
|
|
512
512
|
# Monitor page routes
|
@@ -517,7 +517,9 @@ class UnifiedMonitorServer:
|
|
517
517
|
file_path = static_dir / f"{page_name}.html"
|
518
518
|
|
519
519
|
if file_path.exists() and file_path.is_file():
|
520
|
-
with
|
520
|
+
with Path(file_path).open(
|
521
|
+
encoding="utf-8",
|
522
|
+
) as f:
|
521
523
|
content = f.read()
|
522
524
|
return web.Response(text=content, content_type="text/html")
|
523
525
|
return web.Response(text="Page not found", status=404)
|
@@ -15,7 +15,7 @@ import logging
|
|
15
15
|
import re
|
16
16
|
from dataclasses import asdict, dataclass
|
17
17
|
from pathlib import Path
|
18
|
-
from typing import Dict, List
|
18
|
+
from typing import ClassVar, Dict, List
|
19
19
|
|
20
20
|
|
21
21
|
@dataclass
|
@@ -44,7 +44,7 @@ class ArchitectureAnalyzerService:
|
|
44
44
|
"""
|
45
45
|
|
46
46
|
# Common architectural directories
|
47
|
-
ARCHITECTURE_INDICATORS = {
|
47
|
+
ARCHITECTURE_INDICATORS: ClassVar[dict] = {
|
48
48
|
"mvc": ["models", "views", "controllers"],
|
49
49
|
"mvvm": ["models", "views", "viewmodels"],
|
50
50
|
"layered": ["presentation", "business", "data", "domain"],
|
@@ -58,7 +58,7 @@ class ArchitectureAnalyzerService:
|
|
58
58
|
}
|
59
59
|
|
60
60
|
# Entry point patterns by language/framework
|
61
|
-
ENTRY_POINT_PATTERNS = {
|
61
|
+
ENTRY_POINT_PATTERNS: ClassVar[dict] = {
|
62
62
|
"python": [
|
63
63
|
"main.py",
|
64
64
|
"app.py",
|
@@ -77,7 +77,7 @@ class ArchitectureAnalyzerService:
|
|
77
77
|
}
|
78
78
|
|
79
79
|
# API pattern indicators
|
80
|
-
API_INDICATORS = {
|
80
|
+
API_INDICATORS: ClassVar[dict] = {
|
81
81
|
"rest": ["routes", "endpoints", "resources", "api/v", "/api/"],
|
82
82
|
"graphql": ["schema.graphql", "resolvers", "typeDefs", "graphql"],
|
83
83
|
"grpc": [".proto", "grpc", "protobuf", "rpc"],
|
@@ -86,7 +86,7 @@ class ArchitectureAnalyzerService:
|
|
86
86
|
}
|
87
87
|
|
88
88
|
# Configuration file patterns
|
89
|
-
CONFIG_PATTERNS = {
|
89
|
+
CONFIG_PATTERNS: ClassVar[dict] = {
|
90
90
|
"yaml": [".yaml", ".yml"],
|
91
91
|
"json": [".json", "config.json", "settings.json"],
|
92
92
|
"toml": [".toml", "pyproject.toml", "Cargo.toml"],
|
@@ -15,7 +15,7 @@ import logging
|
|
15
15
|
from collections import Counter
|
16
16
|
from dataclasses import asdict, dataclass
|
17
17
|
from pathlib import Path
|
18
|
-
from typing import Dict, List, Tuple
|
18
|
+
from typing import ClassVar, Dict, List, Tuple
|
19
19
|
|
20
20
|
|
21
21
|
@dataclass
|
@@ -68,7 +68,7 @@ class MetricsCollectorService:
|
|
68
68
|
"""
|
69
69
|
|
70
70
|
# File extensions to analyze
|
71
|
-
CODE_EXTENSIONS = {
|
71
|
+
CODE_EXTENSIONS: ClassVar[set] = {
|
72
72
|
".py",
|
73
73
|
".js",
|
74
74
|
".ts",
|
@@ -90,7 +90,7 @@ class MetricsCollectorService:
|
|
90
90
|
}
|
91
91
|
|
92
92
|
# Test file patterns
|
93
|
-
TEST_PATTERNS = [
|
93
|
+
TEST_PATTERNS: ClassVar[list] = [
|
94
94
|
"test_",
|
95
95
|
"_test.",
|
96
96
|
".test.",
|
@@ -103,7 +103,7 @@ class MetricsCollectorService:
|
|
103
103
|
]
|
104
104
|
|
105
105
|
# Directories to exclude from analysis
|
106
|
-
EXCLUDE_DIRS = {
|
106
|
+
EXCLUDE_DIRS: ClassVar[set] = {
|
107
107
|
".git",
|
108
108
|
"node_modules",
|
109
109
|
"vendor",
|
@@ -17,7 +17,7 @@ Created: 2025-01-26
|
|
17
17
|
"""
|
18
18
|
|
19
19
|
from pathlib import Path
|
20
|
-
from typing import Dict, List, Optional, Tuple
|
20
|
+
from typing import ClassVar, Dict, List, Optional, Tuple
|
21
21
|
|
22
22
|
from rich.console import Console
|
23
23
|
|
@@ -31,7 +31,7 @@ class ProjectOrganizer:
|
|
31
31
|
"""Manages project directory structure and organization."""
|
32
32
|
|
33
33
|
# Standard directory structure for Claude MPM projects
|
34
|
-
STANDARD_DIRECTORIES = {
|
34
|
+
STANDARD_DIRECTORIES: ClassVar[dict] = {
|
35
35
|
"tmp": "Temporary files, test outputs, and experiments",
|
36
36
|
"scripts": "Project scripts and automation tools",
|
37
37
|
"docs": "Project documentation",
|
@@ -44,7 +44,7 @@ class ProjectOrganizer:
|
|
44
44
|
}
|
45
45
|
|
46
46
|
# Comprehensive gitignore patterns for Claude MPM projects
|
47
|
-
GITIGNORE_DIRS = {
|
47
|
+
GITIGNORE_DIRS: ClassVar[dict] = {
|
48
48
|
# Temporary and cache directories
|
49
49
|
"tmp/",
|
50
50
|
"temp/",
|
@@ -160,7 +160,7 @@ class ProjectOrganizer:
|
|
160
160
|
}
|
161
161
|
|
162
162
|
# Project type specific structures
|
163
|
-
PROJECT_STRUCTURES = {
|
163
|
+
PROJECT_STRUCTURES: ClassVar[dict] = {
|
164
164
|
"web": ["public", "src/components", "src/pages", "src/styles"],
|
165
165
|
"api": ["src/routes", "src/models", "src/middleware", "src/services"],
|
166
166
|
"cli": ["src/commands", "src/utils", "src/config"],
|
@@ -124,7 +124,9 @@ class ProjectRegistry:
|
|
124
124
|
# Search all registry files
|
125
125
|
for registry_file in self.registry_dir.glob("*.yaml"):
|
126
126
|
try:
|
127
|
-
with
|
127
|
+
with Path(registry_file).open(
|
128
|
+
encoding="utf-8",
|
129
|
+
) as f:
|
128
130
|
data = yaml.safe_load(f) or {}
|
129
131
|
|
130
132
|
# Check if project_path matches
|
@@ -517,7 +519,9 @@ class ProjectRegistry:
|
|
517
519
|
try:
|
518
520
|
for registry_file in self.registry_dir.glob("*.yaml"):
|
519
521
|
try:
|
520
|
-
with
|
522
|
+
with Path(registry_file).open(
|
523
|
+
encoding="utf-8",
|
524
|
+
) as f:
|
521
525
|
data = yaml.safe_load(f) or {}
|
522
526
|
projects.append(data)
|
523
527
|
except Exception as e:
|
@@ -553,7 +557,9 @@ class ProjectRegistry:
|
|
553
557
|
try:
|
554
558
|
for registry_file in self.registry_dir.glob("*.yaml"):
|
555
559
|
try:
|
556
|
-
with
|
560
|
+
with Path(registry_file).open(
|
561
|
+
encoding="utf-8",
|
562
|
+
) as f:
|
557
563
|
data = yaml.safe_load(f) or {}
|
558
564
|
|
559
565
|
# Check last accessed time
|
@@ -99,9 +99,8 @@ class ConfigServiceBase(LoggerMixin, ABC):
|
|
99
99
|
raise ValueError(f"Required configuration value missing: {full_key}")
|
100
100
|
|
101
101
|
# Type validation
|
102
|
-
if config_type is not None and value is not None:
|
103
|
-
|
104
|
-
try:
|
102
|
+
if config_type is not None and value is not None and not isinstance(value, config_type):
|
103
|
+
try:
|
105
104
|
# Try to convert
|
106
105
|
if config_type == bool and isinstance(value, str):
|
107
106
|
value = value.lower() in ("true", "1", "yes", "on")
|
@@ -5,7 +5,6 @@ safely with security checks. Separating file operations improves security
|
|
5
5
|
auditing and makes it easier to add file-related features.
|
6
6
|
"""
|
7
7
|
|
8
|
-
import os
|
9
8
|
from pathlib import Path
|
10
9
|
from typing import Any, Dict, Optional
|
11
10
|
|
@@ -44,7 +43,7 @@ class FileEventHandler(BaseEventHandler):
|
|
44
43
|
)
|
45
44
|
try:
|
46
45
|
file_path = data.get("file_path")
|
47
|
-
working_dir = data.get("working_dir",
|
46
|
+
working_dir = data.get("working_dir", Path.cwd())
|
48
47
|
max_size = data.get("max_size", 1024 * 1024) # 1MB default limit
|
49
48
|
|
50
49
|
if not file_path:
|
@@ -125,7 +124,7 @@ class FileEventHandler(BaseEventHandler):
|
|
125
124
|
"""
|
126
125
|
try:
|
127
126
|
if working_dir is None:
|
128
|
-
working_dir =
|
127
|
+
working_dir = Path.cwd()
|
129
128
|
|
130
129
|
# Resolve absolute path based on working directory
|
131
130
|
file_path_obj = Path(file_path)
|
@@ -191,7 +190,9 @@ class FileEventHandler(BaseEventHandler):
|
|
191
190
|
|
192
191
|
# Read file content
|
193
192
|
try:
|
194
|
-
with
|
193
|
+
with Path(real_path).open(
|
194
|
+
encoding="utf-8",
|
195
|
+
) as f:
|
195
196
|
content = f.read()
|
196
197
|
|
197
198
|
# Get file extension for syntax highlighting hint
|
@@ -106,7 +106,7 @@ class GitEventHandler(BaseEventHandler):
|
|
106
106
|
"""
|
107
107
|
try:
|
108
108
|
file_path = data.get("file_path")
|
109
|
-
working_dir = data.get("working_dir",
|
109
|
+
working_dir = data.get("working_dir", Path.cwd())
|
110
110
|
|
111
111
|
if not file_path:
|
112
112
|
await self.emit_to_client(
|
@@ -162,7 +162,7 @@ class GitEventHandler(BaseEventHandler):
|
|
162
162
|
"""
|
163
163
|
try:
|
164
164
|
file_path = data.get("file_path")
|
165
|
-
working_dir = data.get("working_dir",
|
165
|
+
working_dir = data.get("working_dir", Path.cwd())
|
166
166
|
|
167
167
|
# Debug: check_git_status called
|
168
168
|
|
@@ -287,7 +287,7 @@ class GitEventHandler(BaseEventHandler):
|
|
287
287
|
"""
|
288
288
|
try:
|
289
289
|
file_path = data.get("file_path")
|
290
|
-
working_dir = data.get("working_dir",
|
290
|
+
working_dir = data.get("working_dir", Path.cwd())
|
291
291
|
|
292
292
|
# Debug: git_add_file called
|
293
293
|
|
@@ -389,7 +389,7 @@ class GitEventHandler(BaseEventHandler):
|
|
389
389
|
if working_dir in invalid_states or (
|
390
390
|
isinstance(working_dir, str) and working_dir.strip() == ""
|
391
391
|
):
|
392
|
-
working_dir =
|
392
|
+
working_dir = Path.cwd()
|
393
393
|
self.logger.info(
|
394
394
|
f"[{operation}] working_dir was invalid ({original_working_dir!r}), using cwd: {working_dir}"
|
395
395
|
)
|
@@ -404,7 +404,7 @@ class GitEventHandler(BaseEventHandler):
|
|
404
404
|
self.logger.warning(
|
405
405
|
f"[{operation}] working_dir contains null bytes, using cwd instead"
|
406
406
|
)
|
407
|
-
working_dir =
|
407
|
+
working_dir = Path.cwd()
|
408
408
|
|
409
409
|
return working_dir
|
410
410
|
|
@@ -640,7 +640,7 @@ class GitEventHandler(BaseEventHandler):
|
|
640
640
|
|
641
641
|
# Handle case where working_dir is None, empty string, or 'Unknown'
|
642
642
|
if not working_dir or working_dir == "Unknown" or working_dir.strip() == "":
|
643
|
-
working_dir =
|
643
|
+
working_dir = Path.cwd()
|
644
644
|
# Debug: working_dir was invalid, using cwd
|
645
645
|
else:
|
646
646
|
# Debug: Using provided working_dir
|
@@ -648,7 +648,7 @@ class GitEventHandler(BaseEventHandler):
|
|
648
648
|
|
649
649
|
# For read-only git operations, we can work from any directory
|
650
650
|
# by passing the -C flag to git commands instead of changing directories
|
651
|
-
original_cwd =
|
651
|
+
original_cwd = Path.cwd()
|
652
652
|
try:
|
653
653
|
# We'll use git -C <working_dir> for all commands instead of chdir
|
654
654
|
|
@@ -396,11 +396,10 @@ class SocketIOServerCore:
|
|
396
396
|
# Add working directory endpoint
|
397
397
|
async def working_directory_handler(request):
|
398
398
|
"""Handle GET /api/working-directory to provide current working directory."""
|
399
|
-
import os
|
400
399
|
from pathlib import Path
|
401
400
|
|
402
401
|
try:
|
403
|
-
working_dir =
|
402
|
+
working_dir = Path.cwd()
|
404
403
|
home_dir = str(Path.home())
|
405
404
|
|
406
405
|
return web.json_response(
|
@@ -430,27 +429,26 @@ class SocketIOServerCore:
|
|
430
429
|
# Add file reading endpoint for source viewer
|
431
430
|
async def file_read_handler(request):
|
432
431
|
"""Handle GET /api/file/read for reading source files."""
|
433
|
-
import os
|
434
432
|
|
435
433
|
file_path = request.query.get("path", "")
|
436
434
|
|
437
435
|
if not file_path:
|
438
436
|
return web.json_response({"error": "No path provided"}, status=400)
|
439
437
|
|
440
|
-
abs_path =
|
438
|
+
abs_path = Path(Path(file_path).resolve().expanduser())
|
441
439
|
|
442
440
|
# Security check - ensure file is within the project
|
443
441
|
try:
|
444
|
-
project_root =
|
442
|
+
project_root = Path.cwd()
|
445
443
|
if not abs_path.startswith(project_root):
|
446
444
|
return web.json_response({"error": "Access denied"}, status=403)
|
447
445
|
except Exception:
|
448
446
|
pass
|
449
447
|
|
450
|
-
if not
|
448
|
+
if not Path(abs_path).exists():
|
451
449
|
return web.json_response({"error": "File not found"}, status=404)
|
452
450
|
|
453
|
-
if not
|
451
|
+
if not Path(abs_path).is_file():
|
454
452
|
return web.json_response({"error": "Not a file"}, status=400)
|
455
453
|
|
456
454
|
try:
|
@@ -460,7 +458,9 @@ class SocketIOServerCore:
|
|
460
458
|
|
461
459
|
for encoding in encodings:
|
462
460
|
try:
|
463
|
-
with
|
461
|
+
with Path(abs_path).open(
|
462
|
+
encoding=encoding,
|
463
|
+
) as f:
|
464
464
|
content = f.read()
|
465
465
|
break
|
466
466
|
except UnicodeDecodeError:
|
@@ -474,10 +474,10 @@ class SocketIOServerCore:
|
|
474
474
|
return web.json_response(
|
475
475
|
{
|
476
476
|
"path": abs_path,
|
477
|
-
"name":
|
477
|
+
"name": Path(abs_path).name,
|
478
478
|
"content": content,
|
479
479
|
"lines": len(content.splitlines()),
|
480
|
-
"size":
|
480
|
+
"size": Path(abs_path).stat().st_size,
|
481
481
|
}
|
482
482
|
)
|
483
483
|
|
@@ -268,16 +268,11 @@ class SubprocessLauncherService(BaseService, SubprocessLauncherInterface):
|
|
268
268
|
Returns:
|
269
269
|
True if subprocess mode with PTY is available
|
270
270
|
"""
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
import tty
|
277
|
-
|
278
|
-
return True
|
279
|
-
except ImportError:
|
280
|
-
return False
|
271
|
+
import importlib.util
|
272
|
+
|
273
|
+
# Check if we can import required modules
|
274
|
+
required = ["pty", "select", "termios", "tty"]
|
275
|
+
return all(importlib.util.find_spec(mod) is not None for mod in required)
|
281
276
|
|
282
277
|
def create_subprocess_command(
|
283
278
|
self, base_cmd: List[str], additional_args: Optional[List[str]] = None
|
@@ -11,17 +11,17 @@ DESIGN DECISIONS:
|
|
11
11
|
- Provides sanitization for user inputs
|
12
12
|
"""
|
13
13
|
|
14
|
-
from typing import Any, Dict, List, Optional, Tuple
|
14
|
+
from typing import Any, ClassVar, Dict, List, Optional, Tuple
|
15
15
|
|
16
16
|
|
17
17
|
class TicketValidationService:
|
18
18
|
"""Service for validating ticket inputs."""
|
19
19
|
|
20
20
|
# Valid ticket types
|
21
|
-
VALID_TYPES = ["task", "issue", "epic", "bug", "feature", "story"]
|
21
|
+
VALID_TYPES: ClassVar[list] = ["task", "issue", "epic", "bug", "feature", "story"]
|
22
22
|
|
23
23
|
# Valid ticket statuses
|
24
|
-
VALID_STATUSES = [
|
24
|
+
VALID_STATUSES: ClassVar[list] = [
|
25
25
|
"open",
|
26
26
|
"in_progress",
|
27
27
|
"ready",
|
@@ -34,10 +34,10 @@ class TicketValidationService:
|
|
34
34
|
]
|
35
35
|
|
36
36
|
# Valid priorities
|
37
|
-
VALID_PRIORITIES = ["low", "medium", "high", "critical"]
|
37
|
+
VALID_PRIORITIES: ClassVar[list] = ["low", "medium", "high", "critical"]
|
38
38
|
|
39
39
|
# Valid workflow states
|
40
|
-
VALID_WORKFLOW_STATES = [
|
40
|
+
VALID_WORKFLOW_STATES: ClassVar[list] = [
|
41
41
|
"todo",
|
42
42
|
"in_progress",
|
43
43
|
"ready",
|
@@ -12,7 +12,7 @@ Created: 2025-01-26
|
|
12
12
|
import json
|
13
13
|
import re
|
14
14
|
from pathlib import Path
|
15
|
-
from typing import Any, Dict, List, Optional
|
15
|
+
from typing import Any, ClassVar, Dict, List, Optional
|
16
16
|
|
17
17
|
from claude_mpm.core.logging_utils import get_logger
|
18
18
|
|
@@ -39,7 +39,7 @@ class DependencyAnalyzerStrategy(AnalyzerStrategy):
|
|
39
39
|
"""
|
40
40
|
|
41
41
|
# Package manager configurations
|
42
|
-
PACKAGE_MANAGERS = {
|
42
|
+
PACKAGE_MANAGERS: ClassVar[dict] = {
|
43
43
|
"package.json": "npm",
|
44
44
|
"yarn.lock": "yarn",
|
45
45
|
"pnpm-lock.yaml": "pnpm",
|
@@ -59,7 +59,7 @@ class DependencyAnalyzerStrategy(AnalyzerStrategy):
|
|
59
59
|
}
|
60
60
|
|
61
61
|
# Database-related dependencies
|
62
|
-
DATABASE_PACKAGES = {
|
62
|
+
DATABASE_PACKAGES: ClassVar[dict] = {
|
63
63
|
"postgresql": ["psycopg2", "pg", "postgres", "postgresql", "node-postgres"],
|
64
64
|
"mysql": ["mysql", "mysql2", "mysqlclient", "mysql-connector"],
|
65
65
|
"sqlite": ["sqlite3", "better-sqlite3"],
|
@@ -71,7 +71,7 @@ class DependencyAnalyzerStrategy(AnalyzerStrategy):
|
|
71
71
|
}
|
72
72
|
|
73
73
|
# Testing framework packages
|
74
|
-
TESTING_PACKAGES = {
|
74
|
+
TESTING_PACKAGES: ClassVar[dict] = {
|
75
75
|
"python": ["pytest", "unittest", "nose", "nose2", "tox", "coverage"],
|
76
76
|
"javascript": [
|
77
77
|
"jest",
|
@@ -89,7 +89,7 @@ class DependencyAnalyzerStrategy(AnalyzerStrategy):
|
|
89
89
|
}
|
90
90
|
|
91
91
|
# Web framework packages
|
92
|
-
FRAMEWORK_PACKAGES = {
|
92
|
+
FRAMEWORK_PACKAGES: ClassVar[dict] = {
|
93
93
|
"python": ["django", "flask", "fastapi", "pyramid", "tornado", "aiohttp"],
|
94
94
|
"javascript": [
|
95
95
|
"express",
|
@@ -12,7 +12,7 @@ Created: 2025-01-26
|
|
12
12
|
import ast
|
13
13
|
import re
|
14
14
|
from pathlib import Path
|
15
|
-
from typing import Any, Dict, List, Optional
|
15
|
+
from typing import Any, ClassVar, Dict, List, Optional
|
16
16
|
|
17
17
|
from claude_mpm.core.logging_utils import get_logger
|
18
18
|
|
@@ -39,7 +39,7 @@ class PerformanceAnalyzerStrategy(AnalyzerStrategy):
|
|
39
39
|
"""
|
40
40
|
|
41
41
|
# Performance anti-patterns
|
42
|
-
PERFORMANCE_PATTERNS = {
|
42
|
+
PERFORMANCE_PATTERNS: ClassVar[dict] = {
|
43
43
|
"n_plus_one_query": {
|
44
44
|
"patterns": [
|
45
45
|
r"for .* in .*:\s*\n.*\.(get|filter|select|find)",
|
@@ -89,7 +89,7 @@ class PerformanceAnalyzerStrategy(AnalyzerStrategy):
|
|
89
89
|
}
|
90
90
|
|
91
91
|
# Algorithm complexity indicators
|
92
|
-
COMPLEXITY_INDICATORS = {
|
92
|
+
COMPLEXITY_INDICATORS: ClassVar[dict] = {
|
93
93
|
"quadratic": ["nested_loops", "bubble_sort", "selection_sort"],
|
94
94
|
"exponential": ["recursive_fibonacci", "recursive_factorial"],
|
95
95
|
"linear": ["single_loop", "map", "filter"],
|
@@ -98,7 +98,7 @@ class PerformanceAnalyzerStrategy(AnalyzerStrategy):
|
|
98
98
|
}
|
99
99
|
|
100
100
|
# Memory usage patterns
|
101
|
-
MEMORY_PATTERNS = {
|
101
|
+
MEMORY_PATTERNS: ClassVar[dict] = {
|
102
102
|
"memory_leak": {
|
103
103
|
"patterns": [
|
104
104
|
r"global\s+\w+\s*=",
|
@@ -12,7 +12,7 @@ Created: 2025-01-26
|
|
12
12
|
import ast
|
13
13
|
import re
|
14
14
|
from pathlib import Path
|
15
|
-
from typing import Any, Dict, List, Optional
|
15
|
+
from typing import Any, ClassVar, Dict, List, Optional
|
16
16
|
|
17
17
|
from claude_mpm.core.logging_utils import get_logger
|
18
18
|
|
@@ -39,7 +39,7 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
|
|
39
39
|
"""
|
40
40
|
|
41
41
|
# Common security vulnerability patterns
|
42
|
-
VULNERABILITY_PATTERNS = {
|
42
|
+
VULNERABILITY_PATTERNS: ClassVar[dict] = {
|
43
43
|
"sql_injection": {
|
44
44
|
"patterns": [
|
45
45
|
r'(execute|query)\s*\(\s*["\'].*%[s|d].*["\'].*%',
|
@@ -97,7 +97,7 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
|
|
97
97
|
}
|
98
98
|
|
99
99
|
# Insecure configuration patterns
|
100
|
-
CONFIG_ISSUES = {
|
100
|
+
CONFIG_ISSUES: ClassVar[dict] = {
|
101
101
|
"debug_enabled": {
|
102
102
|
"patterns": [
|
103
103
|
r"DEBUG\s*=\s*True",
|
@@ -128,7 +128,7 @@ class SecurityAnalyzerStrategy(AnalyzerStrategy):
|
|
128
128
|
}
|
129
129
|
|
130
130
|
# Security headers to check
|
131
|
-
SECURITY_HEADERS = [
|
131
|
+
SECURITY_HEADERS: ClassVar[list] = [
|
132
132
|
"Content-Security-Policy",
|
133
133
|
"X-Content-Type-Options",
|
134
134
|
"X-Frame-Options",
|