claude-mpm 4.4.0__py3-none-any.whl → 4.4.4__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/WORKFLOW.md +2 -14
- claude_mpm/agents/agent_loader.py +3 -2
- claude_mpm/agents/agent_loader_integration.py +2 -1
- claude_mpm/agents/async_agent_loader.py +2 -2
- claude_mpm/agents/base_agent_loader.py +2 -2
- claude_mpm/agents/frontmatter_validator.py +1 -0
- claude_mpm/agents/system_agent_config.py +2 -1
- claude_mpm/cli/commands/configure.py +2 -29
- claude_mpm/cli/commands/doctor.py +44 -5
- claude_mpm/cli/commands/mpm_init.py +117 -63
- claude_mpm/cli/parsers/configure_parser.py +6 -15
- claude_mpm/cli/startup_logging.py +1 -3
- claude_mpm/config/agent_config.py +1 -1
- claude_mpm/config/paths.py +2 -1
- claude_mpm/core/agent_name_normalizer.py +1 -0
- claude_mpm/core/config.py +2 -1
- claude_mpm/core/config_aliases.py +2 -1
- claude_mpm/core/file_utils.py +0 -1
- claude_mpm/core/framework/__init__.py +38 -0
- claude_mpm/core/framework/formatters/__init__.py +11 -0
- claude_mpm/core/framework/formatters/capability_generator.py +367 -0
- claude_mpm/core/framework/formatters/content_formatter.py +288 -0
- claude_mpm/core/framework/formatters/context_generator.py +184 -0
- claude_mpm/core/framework/loaders/__init__.py +13 -0
- claude_mpm/core/framework/loaders/agent_loader.py +206 -0
- claude_mpm/core/framework/loaders/file_loader.py +223 -0
- claude_mpm/core/framework/loaders/instruction_loader.py +161 -0
- claude_mpm/core/framework/loaders/packaged_loader.py +232 -0
- claude_mpm/core/framework/processors/__init__.py +11 -0
- claude_mpm/core/framework/processors/memory_processor.py +230 -0
- claude_mpm/core/framework/processors/metadata_processor.py +146 -0
- claude_mpm/core/framework/processors/template_processor.py +244 -0
- claude_mpm/core/framework_loader.py +298 -1795
- claude_mpm/core/log_manager.py +2 -1
- claude_mpm/core/tool_access_control.py +1 -0
- claude_mpm/core/unified_agent_registry.py +2 -1
- claude_mpm/core/unified_paths.py +1 -0
- claude_mpm/experimental/cli_enhancements.py +1 -0
- claude_mpm/hooks/__init__.py +9 -1
- claude_mpm/hooks/base_hook.py +1 -0
- claude_mpm/hooks/instruction_reinforcement.py +1 -0
- claude_mpm/hooks/kuzu_memory_hook.py +359 -0
- claude_mpm/hooks/validation_hooks.py +1 -1
- claude_mpm/scripts/mpm_doctor.py +1 -0
- claude_mpm/services/agents/loading/agent_profile_loader.py +1 -1
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -1
- claude_mpm/services/agents/loading/framework_agent_loader.py +1 -1
- claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -0
- claude_mpm/services/agents/management/agent_management_service.py +1 -1
- claude_mpm/services/agents/memory/memory_categorization_service.py +0 -1
- claude_mpm/services/agents/memory/memory_file_service.py +6 -2
- claude_mpm/services/agents/memory/memory_format_service.py +0 -1
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +1 -1
- claude_mpm/services/async_session_logger.py +1 -1
- claude_mpm/services/claude_session_logger.py +1 -0
- claude_mpm/services/core/path_resolver.py +2 -0
- claude_mpm/services/diagnostics/checks/__init__.py +2 -0
- claude_mpm/services/diagnostics/checks/installation_check.py +126 -25
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +399 -0
- claude_mpm/services/diagnostics/diagnostic_runner.py +4 -0
- claude_mpm/services/diagnostics/doctor_reporter.py +259 -32
- claude_mpm/services/event_bus/direct_relay.py +2 -1
- claude_mpm/services/event_bus/event_bus.py +1 -0
- claude_mpm/services/event_bus/relay.py +3 -2
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +1 -1
- claude_mpm/services/infrastructure/daemon_manager.py +1 -1
- claude_mpm/services/mcp_config_manager.py +67 -4
- claude_mpm/services/mcp_gateway/core/process_pool.py +320 -0
- claude_mpm/services/mcp_gateway/core/startup_verification.py +2 -2
- claude_mpm/services/mcp_gateway/main.py +3 -13
- claude_mpm/services/mcp_gateway/server/stdio_server.py +4 -10
- claude_mpm/services/mcp_gateway/tools/__init__.py +14 -2
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +38 -6
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +527 -0
- claude_mpm/services/memory/cache/simple_cache.py +1 -1
- claude_mpm/services/project/archive_manager.py +159 -96
- claude_mpm/services/project/documentation_manager.py +64 -45
- claude_mpm/services/project/enhanced_analyzer.py +132 -89
- claude_mpm/services/project/project_organizer.py +225 -131
- claude_mpm/services/response_tracker.py +1 -1
- claude_mpm/services/shared/__init__.py +2 -1
- claude_mpm/services/shared/service_factory.py +8 -5
- claude_mpm/services/socketio/server/eventbus_integration.py +1 -1
- claude_mpm/services/unified/__init__.py +1 -1
- claude_mpm/services/unified/analyzer_strategies/__init__.py +3 -3
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +97 -53
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +81 -40
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +277 -178
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +196 -112
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +83 -49
- claude_mpm/services/unified/config_strategies/__init__.py +175 -0
- claude_mpm/services/unified/config_strategies/config_schema.py +735 -0
- claude_mpm/services/unified/config_strategies/context_strategy.py +750 -0
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +1009 -0
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +879 -0
- claude_mpm/services/unified/config_strategies/unified_config_service.py +814 -0
- claude_mpm/services/unified/config_strategies/validation_strategy.py +1144 -0
- claude_mpm/services/unified/deployment_strategies/__init__.py +7 -7
- claude_mpm/services/unified/deployment_strategies/base.py +24 -28
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +168 -88
- claude_mpm/services/unified/deployment_strategies/local.py +49 -34
- claude_mpm/services/unified/deployment_strategies/utils.py +39 -43
- claude_mpm/services/unified/deployment_strategies/vercel.py +30 -24
- claude_mpm/services/unified/interfaces.py +0 -26
- claude_mpm/services/unified/migration.py +17 -40
- claude_mpm/services/unified/strategies.py +9 -26
- claude_mpm/services/unified/unified_analyzer.py +48 -44
- claude_mpm/services/unified/unified_config.py +21 -19
- claude_mpm/services/unified/unified_deployment.py +21 -26
- claude_mpm/storage/state_storage.py +1 -0
- claude_mpm/utils/agent_dependency_loader.py +18 -6
- claude_mpm/utils/common.py +14 -12
- claude_mpm/utils/database_connector.py +15 -12
- claude_mpm/utils/error_handler.py +1 -0
- claude_mpm/utils/log_cleanup.py +1 -0
- claude_mpm/utils/path_operations.py +1 -0
- claude_mpm/utils/session_logging.py +1 -1
- claude_mpm/utils/subprocess_utils.py +1 -0
- claude_mpm/validation/agent_validator.py +1 -1
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/METADATA +23 -17
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/RECORD +126 -105
- claude_mpm/cli/commands/configure_tui.py +0 -1927
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +0 -645
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +0 -602
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/WHEEL +0 -0
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.4.0.dist-info → claude_mpm-4.4.4.dist-info}/top_level.txt +0 -0
@@ -16,14 +16,13 @@ Author: Claude MPM Development Team
|
|
16
16
|
Created: 2025-01-26
|
17
17
|
"""
|
18
18
|
|
19
|
-
import os
|
20
|
-
import stat
|
21
19
|
from pathlib import Path
|
22
|
-
from typing import Dict, List, Optional,
|
20
|
+
from typing import Dict, List, Optional, Tuple
|
23
21
|
|
24
22
|
from rich.console import Console
|
25
23
|
|
26
24
|
from claude_mpm.core.logging_utils import get_logger
|
25
|
+
|
27
26
|
logger = get_logger(__name__)
|
28
27
|
console = Console()
|
29
28
|
|
@@ -55,7 +54,6 @@ class ProjectOrganizer:
|
|
55
54
|
".claude-mpm/cache/",
|
56
55
|
".claude-mpm/logs/",
|
57
56
|
".claude/cache/",
|
58
|
-
|
59
57
|
# Python artifacts
|
60
58
|
"__pycache__/",
|
61
59
|
"*.py[cod]",
|
@@ -72,7 +70,6 @@ class ProjectOrganizer:
|
|
72
70
|
"pip-wheel-metadata/",
|
73
71
|
"*.manifest",
|
74
72
|
"*.spec",
|
75
|
-
|
76
73
|
# Testing and coverage
|
77
74
|
".pytest_cache/",
|
78
75
|
".coverage",
|
@@ -85,7 +82,6 @@ class ProjectOrganizer:
|
|
85
82
|
".pytype/",
|
86
83
|
"coverage.xml",
|
87
84
|
"*.pytest_cache",
|
88
|
-
|
89
85
|
# Virtual environments
|
90
86
|
".env",
|
91
87
|
".venv",
|
@@ -97,7 +93,6 @@ class ProjectOrganizer:
|
|
97
93
|
"virtualenv/",
|
98
94
|
".conda/",
|
99
95
|
"conda-env/",
|
100
|
-
|
101
96
|
# IDE and editor files
|
102
97
|
".vscode/",
|
103
98
|
".idea/",
|
@@ -109,7 +104,6 @@ class ProjectOrganizer:
|
|
109
104
|
".settings/",
|
110
105
|
"*.sublime-workspace",
|
111
106
|
"*.sublime-project",
|
112
|
-
|
113
107
|
# OS-specific files
|
114
108
|
".DS_Store",
|
115
109
|
"Thumbs.db",
|
@@ -121,7 +115,6 @@ class ProjectOrganizer:
|
|
121
115
|
"*.msm",
|
122
116
|
"*.msp",
|
123
117
|
"*.lnk",
|
124
|
-
|
125
118
|
# Logs and databases
|
126
119
|
"*.log",
|
127
120
|
"*.sql",
|
@@ -129,7 +122,6 @@ class ProjectOrganizer:
|
|
129
122
|
"*.sqlite3",
|
130
123
|
"*.db",
|
131
124
|
"logs/",
|
132
|
-
|
133
125
|
# Node/JavaScript
|
134
126
|
"node_modules/",
|
135
127
|
"npm-debug.log*",
|
@@ -137,12 +129,10 @@ class ProjectOrganizer:
|
|
137
129
|
"yarn-error.log*",
|
138
130
|
".npm",
|
139
131
|
".yarn/",
|
140
|
-
|
141
132
|
# Documentation builds
|
142
133
|
"_build/",
|
143
134
|
"site/",
|
144
135
|
"docs/_build/",
|
145
|
-
|
146
136
|
# Security and credentials
|
147
137
|
".env.*",
|
148
138
|
"*.pem",
|
@@ -151,14 +141,12 @@ class ProjectOrganizer:
|
|
151
141
|
"*.crt",
|
152
142
|
".secrets/",
|
153
143
|
"credentials/",
|
154
|
-
|
155
144
|
# Claude MPM specific
|
156
145
|
".claude-mpm/*.log",
|
157
146
|
".claude-mpm/sessions/",
|
158
147
|
".claude-mpm/tmp/",
|
159
148
|
".claude/sessions/",
|
160
149
|
"*.mpm.tmp",
|
161
|
-
|
162
150
|
# Backup files
|
163
151
|
"*.bak",
|
164
152
|
"*.backup",
|
@@ -197,28 +185,34 @@ class ProjectOrganizer:
|
|
197
185
|
for dir_name, description in self.STANDARD_DIRECTORIES.items():
|
198
186
|
dir_path = self.project_path / dir_name
|
199
187
|
if dir_path.exists():
|
200
|
-
report["exists"].append(
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
188
|
+
report["exists"].append(
|
189
|
+
{
|
190
|
+
"path": dir_name,
|
191
|
+
"description": description,
|
192
|
+
"is_directory": dir_path.is_dir(),
|
193
|
+
}
|
194
|
+
)
|
205
195
|
else:
|
206
|
-
report["missing"].append(
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
196
|
+
report["missing"].append(
|
197
|
+
{
|
198
|
+
"path": dir_name,
|
199
|
+
"description": description,
|
200
|
+
"required": self._is_required_directory(dir_name, project_type),
|
201
|
+
}
|
202
|
+
)
|
211
203
|
|
212
204
|
# Check project-type specific directories
|
213
205
|
if project_type and project_type in self.PROJECT_STRUCTURES:
|
214
206
|
for dir_name in self.PROJECT_STRUCTURES[project_type]:
|
215
207
|
dir_path = self.project_path / dir_name
|
216
208
|
if not dir_path.exists():
|
217
|
-
report["missing"].append(
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
209
|
+
report["missing"].append(
|
210
|
+
{
|
211
|
+
"path": dir_name,
|
212
|
+
"description": f"{project_type} specific directory",
|
213
|
+
"required": False,
|
214
|
+
}
|
215
|
+
)
|
222
216
|
|
223
217
|
# Check for common issues
|
224
218
|
report["issues"] = self._check_common_issues()
|
@@ -229,7 +223,9 @@ class ProjectOrganizer:
|
|
229
223
|
self.structure_report = report
|
230
224
|
return report
|
231
225
|
|
232
|
-
def _is_required_directory(
|
226
|
+
def _is_required_directory(
|
227
|
+
self, dir_name: str, project_type: Optional[str]
|
228
|
+
) -> bool:
|
233
229
|
"""Determine if a directory is required for the project."""
|
234
230
|
# Always required directories
|
235
231
|
always_required = {"tmp", "scripts", "docs"}
|
@@ -253,34 +249,43 @@ class ProjectOrganizer:
|
|
253
249
|
root_files = list(self.project_path.glob("*.py"))
|
254
250
|
test_files_in_root = [f for f in root_files if "test" in f.name.lower()]
|
255
251
|
if test_files_in_root:
|
256
|
-
issues.append(
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
252
|
+
issues.append(
|
253
|
+
{
|
254
|
+
"type": "misplaced_tests",
|
255
|
+
"description": "Test files found in project root",
|
256
|
+
"files": [str(f.name) for f in test_files_in_root],
|
257
|
+
"recommendation": "Move test files to tests/ directory",
|
258
|
+
}
|
259
|
+
)
|
262
260
|
|
263
261
|
script_files_in_root = [
|
264
|
-
f
|
262
|
+
f
|
263
|
+
for f in root_files
|
265
264
|
if f.name.lower().endswith((".sh", ".bash", ".py"))
|
266
265
|
and not f.name.startswith(".")
|
267
266
|
and f.name not in ["setup.py", "pyproject.toml"]
|
268
267
|
]
|
269
268
|
if script_files_in_root:
|
270
|
-
issues.append(
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
269
|
+
issues.append(
|
270
|
+
{
|
271
|
+
"type": "misplaced_scripts",
|
272
|
+
"description": "Script files found in project root",
|
273
|
+
"files": [
|
274
|
+
str(f.name) for f in script_files_in_root[:5]
|
275
|
+
], # Limit to 5
|
276
|
+
"recommendation": "Move scripts to scripts/ directory",
|
277
|
+
}
|
278
|
+
)
|
276
279
|
|
277
280
|
# Check for missing .gitignore
|
278
281
|
if not self.gitignore_path.exists():
|
279
|
-
issues.append(
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
282
|
+
issues.append(
|
283
|
+
{
|
284
|
+
"type": "missing_gitignore",
|
285
|
+
"description": "No .gitignore file found",
|
286
|
+
"recommendation": "Create .gitignore with standard patterns",
|
287
|
+
}
|
288
|
+
)
|
284
289
|
else:
|
285
290
|
# Check .gitignore completeness
|
286
291
|
gitignore_content = self.gitignore_path.read_text()
|
@@ -290,12 +295,14 @@ class ProjectOrganizer:
|
|
290
295
|
missing_patterns.append(pattern)
|
291
296
|
|
292
297
|
if missing_patterns:
|
293
|
-
issues.append(
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
298
|
+
issues.append(
|
299
|
+
{
|
300
|
+
"type": "incomplete_gitignore",
|
301
|
+
"description": "Common patterns missing from .gitignore",
|
302
|
+
"patterns": missing_patterns,
|
303
|
+
"recommendation": "Update .gitignore with missing patterns",
|
304
|
+
}
|
305
|
+
)
|
299
306
|
|
300
307
|
# Check for large files that should be in tmp
|
301
308
|
large_files = []
|
@@ -305,20 +312,24 @@ class ProjectOrganizer:
|
|
305
312
|
size_mb = file.stat().st_size / (1024 * 1024)
|
306
313
|
if size_mb > 10: # Files larger than 10MB
|
307
314
|
if "tmp" not in str(file) and "node_modules" not in str(file):
|
308
|
-
large_files.append(
|
309
|
-
|
310
|
-
|
311
|
-
|
315
|
+
large_files.append(
|
316
|
+
{
|
317
|
+
"path": str(file.relative_to(self.project_path)),
|
318
|
+
"size_mb": round(size_mb, 2),
|
319
|
+
}
|
320
|
+
)
|
312
321
|
except (OSError, PermissionError):
|
313
322
|
continue
|
314
323
|
|
315
324
|
if large_files:
|
316
|
-
issues.append(
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
325
|
+
issues.append(
|
326
|
+
{
|
327
|
+
"type": "large_files",
|
328
|
+
"description": "Large files outside tmp/ directory",
|
329
|
+
"files": large_files[:5], # Limit to 5
|
330
|
+
"recommendation": "Consider moving large files to tmp/ or adding to .gitignore",
|
331
|
+
}
|
332
|
+
)
|
322
333
|
|
323
334
|
return issues
|
324
335
|
|
@@ -433,7 +444,11 @@ This directory is used for {description.lower()}.
|
|
433
444
|
existing_patterns = set()
|
434
445
|
if self.gitignore_path.exists():
|
435
446
|
content = self.gitignore_path.read_text()
|
436
|
-
existing_patterns = set(
|
447
|
+
existing_patterns = set(
|
448
|
+
line.strip()
|
449
|
+
for line in content.splitlines()
|
450
|
+
if line.strip() and not line.startswith("#")
|
451
|
+
)
|
437
452
|
else:
|
438
453
|
content = ""
|
439
454
|
|
@@ -460,15 +475,16 @@ This directory is used for {description.lower()}.
|
|
460
475
|
self.gitignore_path.write_text(content)
|
461
476
|
logger.info(f"Updated .gitignore with {len(missing)} patterns")
|
462
477
|
return True
|
463
|
-
|
464
|
-
|
465
|
-
return False
|
478
|
+
logger.info(".gitignore already contains all standard patterns")
|
479
|
+
return False
|
466
480
|
|
467
481
|
except Exception as e:
|
468
482
|
logger.error(f"Failed to update .gitignore: {e}")
|
469
483
|
return False
|
470
484
|
|
471
|
-
def organize_misplaced_files(
|
485
|
+
def organize_misplaced_files(
|
486
|
+
self, dry_run: bool = True, auto_safe: bool = True
|
487
|
+
) -> Dict:
|
472
488
|
"""Organize misplaced files into proper directories.
|
473
489
|
|
474
490
|
Args:
|
@@ -481,13 +497,35 @@ This directory is used for {description.lower()}.
|
|
481
497
|
|
482
498
|
# Files that should never be moved from root
|
483
499
|
protected_root_files = {
|
484
|
-
"setup.py",
|
485
|
-
"
|
486
|
-
"
|
487
|
-
".
|
488
|
-
"
|
489
|
-
"
|
490
|
-
".
|
500
|
+
"setup.py",
|
501
|
+
"pyproject.toml",
|
502
|
+
"package.json",
|
503
|
+
"package-lock.json",
|
504
|
+
"requirements.txt",
|
505
|
+
"Pipfile",
|
506
|
+
"Pipfile.lock",
|
507
|
+
"poetry.lock",
|
508
|
+
"Makefile",
|
509
|
+
"makefile",
|
510
|
+
"Dockerfile",
|
511
|
+
"docker-compose.yml",
|
512
|
+
".gitignore",
|
513
|
+
".gitattributes",
|
514
|
+
"LICENSE",
|
515
|
+
"README.md",
|
516
|
+
"README.rst",
|
517
|
+
"CHANGELOG.md",
|
518
|
+
"CONTRIBUTING.md",
|
519
|
+
"CODE_OF_CONDUCT.md",
|
520
|
+
"CLAUDE.md",
|
521
|
+
"CODE.md",
|
522
|
+
"DEVELOPER.md",
|
523
|
+
"STRUCTURE.md",
|
524
|
+
"OPS.md",
|
525
|
+
".env.example",
|
526
|
+
".env.sample",
|
527
|
+
"VERSION",
|
528
|
+
"BUILD_NUMBER",
|
491
529
|
}
|
492
530
|
|
493
531
|
# Scan root directory for misplaced files
|
@@ -503,7 +541,9 @@ This directory is used for {description.lower()}.
|
|
503
541
|
|
504
542
|
# Test files (high confidence)
|
505
543
|
if "test" in file_lower and file.suffix == ".py":
|
506
|
-
if file_lower.startswith("test_") or file_lower.endswith(
|
544
|
+
if file_lower.startswith("test_") or file_lower.endswith(
|
545
|
+
"_test.py"
|
546
|
+
):
|
507
547
|
target_dir = "tests"
|
508
548
|
confidence = "high"
|
509
549
|
reason = "Test file pattern detected"
|
@@ -513,7 +553,10 @@ This directory is used for {description.lower()}.
|
|
513
553
|
target_dir = "scripts"
|
514
554
|
confidence = "high"
|
515
555
|
reason = "Shell script file"
|
516
|
-
elif file.suffix == ".py" and any(
|
556
|
+
elif file.suffix == ".py" and any(
|
557
|
+
pattern in file_lower
|
558
|
+
for pattern in ["script", "run", "cli", "tool"]
|
559
|
+
):
|
517
560
|
target_dir = "scripts"
|
518
561
|
confidence = "medium"
|
519
562
|
reason = "Python script pattern detected"
|
@@ -529,15 +572,23 @@ This directory is used for {description.lower()}.
|
|
529
572
|
reason = "Temporary file pattern"
|
530
573
|
|
531
574
|
# Documentation files (medium confidence)
|
532
|
-
elif
|
533
|
-
|
575
|
+
elif (
|
576
|
+
file.suffix in [".md", ".rst", ".txt"]
|
577
|
+
and file.name not in protected_root_files
|
578
|
+
):
|
579
|
+
if any(
|
580
|
+
pattern in file_lower
|
581
|
+
for pattern in ["notes", "draft", "todo", "spec", "design"]
|
582
|
+
):
|
534
583
|
target_dir = "docs"
|
535
584
|
confidence = "medium"
|
536
585
|
reason = "Documentation file pattern"
|
537
586
|
|
538
587
|
# Data files (medium confidence)
|
539
588
|
elif file.suffix in [".csv", ".json", ".xml", ".yaml", ".yml"]:
|
540
|
-
if file.suffix in [".yaml", ".yml"] and any(
|
589
|
+
if file.suffix in [".yaml", ".yml"] and any(
|
590
|
+
pattern in file_lower for pattern in ["config", "settings"]
|
591
|
+
):
|
541
592
|
# Config files might belong in root
|
542
593
|
confidence = "low"
|
543
594
|
else:
|
@@ -546,7 +597,9 @@ This directory is used for {description.lower()}.
|
|
546
597
|
reason = "Data file"
|
547
598
|
|
548
599
|
# Build artifacts (high confidence)
|
549
|
-
elif file.suffix in [".whl", ".tar.gz", ".zip"] and "dist" not in str(
|
600
|
+
elif file.suffix in [".whl", ".tar.gz", ".zip"] and "dist" not in str(
|
601
|
+
file.parent
|
602
|
+
):
|
550
603
|
target_dir = "dist"
|
551
604
|
confidence = "high"
|
552
605
|
reason = "Build artifact"
|
@@ -559,16 +612,24 @@ This directory is used for {description.lower()}.
|
|
559
612
|
|
560
613
|
if target_dir:
|
561
614
|
# Check if we should move based on confidence and auto_safe setting
|
562
|
-
should_move = (
|
615
|
+
should_move = (
|
616
|
+
(confidence == "high")
|
617
|
+
if auto_safe
|
618
|
+
else (confidence in ["high", "medium"])
|
619
|
+
)
|
563
620
|
|
564
621
|
if should_move:
|
565
622
|
target_path = self.project_path / target_dir / file.name
|
566
|
-
moves.append(
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
623
|
+
moves.append(
|
624
|
+
{
|
625
|
+
"source": str(file.relative_to(self.project_path)),
|
626
|
+
"target": str(
|
627
|
+
target_path.relative_to(self.project_path)
|
628
|
+
),
|
629
|
+
"reason": reason,
|
630
|
+
"confidence": confidence,
|
631
|
+
}
|
632
|
+
)
|
572
633
|
|
573
634
|
if not dry_run:
|
574
635
|
try:
|
@@ -576,20 +637,21 @@ This directory is used for {description.lower()}.
|
|
576
637
|
target_path.parent.mkdir(parents=True, exist_ok=True)
|
577
638
|
# Move file
|
578
639
|
file.rename(target_path)
|
579
|
-
logger.info(
|
640
|
+
logger.info(
|
641
|
+
f"Moved {file.name} to {target_dir}/ ({reason})"
|
642
|
+
)
|
580
643
|
except Exception as e:
|
581
|
-
errors.append({
|
582
|
-
"file": str(file.name),
|
583
|
-
"error": str(e)
|
584
|
-
})
|
644
|
+
errors.append({"file": str(file.name), "error": str(e)})
|
585
645
|
logger.error(f"Failed to move {file.name}: {e}")
|
586
646
|
else:
|
587
|
-
skipped.append(
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
647
|
+
skipped.append(
|
648
|
+
{
|
649
|
+
"file": str(file.name),
|
650
|
+
"suggested_dir": target_dir,
|
651
|
+
"confidence": confidence,
|
652
|
+
"reason": "Low confidence move - skipped (use --no-auto-safe to include)",
|
653
|
+
}
|
654
|
+
)
|
593
655
|
|
594
656
|
# Also check for deeply nested test files that should be in tests/
|
595
657
|
if not auto_safe: # Only in non-safe mode
|
@@ -598,16 +660,21 @@ This directory is used for {description.lower()}.
|
|
598
660
|
if "tests" in test_file.parts or "test" in test_file.parts:
|
599
661
|
continue
|
600
662
|
# Skip if in node_modules or venv
|
601
|
-
if any(
|
663
|
+
if any(
|
664
|
+
part in test_file.parts
|
665
|
+
for part in ["node_modules", "venv", ".venv", "site-packages"]
|
666
|
+
):
|
602
667
|
continue
|
603
668
|
|
604
669
|
target_path = self.project_path / "tests" / test_file.name
|
605
|
-
moves.append(
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
670
|
+
moves.append(
|
671
|
+
{
|
672
|
+
"source": str(test_file.relative_to(self.project_path)),
|
673
|
+
"target": str(target_path.relative_to(self.project_path)),
|
674
|
+
"reason": "Test file found outside tests directory",
|
675
|
+
"confidence": "medium",
|
676
|
+
}
|
677
|
+
)
|
611
678
|
|
612
679
|
if not dry_run:
|
613
680
|
try:
|
@@ -615,10 +682,12 @@ This directory is used for {description.lower()}.
|
|
615
682
|
test_file.rename(target_path)
|
616
683
|
logger.info(f"Moved {test_file.name} to tests/")
|
617
684
|
except Exception as e:
|
618
|
-
errors.append(
|
619
|
-
|
620
|
-
|
621
|
-
|
685
|
+
errors.append(
|
686
|
+
{
|
687
|
+
"file": str(test_file.relative_to(self.project_path)),
|
688
|
+
"error": str(e),
|
689
|
+
}
|
690
|
+
)
|
622
691
|
|
623
692
|
return {
|
624
693
|
"dry_run": dry_run,
|
@@ -699,7 +768,13 @@ This directory is used for {description.lower()}.
|
|
699
768
|
doc += "## .gitignore Configuration\n\n"
|
700
769
|
if self.gitignore_path.exists():
|
701
770
|
gitignore_content = self.gitignore_path.read_text()
|
702
|
-
critical_patterns = [
|
771
|
+
critical_patterns = [
|
772
|
+
"tmp/",
|
773
|
+
"__pycache__",
|
774
|
+
".env",
|
775
|
+
"*.log",
|
776
|
+
".claude-mpm/cache/",
|
777
|
+
]
|
703
778
|
doc += "### Critical Patterns Status:\n"
|
704
779
|
for pattern in critical_patterns:
|
705
780
|
status = "✅" if pattern in gitignore_content else "❌"
|
@@ -741,14 +816,14 @@ This directory is used for {description.lower()}.
|
|
741
816
|
"misplaced_files": organize_result,
|
742
817
|
"gitignore": {
|
743
818
|
"exists": self.gitignore_path.exists(),
|
744
|
-
"patterns_status": {}
|
819
|
+
"patterns_status": {},
|
745
820
|
},
|
746
821
|
"statistics": {
|
747
822
|
"total_directories": 0,
|
748
823
|
"total_files": 0,
|
749
824
|
"misplaced_files": len(organize_result.get("proposed_moves", [])),
|
750
|
-
"structure_score": validation.get("score", 0)
|
751
|
-
}
|
825
|
+
"structure_score": validation.get("score", 0),
|
826
|
+
},
|
752
827
|
}
|
753
828
|
|
754
829
|
# Check directory status
|
@@ -758,7 +833,7 @@ This directory is used for {description.lower()}.
|
|
758
833
|
"exists": dir_path.exists(),
|
759
834
|
"description": description,
|
760
835
|
"file_count": len(list(dir_path.glob("*"))) if dir_path.exists() else 0,
|
761
|
-
"is_directory": dir_path.is_dir() if dir_path.exists() else None
|
836
|
+
"is_directory": dir_path.is_dir() if dir_path.exists() else None,
|
762
837
|
}
|
763
838
|
if dir_path.exists():
|
764
839
|
report["statistics"]["total_directories"] += 1
|
@@ -766,9 +841,17 @@ This directory is used for {description.lower()}.
|
|
766
841
|
# Check gitignore patterns
|
767
842
|
if self.gitignore_path.exists():
|
768
843
|
gitignore_content = self.gitignore_path.read_text()
|
769
|
-
critical_patterns = [
|
844
|
+
critical_patterns = [
|
845
|
+
"tmp/",
|
846
|
+
"__pycache__",
|
847
|
+
".env",
|
848
|
+
"*.log",
|
849
|
+
".claude-mpm/cache/",
|
850
|
+
]
|
770
851
|
for pattern in critical_patterns:
|
771
|
-
report["gitignore"]["patterns_status"][pattern] =
|
852
|
+
report["gitignore"]["patterns_status"][pattern] = (
|
853
|
+
pattern in gitignore_content
|
854
|
+
)
|
772
855
|
|
773
856
|
# Count total files
|
774
857
|
for item in self.project_path.rglob("*"):
|
@@ -777,7 +860,9 @@ This directory is used for {description.lower()}.
|
|
777
860
|
|
778
861
|
return report
|
779
862
|
|
780
|
-
def ensure_project_ready(
|
863
|
+
def ensure_project_ready(
|
864
|
+
self, auto_organize: bool = False, safe_mode: bool = True
|
865
|
+
) -> Tuple[bool, List[str]]:
|
781
866
|
"""Ensure project is ready for Claude MPM usage.
|
782
867
|
|
783
868
|
Args:
|
@@ -799,7 +884,9 @@ This directory is used for {description.lower()}.
|
|
799
884
|
tmp_dir = self.project_path / "tmp"
|
800
885
|
if not tmp_dir.exists():
|
801
886
|
tmp_dir.mkdir(parents=True, exist_ok=True)
|
802
|
-
self._add_directory_readme(
|
887
|
+
self._add_directory_readme(
|
888
|
+
tmp_dir, "Temporary files, test outputs, and experiments"
|
889
|
+
)
|
803
890
|
actions_taken.append("Created tmp/ directory with README")
|
804
891
|
|
805
892
|
# Update .gitignore with comprehensive patterns
|
@@ -807,11 +894,15 @@ This directory is used for {description.lower()}.
|
|
807
894
|
actions_taken.append("Updated .gitignore with comprehensive patterns")
|
808
895
|
|
809
896
|
# Check if organization is needed
|
810
|
-
organize_result = self.organize_misplaced_files(
|
897
|
+
organize_result = self.organize_misplaced_files(
|
898
|
+
dry_run=True, auto_safe=safe_mode
|
899
|
+
)
|
811
900
|
if organize_result["proposed_moves"]:
|
812
901
|
if auto_organize:
|
813
902
|
# Perform the organization
|
814
|
-
actual_result = self.organize_misplaced_files(
|
903
|
+
actual_result = self.organize_misplaced_files(
|
904
|
+
dry_run=False, auto_safe=safe_mode
|
905
|
+
)
|
815
906
|
if actual_result["completed_moves"]:
|
816
907
|
actions_taken.append(
|
817
908
|
f"Organized {len(actual_result['completed_moves'])} files into proper directories"
|
@@ -832,7 +923,10 @@ This directory is used for {description.lower()}.
|
|
832
923
|
# Check for remaining issues
|
833
924
|
if self.structure_report.get("issues"):
|
834
925
|
for issue in self.structure_report["issues"]:
|
835
|
-
if issue["type"] not in [
|
926
|
+
if issue["type"] not in [
|
927
|
+
"misplaced_scripts",
|
928
|
+
"misplaced_tests",
|
929
|
+
]: # These may be handled
|
836
930
|
issues_found.append(issue["description"])
|
837
931
|
|
838
932
|
# Generate structure validation report
|
@@ -870,7 +964,9 @@ This directory is used for {description.lower()}.
|
|
870
964
|
critical_patterns = ["tmp/", "__pycache__", ".env", "*.log"]
|
871
965
|
for pattern in critical_patterns:
|
872
966
|
if pattern not in gitignore_content:
|
873
|
-
validation["warnings"].append(
|
967
|
+
validation["warnings"].append(
|
968
|
+
f"Missing gitignore pattern: {pattern}"
|
969
|
+
)
|
874
970
|
validation["score"] -= 2
|
875
971
|
|
876
972
|
# Check for files in wrong locations
|
@@ -878,15 +974,13 @@ This directory is used for {description.lower()}.
|
|
878
974
|
misplaced_count = 0
|
879
975
|
for file in root_files:
|
880
976
|
if file.is_file():
|
881
|
-
if "test" in file.name.lower() and file.suffix == ".py":
|
882
|
-
misplaced_count += 1
|
883
|
-
elif file.suffix in [".sh", ".bash"] and file.name not in ["Makefile"]:
|
884
|
-
misplaced_count += 1
|
885
|
-
elif file.suffix in [".log", ".tmp", ".cache"]:
|
977
|
+
if ("test" in file.name.lower() and file.suffix == ".py") or (file.suffix in [".sh", ".bash"] and file.name not in ["Makefile"]) or file.suffix in [".log", ".tmp", ".cache"]:
|
886
978
|
misplaced_count += 1
|
887
979
|
|
888
980
|
if misplaced_count > 0:
|
889
|
-
validation["warnings"].append(
|
981
|
+
validation["warnings"].append(
|
982
|
+
f"{misplaced_count} files potentially misplaced in root"
|
983
|
+
)
|
890
984
|
validation["score"] -= min(misplaced_count * 2, 20)
|
891
985
|
|
892
986
|
# Score interpretation
|
@@ -901,4 +995,4 @@ This directory is used for {description.lower()}.
|
|
901
995
|
else:
|
902
996
|
validation["grade"] = "F - Poor structure"
|
903
997
|
|
904
|
-
return validation
|
998
|
+
return validation
|