claude-mpm 4.4.3__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.
Files changed (118) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/agent_loader.py +3 -2
  3. claude_mpm/agents/agent_loader_integration.py +2 -1
  4. claude_mpm/agents/async_agent_loader.py +2 -2
  5. claude_mpm/agents/base_agent_loader.py +2 -2
  6. claude_mpm/agents/frontmatter_validator.py +1 -0
  7. claude_mpm/agents/system_agent_config.py +2 -1
  8. claude_mpm/cli/commands/doctor.py +44 -5
  9. claude_mpm/cli/commands/mpm_init.py +116 -62
  10. claude_mpm/cli/parsers/configure_parser.py +3 -1
  11. claude_mpm/cli/startup_logging.py +1 -3
  12. claude_mpm/config/agent_config.py +1 -1
  13. claude_mpm/config/paths.py +2 -1
  14. claude_mpm/core/agent_name_normalizer.py +1 -0
  15. claude_mpm/core/config.py +2 -1
  16. claude_mpm/core/config_aliases.py +2 -1
  17. claude_mpm/core/file_utils.py +0 -1
  18. claude_mpm/core/framework/__init__.py +6 -6
  19. claude_mpm/core/framework/formatters/__init__.py +2 -2
  20. claude_mpm/core/framework/formatters/capability_generator.py +19 -8
  21. claude_mpm/core/framework/formatters/content_formatter.py +8 -3
  22. claude_mpm/core/framework/formatters/context_generator.py +7 -3
  23. claude_mpm/core/framework/loaders/__init__.py +3 -3
  24. claude_mpm/core/framework/loaders/agent_loader.py +7 -3
  25. claude_mpm/core/framework/loaders/file_loader.py +16 -6
  26. claude_mpm/core/framework/loaders/instruction_loader.py +16 -6
  27. claude_mpm/core/framework/loaders/packaged_loader.py +36 -12
  28. claude_mpm/core/framework/processors/__init__.py +2 -2
  29. claude_mpm/core/framework/processors/memory_processor.py +14 -6
  30. claude_mpm/core/framework/processors/metadata_processor.py +5 -5
  31. claude_mpm/core/framework/processors/template_processor.py +12 -6
  32. claude_mpm/core/framework_loader.py +44 -20
  33. claude_mpm/core/log_manager.py +2 -1
  34. claude_mpm/core/tool_access_control.py +1 -0
  35. claude_mpm/core/unified_agent_registry.py +2 -1
  36. claude_mpm/core/unified_paths.py +1 -0
  37. claude_mpm/experimental/cli_enhancements.py +1 -0
  38. claude_mpm/hooks/base_hook.py +1 -0
  39. claude_mpm/hooks/instruction_reinforcement.py +1 -0
  40. claude_mpm/hooks/kuzu_memory_hook.py +20 -13
  41. claude_mpm/hooks/validation_hooks.py +1 -1
  42. claude_mpm/scripts/mpm_doctor.py +1 -0
  43. claude_mpm/services/agents/loading/agent_profile_loader.py +1 -1
  44. claude_mpm/services/agents/loading/base_agent_manager.py +1 -1
  45. claude_mpm/services/agents/loading/framework_agent_loader.py +1 -1
  46. claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -0
  47. claude_mpm/services/agents/management/agent_management_service.py +1 -1
  48. claude_mpm/services/agents/memory/memory_categorization_service.py +0 -1
  49. claude_mpm/services/agents/memory/memory_file_service.py +6 -2
  50. claude_mpm/services/agents/memory/memory_format_service.py +0 -1
  51. claude_mpm/services/agents/registry/deployed_agent_discovery.py +1 -1
  52. claude_mpm/services/async_session_logger.py +1 -1
  53. claude_mpm/services/claude_session_logger.py +1 -0
  54. claude_mpm/services/core/path_resolver.py +1 -0
  55. claude_mpm/services/diagnostics/checks/__init__.py +2 -0
  56. claude_mpm/services/diagnostics/checks/installation_check.py +126 -25
  57. claude_mpm/services/diagnostics/checks/mcp_services_check.py +399 -0
  58. claude_mpm/services/diagnostics/diagnostic_runner.py +3 -0
  59. claude_mpm/services/diagnostics/doctor_reporter.py +259 -32
  60. claude_mpm/services/event_bus/direct_relay.py +2 -1
  61. claude_mpm/services/event_bus/event_bus.py +1 -0
  62. claude_mpm/services/event_bus/relay.py +3 -2
  63. claude_mpm/services/framework_claude_md_generator/content_assembler.py +1 -1
  64. claude_mpm/services/infrastructure/daemon_manager.py +1 -1
  65. claude_mpm/services/mcp_config_manager.py +10 -10
  66. claude_mpm/services/mcp_gateway/core/process_pool.py +62 -23
  67. claude_mpm/services/mcp_gateway/tools/__init__.py +6 -5
  68. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +3 -1
  69. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +16 -31
  70. claude_mpm/services/memory/cache/simple_cache.py +1 -1
  71. claude_mpm/services/project/archive_manager.py +159 -96
  72. claude_mpm/services/project/documentation_manager.py +64 -45
  73. claude_mpm/services/project/enhanced_analyzer.py +132 -89
  74. claude_mpm/services/project/project_organizer.py +225 -131
  75. claude_mpm/services/response_tracker.py +1 -1
  76. claude_mpm/services/socketio/server/eventbus_integration.py +1 -1
  77. claude_mpm/services/unified/__init__.py +1 -1
  78. claude_mpm/services/unified/analyzer_strategies/__init__.py +3 -3
  79. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +97 -53
  80. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +81 -40
  81. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +277 -178
  82. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +196 -112
  83. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +83 -49
  84. claude_mpm/services/unified/config_strategies/__init__.py +111 -126
  85. claude_mpm/services/unified/config_strategies/config_schema.py +157 -111
  86. claude_mpm/services/unified/config_strategies/context_strategy.py +91 -89
  87. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +183 -173
  88. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +160 -152
  89. claude_mpm/services/unified/config_strategies/unified_config_service.py +124 -112
  90. claude_mpm/services/unified/config_strategies/validation_strategy.py +298 -259
  91. claude_mpm/services/unified/deployment_strategies/__init__.py +7 -7
  92. claude_mpm/services/unified/deployment_strategies/base.py +24 -28
  93. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +168 -88
  94. claude_mpm/services/unified/deployment_strategies/local.py +49 -34
  95. claude_mpm/services/unified/deployment_strategies/utils.py +39 -43
  96. claude_mpm/services/unified/deployment_strategies/vercel.py +30 -24
  97. claude_mpm/services/unified/interfaces.py +0 -26
  98. claude_mpm/services/unified/migration.py +17 -40
  99. claude_mpm/services/unified/strategies.py +9 -26
  100. claude_mpm/services/unified/unified_analyzer.py +48 -44
  101. claude_mpm/services/unified/unified_config.py +21 -19
  102. claude_mpm/services/unified/unified_deployment.py +21 -26
  103. claude_mpm/storage/state_storage.py +1 -0
  104. claude_mpm/utils/agent_dependency_loader.py +18 -6
  105. claude_mpm/utils/common.py +14 -12
  106. claude_mpm/utils/database_connector.py +15 -12
  107. claude_mpm/utils/error_handler.py +1 -0
  108. claude_mpm/utils/log_cleanup.py +1 -0
  109. claude_mpm/utils/path_operations.py +1 -0
  110. claude_mpm/utils/session_logging.py +1 -1
  111. claude_mpm/utils/subprocess_utils.py +1 -0
  112. claude_mpm/validation/agent_validator.py +1 -1
  113. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/METADATA +9 -3
  114. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/RECORD +118 -117
  115. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/WHEEL +0 -0
  116. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/entry_points.txt +0 -0
  117. {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/licenses/LICENSE +0 -0
  118. {claude_mpm-4.4.3.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, Set, Tuple
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
- "path": dir_name,
202
- "description": description,
203
- "is_directory": dir_path.is_dir(),
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
- "path": dir_name,
208
- "description": description,
209
- "required": self._is_required_directory(dir_name, project_type),
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
- "path": dir_name,
219
- "description": f"{project_type} specific directory",
220
- "required": False,
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(self, dir_name: str, project_type: Optional[str]) -> bool:
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
- "type": "misplaced_tests",
258
- "description": "Test files found in project root",
259
- "files": [str(f.name) for f in test_files_in_root],
260
- "recommendation": "Move test files to tests/ directory",
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 for f in root_files
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
- "type": "misplaced_scripts",
272
- "description": "Script files found in project root",
273
- "files": [str(f.name) for f in script_files_in_root[:5]], # Limit to 5
274
- "recommendation": "Move scripts to scripts/ directory",
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
- "type": "missing_gitignore",
281
- "description": "No .gitignore file found",
282
- "recommendation": "Create .gitignore with standard patterns",
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
- "type": "incomplete_gitignore",
295
- "description": "Common patterns missing from .gitignore",
296
- "patterns": missing_patterns,
297
- "recommendation": "Update .gitignore with missing patterns",
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
- "path": str(file.relative_to(self.project_path)),
310
- "size_mb": round(size_mb, 2),
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
- "type": "large_files",
318
- "description": "Large files outside tmp/ directory",
319
- "files": large_files[:5], # Limit to 5
320
- "recommendation": "Consider moving large files to tmp/ or adding to .gitignore",
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(line.strip() for line in content.splitlines() if line.strip() and not line.startswith("#"))
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
- else:
464
- logger.info(".gitignore already contains all standard patterns")
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(self, dry_run: bool = True, auto_safe: bool = True) -> Dict:
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", "pyproject.toml", "package.json", "package-lock.json",
485
- "requirements.txt", "Pipfile", "Pipfile.lock", "poetry.lock",
486
- "Makefile", "makefile", "Dockerfile", "docker-compose.yml",
487
- ".gitignore", ".gitattributes", "LICENSE", "README.md", "README.rst",
488
- "CHANGELOG.md", "CONTRIBUTING.md", "CODE_OF_CONDUCT.md",
489
- "CLAUDE.md", "CODE.md", "DEVELOPER.md", "STRUCTURE.md", "OPS.md",
490
- ".env.example", ".env.sample", "VERSION", "BUILD_NUMBER"
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("_test.py"):
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(pattern in file_lower for pattern in ["script", "run", "cli", "tool"]):
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 file.suffix in [".md", ".rst", ".txt"] and file.name not in protected_root_files:
533
- if any(pattern in file_lower for pattern in ["notes", "draft", "todo", "spec", "design"]):
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(pattern in file_lower for pattern in ["config", "settings"]):
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(file.parent):
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 = (confidence == "high") if auto_safe else (confidence in ["high", "medium"])
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
- "source": str(file.relative_to(self.project_path)),
568
- "target": str(target_path.relative_to(self.project_path)),
569
- "reason": reason,
570
- "confidence": confidence,
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(f"Moved {file.name} to {target_dir}/ ({reason})")
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
- "file": str(file.name),
589
- "suggested_dir": target_dir,
590
- "confidence": confidence,
591
- "reason": f"Low confidence move - skipped (use --no-auto-safe to include)"
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(part in test_file.parts for part in ["node_modules", "venv", ".venv", "site-packages"]):
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
- "source": str(test_file.relative_to(self.project_path)),
607
- "target": str(target_path.relative_to(self.project_path)),
608
- "reason": "Test file found outside tests directory",
609
- "confidence": "medium",
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
- "file": str(test_file.relative_to(self.project_path)),
620
- "error": str(e)
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 = ["tmp/", "__pycache__", ".env", "*.log", ".claude-mpm/cache/"]
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 = ["tmp/", "__pycache__", ".env", "*.log", ".claude-mpm/cache/"]
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] = pattern in gitignore_content
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(self, auto_organize: bool = False, safe_mode: bool = True) -> Tuple[bool, List[str]]:
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(tmp_dir, "Temporary files, test outputs, and experiments")
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(dry_run=True, auto_safe=safe_mode)
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(dry_run=False, auto_safe=safe_mode)
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 ["misplaced_scripts", "misplaced_tests"]: # These may be handled
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(f"Missing gitignore pattern: {pattern}")
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(f"{misplaced_count} files potentially misplaced in root")
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