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
@@ -18,15 +18,15 @@ Created: 2025-01-26
18
18
 
19
19
  import difflib
20
20
  import hashlib
21
- import json
22
21
  import re
23
22
  from datetime import datetime
24
23
  from pathlib import Path
25
- from typing import Dict, List, Optional, Set, Tuple
24
+ from typing import Dict, List, Tuple
26
25
 
27
26
  from rich.console import Console
28
27
 
29
28
  from claude_mpm.core.logging_utils import get_logger
29
+
30
30
  logger = get_logger(__name__)
31
31
  console = Console()
32
32
 
@@ -70,7 +70,9 @@ class DocumentationManager:
70
70
  if self.claude_md_path.exists():
71
71
  self.existing_content = self.claude_md_path.read_text(encoding="utf-8")
72
72
  self.content_hash = hashlib.md5(self.existing_content.encode()).hexdigest()
73
- logger.info(f"Loaded existing CLAUDE.md ({len(self.existing_content)} chars)")
73
+ logger.info(
74
+ f"Loaded existing CLAUDE.md ({len(self.existing_content)} chars)"
75
+ )
74
76
 
75
77
  def has_existing_documentation(self) -> bool:
76
78
  """Check if project has existing CLAUDE.md."""
@@ -118,15 +120,17 @@ class DocumentationManager:
118
120
  if line.startswith("#"):
119
121
  # Save previous section if exists
120
122
  if current_section:
121
- sections.append({
122
- "title": current_section,
123
- "level": current_level,
124
- "start_line": section_start,
125
- "end_line": i - 1,
126
- "content_preview": self._get_content_preview(
127
- lines[section_start:i]
128
- ),
129
- })
123
+ sections.append(
124
+ {
125
+ "title": current_section,
126
+ "level": current_level,
127
+ "start_line": section_start,
128
+ "end_line": i - 1,
129
+ "content_preview": self._get_content_preview(
130
+ lines[section_start:i]
131
+ ),
132
+ }
133
+ )
130
134
 
131
135
  # Parse new section
132
136
  level = len(line.split()[0])
@@ -137,13 +141,15 @@ class DocumentationManager:
137
141
 
138
142
  # Add last section
139
143
  if current_section:
140
- sections.append({
141
- "title": current_section,
142
- "level": current_level,
143
- "start_line": section_start,
144
- "end_line": len(lines) - 1,
145
- "content_preview": self._get_content_preview(lines[section_start:]),
146
- })
144
+ sections.append(
145
+ {
146
+ "title": current_section,
147
+ "level": current_level,
148
+ "start_line": section_start,
149
+ "end_line": len(lines) - 1,
150
+ "content_preview": self._get_content_preview(lines[section_start:]),
151
+ }
152
+ )
147
153
 
148
154
  return sections
149
155
 
@@ -160,13 +166,18 @@ class DocumentationManager:
160
166
 
161
167
  if self.existing_content:
162
168
  # Check for old patterns
163
- if "## Installation" in self.existing_content and "pip install" not in self.existing_content:
169
+ if (
170
+ "## Installation" in self.existing_content
171
+ and "pip install" not in self.existing_content
172
+ ):
164
173
  patterns.append("Missing installation instructions")
165
174
 
166
175
  if "TODO" in self.existing_content or "FIXME" in self.existing_content:
167
176
  patterns.append("Contains TODO/FIXME items")
168
177
 
169
- if not re.search(r"Last Updated:|Last Modified:", self.existing_content, re.IGNORECASE):
178
+ if not re.search(
179
+ r"Last Updated:|Last Modified:", self.existing_content, re.IGNORECASE
180
+ ):
170
181
  patterns.append("Missing update timestamp")
171
182
 
172
183
  if "```" not in self.existing_content:
@@ -193,12 +204,16 @@ class DocumentationManager:
193
204
  custom = []
194
205
  for section in sections:
195
206
  title_lower = section["title"].lower()
196
- if not any(re.search(pattern, title_lower) for pattern in standard_patterns):
207
+ if not any(
208
+ re.search(pattern, title_lower) for pattern in standard_patterns
209
+ ):
197
210
  custom.append(section["title"])
198
211
 
199
212
  return custom
200
213
 
201
- def merge_with_template(self, new_content: str, preserve_custom: bool = True) -> str:
214
+ def merge_with_template(
215
+ self, new_content: str, preserve_custom: bool = True
216
+ ) -> str:
202
217
  """Merge existing content with new template content."""
203
218
  if not self.existing_content:
204
219
  return new_content
@@ -279,35 +294,37 @@ class DocumentationManager:
279
294
  # Map to known section types
280
295
  if "priority" in title and "index" in title:
281
296
  return "priority_index"
282
- elif "critical" in title and "security" in title:
297
+ if "critical" in title and "security" in title:
283
298
  return "critical_security"
284
- elif "critical" in title and "business" in title:
299
+ if "critical" in title and "business" in title:
285
300
  return "critical_business"
286
- elif "important" in title and "architecture" in title:
301
+ if "important" in title and "architecture" in title:
287
302
  return "important_architecture"
288
- elif "important" in title and "workflow" in title:
303
+ if "important" in title and "workflow" in title:
289
304
  return "important_workflow"
290
- elif "project" in title and "overview" in title:
305
+ if "project" in title and "overview" in title:
291
306
  return "project_overview"
292
- elif "standard" in title and "coding" in title:
307
+ if "standard" in title and "coding" in title:
293
308
  return "standard_coding"
294
- elif "standard" in title and "tasks" in title:
309
+ if "standard" in title and "tasks" in title:
295
310
  return "standard_tasks"
296
- elif "documentation" in title:
311
+ if "documentation" in title:
297
312
  return "documentation_links"
298
- elif "optional" in title or "future" in title:
313
+ if "optional" in title or "future" in title:
299
314
  return "optional_future"
300
- elif "meta" in title or "maintain" in title:
315
+ if "meta" in title or "maintain" in title:
301
316
  return "meta_maintenance"
302
- else:
303
- return "unknown"
317
+ return "unknown"
304
318
 
305
319
  def _merge_section_content(
306
320
  self, existing: str, new: str, section_header: str
307
321
  ) -> str:
308
322
  """Merge content from existing and new sections."""
309
323
  # For critical sections, prefer new content but append unique existing items
310
- if "critical" in section_header.lower() or "important" in section_header.lower():
324
+ if (
325
+ "critical" in section_header.lower()
326
+ or "important" in section_header.lower()
327
+ ):
311
328
  # Extract bullet points from both
312
329
  existing_items = self._extract_bullet_points(existing)
313
330
  new_items = self._extract_bullet_points(new)
@@ -321,13 +338,11 @@ class DocumentationManager:
321
338
  # Reconstruct section
322
339
  if all_items:
323
340
  return "\n".join([""] + all_items + [""])
324
- else:
325
- return new
326
- else:
327
- # For other sections, use new as base and append existing
328
- if existing.strip() and existing.strip() != new.strip():
329
- return f"{new}\n\n<!-- Preserved from previous version -->\n{existing}"
330
341
  return new
342
+ # For other sections, use new as base and append existing
343
+ if existing.strip() and existing.strip() != new.strip():
344
+ return f"{new}\n\n<!-- Preserved from previous version -->\n{existing}"
345
+ return new
331
346
 
332
347
  def _extract_bullet_points(self, content: str) -> List[str]:
333
348
  """Extract bullet points from content."""
@@ -343,7 +358,9 @@ class DocumentationManager:
343
358
  for existing in items:
344
359
  existing_clean = re.sub(r"[^a-zA-Z0-9\s]", "", existing.lower())
345
360
  # Use fuzzy matching for similarity
346
- similarity = difflib.SequenceMatcher(None, item_clean, existing_clean).ratio()
361
+ similarity = difflib.SequenceMatcher(
362
+ None, item_clean, existing_clean
363
+ ).ratio()
347
364
  if similarity > 0.8: # 80% similarity threshold
348
365
  return True
349
366
  return False
@@ -464,7 +481,9 @@ class DocumentationManager:
464
481
  issues.append(f"Missing required section: {section}")
465
482
 
466
483
  # Check for priority markers
467
- has_markers = any(marker in content for marker in self.PRIORITY_MARKERS.values())
484
+ has_markers = any(
485
+ marker in content for marker in self.PRIORITY_MARKERS.values()
486
+ )
468
487
  if not has_markers:
469
488
  issues.append("No priority markers found (🔴🟡🟢⚪)")
470
489
 
@@ -533,4 +552,4 @@ class DocumentationManager:
533
552
  - **Last Updated**: {datetime.now().isoformat()}
534
553
  - **Created By**: Claude MPM /mpm-init
535
554
  - **Update Frequency**: As needed when requirements change
536
- """
555
+ """
@@ -16,15 +16,15 @@ Author: Claude MPM Development Team
16
16
  Created: 2025-01-26
17
17
  """
18
18
 
19
- import json
20
19
  import subprocess
21
20
  from datetime import datetime, timedelta
22
21
  from pathlib import Path
23
- from typing import Dict, List, Optional, Set, Tuple
22
+ from typing import Dict, List, Optional
24
23
 
25
24
  from rich.console import Console
26
25
 
27
26
  from claude_mpm.core.logging_utils import get_logger
27
+
28
28
  logger = get_logger(__name__)
29
29
  console = Console()
30
30
 
@@ -77,12 +77,14 @@ class EnhancedProjectAnalyzer:
77
77
  since_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
78
78
 
79
79
  # Get commit log with structured format
80
- output = self._run_git_command([
81
- "log",
82
- f"--since={since_date}",
83
- "--pretty=format:%H|%an|%ae|%at|%s",
84
- "--no-merges",
85
- ])
80
+ output = self._run_git_command(
81
+ [
82
+ "log",
83
+ f"--since={since_date}",
84
+ "--pretty=format:%H|%an|%ae|%at|%s",
85
+ "--no-merges",
86
+ ]
87
+ )
86
88
 
87
89
  if not output:
88
90
  return []
@@ -91,13 +93,15 @@ class EnhancedProjectAnalyzer:
91
93
  for line in output.splitlines():
92
94
  parts = line.split("|", 4)
93
95
  if len(parts) == 5:
94
- commits.append({
95
- "hash": parts[0][:8],
96
- "author": parts[1],
97
- "email": parts[2],
98
- "timestamp": datetime.fromtimestamp(int(parts[3])).isoformat(),
99
- "message": parts[4],
100
- })
96
+ commits.append(
97
+ {
98
+ "hash": parts[0][:8],
99
+ "author": parts[1],
100
+ "email": parts[2],
101
+ "timestamp": datetime.fromtimestamp(int(parts[3])).isoformat(),
102
+ "message": parts[4],
103
+ }
104
+ )
101
105
 
102
106
  return commits[:50] # Limit to 50 most recent
103
107
 
@@ -105,13 +109,15 @@ class EnhancedProjectAnalyzer:
105
109
  """Get files changed in recent commits."""
106
110
  since_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
107
111
 
108
- output = self._run_git_command([
109
- "log",
110
- f"--since={since_date}",
111
- "--pretty=format:",
112
- "--name-only",
113
- "--no-merges",
114
- ])
112
+ output = self._run_git_command(
113
+ [
114
+ "log",
115
+ f"--since={since_date}",
116
+ "--pretty=format:",
117
+ "--name-only",
118
+ "--no-merges",
119
+ ]
120
+ )
115
121
 
116
122
  if not output:
117
123
  return {}
@@ -134,13 +140,15 @@ class EnhancedProjectAnalyzer:
134
140
  """Get files added in recent commits."""
135
141
  since_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
136
142
 
137
- output = self._run_git_command([
138
- "log",
139
- f"--since={since_date}",
140
- "--pretty=format:",
141
- "--name-status",
142
- "--diff-filter=A", # Added files only
143
- ])
143
+ output = self._run_git_command(
144
+ [
145
+ "log",
146
+ f"--since={since_date}",
147
+ "--pretty=format:",
148
+ "--name-status",
149
+ "--diff-filter=A", # Added files only
150
+ ]
151
+ )
144
152
 
145
153
  if not output:
146
154
  return []
@@ -156,12 +164,14 @@ class EnhancedProjectAnalyzer:
156
164
  """Get author contribution statistics."""
157
165
  since_date = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
158
166
 
159
- output = self._run_git_command([
160
- "shortlog",
161
- "-sne",
162
- f"--since={since_date}",
163
- "--no-merges",
164
- ])
167
+ output = self._run_git_command(
168
+ [
169
+ "shortlog",
170
+ "-sne",
171
+ f"--since={since_date}",
172
+ "--no-merges",
173
+ ]
174
+ )
165
175
 
166
176
  if not output:
167
177
  return {}
@@ -201,10 +211,12 @@ class EnhancedProjectAnalyzer:
201
211
  for line in remotes.splitlines():
202
212
  parts = line.split()
203
213
  if len(parts) >= 2:
204
- info["remotes"].append({
205
- "name": parts[0],
206
- "url": parts[1],
207
- })
214
+ info["remotes"].append(
215
+ {
216
+ "name": parts[0],
217
+ "url": parts[1],
218
+ }
219
+ )
208
220
 
209
221
  # Check for uncommitted changes
210
222
  status = self._run_git_command(["status", "--porcelain"])
@@ -223,12 +235,15 @@ class EnhancedProjectAnalyzer:
223
235
 
224
236
  doc_changes = {}
225
237
  for pattern in doc_patterns:
226
- output = self._run_git_command([
227
- "log",
228
- f"--since={since_date}",
229
- "--pretty=format:%H|%s",
230
- "--", pattern,
231
- ])
238
+ output = self._run_git_command(
239
+ [
240
+ "log",
241
+ f"--since={since_date}",
242
+ "--pretty=format:%H|%s",
243
+ "--",
244
+ pattern,
245
+ ]
246
+ )
232
247
 
233
248
  if output:
234
249
  for line in output.splitlines():
@@ -236,29 +251,38 @@ class EnhancedProjectAnalyzer:
236
251
  if len(parts) == 2:
237
252
  if pattern not in doc_changes:
238
253
  doc_changes[pattern] = []
239
- doc_changes[pattern].append({
240
- "commit": parts[0][:8],
241
- "message": parts[1],
242
- })
254
+ doc_changes[pattern].append(
255
+ {
256
+ "commit": parts[0][:8],
257
+ "message": parts[1],
258
+ }
259
+ )
243
260
 
244
261
  # Check CLAUDE.md specifically
245
- claude_history = self._run_git_command([
246
- "log",
247
- f"--since={since_date}",
248
- "--pretty=format:%H|%at|%s",
249
- "--", "CLAUDE.md",
250
- ])
262
+ claude_history = self._run_git_command(
263
+ [
264
+ "log",
265
+ f"--since={since_date}",
266
+ "--pretty=format:%H|%at|%s",
267
+ "--",
268
+ "CLAUDE.md",
269
+ ]
270
+ )
251
271
 
252
272
  claude_updates = []
253
273
  if claude_history:
254
274
  for line in claude_history.splitlines():
255
275
  parts = line.split("|", 2)
256
276
  if len(parts) == 3:
257
- claude_updates.append({
258
- "commit": parts[0][:8],
259
- "timestamp": datetime.fromtimestamp(int(parts[1])).isoformat(),
260
- "message": parts[2],
261
- })
277
+ claude_updates.append(
278
+ {
279
+ "commit": parts[0][:8],
280
+ "timestamp": datetime.fromtimestamp(
281
+ int(parts[1])
282
+ ).isoformat(),
283
+ "message": parts[2],
284
+ }
285
+ )
262
286
 
263
287
  return {
264
288
  "documentation_commits": doc_changes,
@@ -284,7 +308,9 @@ class EnhancedProjectAnalyzer:
284
308
  patterns["features"].append(commit["message"][:100])
285
309
  elif any(kw in msg_lower for kw in ["fix", "bug", "resolve", "patch"]):
286
310
  patterns["fixes"].append(commit["message"][:100])
287
- elif any(kw in msg_lower for kw in ["refactor", "restructure", "reorganize"]):
311
+ elif any(
312
+ kw in msg_lower for kw in ["refactor", "restructure", "reorganize"]
313
+ ):
288
314
  patterns["refactoring"].append(commit["message"][:100])
289
315
  elif any(kw in msg_lower for kw in ["doc", "readme", "comment"]):
290
316
  patterns["documentation"].append(commit["message"][:100])
@@ -318,12 +344,14 @@ class EnhancedProjectAnalyzer:
318
344
  hot_spots = []
319
345
  for file_path, change_count in list(changed_files["most_changed"].items())[:10]:
320
346
  file_type = Path(file_path).suffix
321
- hot_spots.append({
322
- "file": file_path,
323
- "changes": change_count,
324
- "type": file_type,
325
- "category": self._categorize_file(file_path),
326
- })
347
+ hot_spots.append(
348
+ {
349
+ "file": file_path,
350
+ "changes": change_count,
351
+ "type": file_type,
352
+ "category": self._categorize_file(file_path),
353
+ }
354
+ )
327
355
 
328
356
  return hot_spots
329
357
 
@@ -334,18 +362,17 @@ class EnhancedProjectAnalyzer:
334
362
  # Check directory
335
363
  if "test" in str(path).lower():
336
364
  return "test"
337
- elif "docs" in str(path).lower():
365
+ if "docs" in str(path).lower():
338
366
  return "documentation"
339
- elif "src" in str(path) or "lib" in str(path):
367
+ if "src" in str(path) or "lib" in str(path):
340
368
  return "source"
341
- elif "scripts" in str(path):
369
+ if "scripts" in str(path):
342
370
  return "scripts"
343
- elif path.suffix in [".yml", ".yaml", ".json", ".toml", ".ini"]:
371
+ if path.suffix in [".yml", ".yaml", ".json", ".toml", ".ini"]:
344
372
  return "configuration"
345
- elif path.suffix in [".md", ".rst", ".txt"]:
373
+ if path.suffix in [".md", ".rst", ".txt"]:
346
374
  return "documentation"
347
- else:
348
- return "other"
375
+ return "other"
349
376
 
350
377
  def detect_project_state(self) -> Dict:
351
378
  """Detect the current state and lifecycle phase of the project."""
@@ -371,7 +398,9 @@ class EnhancedProjectAnalyzer:
371
398
  indicators.append("Has GitLab CI")
372
399
 
373
400
  # Check for tests
374
- if (self.project_path / "tests").exists() or (self.project_path / "test").exists():
401
+ if (self.project_path / "tests").exists() or (
402
+ self.project_path / "test"
403
+ ).exists():
375
404
  indicators.append("Has test directory")
376
405
 
377
406
  # Check for documentation
@@ -391,33 +420,45 @@ class EnhancedProjectAnalyzer:
391
420
  # Determine phase based on commit count
392
421
  if count < 10:
393
422
  state["phase"] = "initial"
394
- state["recommendations"].append("Focus on establishing core structure")
423
+ state["recommendations"].append(
424
+ "Focus on establishing core structure"
425
+ )
395
426
  elif count < 50:
396
427
  state["phase"] = "early_development"
397
- state["recommendations"].append("Consider adding tests and documentation")
428
+ state["recommendations"].append(
429
+ "Consider adding tests and documentation"
430
+ )
398
431
  elif count < 200:
399
432
  state["phase"] = "active_development"
400
- state["recommendations"].append("Ensure CI/CD and testing are in place")
433
+ state["recommendations"].append(
434
+ "Ensure CI/CD and testing are in place"
435
+ )
401
436
  elif count < 1000:
402
437
  state["phase"] = "maturing"
403
- state["recommendations"].append("Focus on optimization and documentation")
438
+ state["recommendations"].append(
439
+ "Focus on optimization and documentation"
440
+ )
404
441
  else:
405
442
  state["phase"] = "mature"
406
443
  state["recommendations"].append("Maintain backward compatibility")
407
444
 
408
445
  # Check age
409
- first_commit = self._run_git_command([
410
- "log", "--reverse", "--format=%at", "-1"
411
- ])
446
+ first_commit = self._run_git_command(
447
+ ["log", "--reverse", "--format=%at", "-1"]
448
+ )
412
449
  if first_commit:
413
- age_days = (datetime.now() - datetime.fromtimestamp(int(first_commit))).days
450
+ age_days = (
451
+ datetime.now() - datetime.fromtimestamp(int(first_commit))
452
+ ).days
414
453
  indicators.append(f"{age_days} days old")
415
454
 
416
455
  state["indicators"] = indicators
417
456
 
418
457
  # Add phase-specific recommendations
419
458
  if not (self.project_path / "CLAUDE.md").exists():
420
- state["recommendations"].append("Create CLAUDE.md for AI agent documentation")
459
+ state["recommendations"].append(
460
+ "Create CLAUDE.md for AI agent documentation"
461
+ )
421
462
  if not (self.project_path / "tests").exists():
422
463
  state["recommendations"].append("Add tests directory for test organization")
423
464
  if not (self.project_path / ".gitignore").exists():
@@ -474,10 +515,12 @@ class EnhancedProjectAnalyzer:
474
515
  try:
475
516
  size = path.stat().st_size
476
517
  if size > 1024 * 1024: # Files over 1MB
477
- stats["largest_files"].append({
478
- "path": str(path.relative_to(self.project_path)),
479
- "size_mb": round(size / (1024 * 1024), 2),
480
- })
518
+ stats["largest_files"].append(
519
+ {
520
+ "path": str(path.relative_to(self.project_path)),
521
+ "size_mb": round(size / (1024 * 1024), 2),
522
+ }
523
+ )
481
524
  except (OSError, PermissionError):
482
525
  pass
483
526
 
@@ -488,4 +531,4 @@ class EnhancedProjectAnalyzer:
488
531
  stats["largest_files"].sort(key=lambda x: x["size_mb"], reverse=True)
489
532
  stats["largest_files"] = stats["largest_files"][:10] # Top 10
490
533
 
491
- return stats
534
+ return stats