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.
- claude_mpm/VERSION +1 -1
- 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/doctor.py +44 -5
- claude_mpm/cli/commands/mpm_init.py +116 -62
- claude_mpm/cli/parsers/configure_parser.py +3 -1
- 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 +6 -6
- claude_mpm/core/framework/formatters/__init__.py +2 -2
- claude_mpm/core/framework/formatters/capability_generator.py +19 -8
- claude_mpm/core/framework/formatters/content_formatter.py +8 -3
- claude_mpm/core/framework/formatters/context_generator.py +7 -3
- claude_mpm/core/framework/loaders/__init__.py +3 -3
- claude_mpm/core/framework/loaders/agent_loader.py +7 -3
- claude_mpm/core/framework/loaders/file_loader.py +16 -6
- claude_mpm/core/framework/loaders/instruction_loader.py +16 -6
- claude_mpm/core/framework/loaders/packaged_loader.py +36 -12
- claude_mpm/core/framework/processors/__init__.py +2 -2
- claude_mpm/core/framework/processors/memory_processor.py +14 -6
- claude_mpm/core/framework/processors/metadata_processor.py +5 -5
- claude_mpm/core/framework/processors/template_processor.py +12 -6
- claude_mpm/core/framework_loader.py +44 -20
- 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/base_hook.py +1 -0
- claude_mpm/hooks/instruction_reinforcement.py +1 -0
- claude_mpm/hooks/kuzu_memory_hook.py +20 -13
- 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 +1 -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 +3 -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 +10 -10
- claude_mpm/services/mcp_gateway/core/process_pool.py +62 -23
- claude_mpm/services/mcp_gateway/tools/__init__.py +6 -5
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +3 -1
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +16 -31
- 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/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 +111 -126
- claude_mpm/services/unified/config_strategies/config_schema.py +157 -111
- claude_mpm/services/unified/config_strategies/context_strategy.py +91 -89
- claude_mpm/services/unified/config_strategies/error_handling_strategy.py +183 -173
- claude_mpm/services/unified/config_strategies/file_loader_strategy.py +160 -152
- claude_mpm/services/unified/config_strategies/unified_config_service.py +124 -112
- claude_mpm/services/unified/config_strategies/validation_strategy.py +298 -259
- 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.3.dist-info → claude_mpm-4.4.4.dist-info}/METADATA +9 -3
- {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/RECORD +118 -117
- {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/WHEEL +0 -0
- {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.4.3.dist-info → claude_mpm-4.4.4.dist-info}/licenses/LICENSE +0 -0
- {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,
|
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(
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
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(
|
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(
|
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(
|
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
|
-
|
297
|
+
if "critical" in title and "security" in title:
|
283
298
|
return "critical_security"
|
284
|
-
|
299
|
+
if "critical" in title and "business" in title:
|
285
300
|
return "critical_business"
|
286
|
-
|
301
|
+
if "important" in title and "architecture" in title:
|
287
302
|
return "important_architecture"
|
288
|
-
|
303
|
+
if "important" in title and "workflow" in title:
|
289
304
|
return "important_workflow"
|
290
|
-
|
305
|
+
if "project" in title and "overview" in title:
|
291
306
|
return "project_overview"
|
292
|
-
|
307
|
+
if "standard" in title and "coding" in title:
|
293
308
|
return "standard_coding"
|
294
|
-
|
309
|
+
if "standard" in title and "tasks" in title:
|
295
310
|
return "standard_tasks"
|
296
|
-
|
311
|
+
if "documentation" in title:
|
297
312
|
return "documentation_links"
|
298
|
-
|
313
|
+
if "optional" in title or "future" in title:
|
299
314
|
return "optional_future"
|
300
|
-
|
315
|
+
if "meta" in title or "maintain" in title:
|
301
316
|
return "meta_maintenance"
|
302
|
-
|
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
|
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(
|
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(
|
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
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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
|
-
|
206
|
-
|
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
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
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
|
-
|
241
|
-
|
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
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
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
|
-
|
259
|
-
|
260
|
-
|
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(
|
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
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
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
|
-
|
365
|
+
if "docs" in str(path).lower():
|
338
366
|
return "documentation"
|
339
|
-
|
367
|
+
if "src" in str(path) or "lib" in str(path):
|
340
368
|
return "source"
|
341
|
-
|
369
|
+
if "scripts" in str(path):
|
342
370
|
return "scripts"
|
343
|
-
|
371
|
+
if path.suffix in [".yml", ".yaml", ".json", ".toml", ".ini"]:
|
344
372
|
return "configuration"
|
345
|
-
|
373
|
+
if path.suffix in [".md", ".rst", ".txt"]:
|
346
374
|
return "documentation"
|
347
|
-
|
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 (
|
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(
|
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(
|
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(
|
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(
|
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 = (
|
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(
|
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
|
-
|
479
|
-
|
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
|