claude-mpm 3.3.0__py3-none-any.whl → 3.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. claude_mpm/agents/templates/data_engineer.json +1 -1
  2. claude_mpm/agents/templates/documentation.json +1 -1
  3. claude_mpm/agents/templates/engineer.json +1 -1
  4. claude_mpm/agents/templates/ops.json +1 -1
  5. claude_mpm/agents/templates/pm.json +1 -1
  6. claude_mpm/agents/templates/qa.json +1 -1
  7. claude_mpm/agents/templates/research.json +1 -1
  8. claude_mpm/agents/templates/security.json +1 -1
  9. claude_mpm/agents/templates/test_integration.json +112 -0
  10. claude_mpm/agents/templates/version_control.json +1 -1
  11. claude_mpm/cli/commands/memory.py +749 -26
  12. claude_mpm/cli/commands/run.py +115 -14
  13. claude_mpm/cli/parser.py +89 -1
  14. claude_mpm/constants.py +6 -0
  15. claude_mpm/core/claude_runner.py +74 -11
  16. claude_mpm/core/config.py +1 -1
  17. claude_mpm/core/session_manager.py +46 -0
  18. claude_mpm/core/simple_runner.py +74 -11
  19. claude_mpm/hooks/builtin/mpm_command_hook.py +5 -5
  20. claude_mpm/hooks/claude_hooks/hook_handler.py +213 -30
  21. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +9 -2
  22. claude_mpm/hooks/memory_integration_hook.py +51 -5
  23. claude_mpm/services/__init__.py +23 -5
  24. claude_mpm/services/agent_memory_manager.py +800 -71
  25. claude_mpm/services/memory_builder.py +823 -0
  26. claude_mpm/services/memory_optimizer.py +619 -0
  27. claude_mpm/services/memory_router.py +445 -0
  28. claude_mpm/services/project_analyzer.py +771 -0
  29. claude_mpm/services/socketio_server.py +649 -45
  30. claude_mpm/services/version_control/git_operations.py +26 -0
  31. claude_mpm-3.4.0.dist-info/METADATA +183 -0
  32. {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/RECORD +36 -52
  33. claude_mpm/agents/agent-template.yaml +0 -83
  34. claude_mpm/agents/templates/test-integration-agent.md +0 -34
  35. claude_mpm/agents/test_fix_deployment/.claude-pm/config/project.json +0 -6
  36. claude_mpm/cli/README.md +0 -109
  37. claude_mpm/cli_module/refactoring_guide.md +0 -253
  38. claude_mpm/core/agent_registry.py.bak +0 -312
  39. claude_mpm/core/base_service.py.bak +0 -406
  40. claude_mpm/core/websocket_handler.py +0 -233
  41. claude_mpm/hooks/README.md +0 -97
  42. claude_mpm/orchestration/SUBPROCESS_DESIGN.md +0 -66
  43. claude_mpm/schemas/README_SECURITY.md +0 -92
  44. claude_mpm/schemas/agent_schema.json +0 -395
  45. claude_mpm/schemas/agent_schema_documentation.md +0 -181
  46. claude_mpm/schemas/agent_schema_security_notes.md +0 -165
  47. claude_mpm/schemas/examples/standard_workflow.json +0 -505
  48. claude_mpm/schemas/ticket_workflow_documentation.md +0 -482
  49. claude_mpm/schemas/ticket_workflow_schema.json +0 -590
  50. claude_mpm/services/framework_claude_md_generator/README.md +0 -92
  51. claude_mpm/services/parent_directory_manager/README.md +0 -83
  52. claude_mpm/services/version_control/VERSION +0 -1
  53. claude_mpm/services/websocket_server.py +0 -376
  54. claude_mpm-3.3.0.dist-info/METADATA +0 -432
  55. {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/WHEEL +0 -0
  56. {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/entry_points.txt +0 -0
  57. {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/licenses/LICENSE +0 -0
  58. {claude_mpm-3.3.0.dist-info → claude_mpm-3.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,771 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Project Analyzer Service
4
+ =======================
5
+
6
+ Analyzes project characteristics to enable project-specific memory creation.
7
+
8
+ WHY: Instead of creating generic memories, agents need to understand the specific
9
+ project they're working on - its tech stack, architecture patterns, coding conventions,
10
+ and key components. This service extracts these characteristics automatically.
11
+
12
+ DESIGN DECISION: Separates project analysis from memory creation to allow reuse
13
+ across different memory-related services and enable caching of analysis results.
14
+
15
+ This service analyzes:
16
+ - Technology stack from config files (package.json, requirements.txt, etc.)
17
+ - Code patterns from source files
18
+ - Architecture patterns from directory structure
19
+ - Testing frameworks and approaches
20
+ - API patterns and endpoints
21
+ - Database integrations
22
+ - Project-specific terminology and conventions
23
+ """
24
+
25
+ import json
26
+ import re
27
+ from pathlib import Path
28
+ from typing import Dict, List, Optional, Any, Set, Tuple
29
+ import logging
30
+ from dataclasses import dataclass, asdict
31
+ from collections import defaultdict, Counter
32
+
33
+ from claude_mpm.core.config import Config
34
+ from claude_mpm.utils.paths import PathResolver
35
+
36
+
37
+ @dataclass
38
+ class ProjectCharacteristics:
39
+ """Structured representation of project characteristics."""
40
+
41
+ # Core project info
42
+ project_name: str
43
+ primary_language: Optional[str]
44
+ languages: List[str]
45
+ frameworks: List[str]
46
+
47
+ # Architecture and structure
48
+ architecture_type: str
49
+ main_modules: List[str]
50
+ key_directories: List[str]
51
+ entry_points: List[str]
52
+
53
+ # Development practices
54
+ testing_framework: Optional[str]
55
+ test_patterns: List[str]
56
+ package_manager: Optional[str]
57
+ build_tools: List[str]
58
+
59
+ # Integrations and dependencies
60
+ databases: List[str]
61
+ web_frameworks: List[str]
62
+ api_patterns: List[str]
63
+ key_dependencies: List[str]
64
+
65
+ # Project-specific patterns
66
+ code_conventions: List[str]
67
+ configuration_patterns: List[str]
68
+ project_terminology: List[str]
69
+
70
+ # Documentation and structure
71
+ documentation_files: List[str]
72
+ important_configs: List[str]
73
+
74
+ def to_dict(self) -> Dict[str, Any]:
75
+ """Convert to dictionary for JSON serialization."""
76
+ return asdict(self)
77
+
78
+
79
+ class ProjectAnalyzer:
80
+ """Analyzes project characteristics for context-aware memory creation.
81
+
82
+ WHY: Generic agent memories aren't helpful for specific projects. This analyzer
83
+ extracts project-specific characteristics that enable agents to create relevant,
84
+ actionable memories with proper context.
85
+
86
+ DESIGN DECISION: Uses a combination of file pattern analysis, content parsing,
87
+ and directory structure analysis to build comprehensive project understanding
88
+ without requiring external tools or API calls.
89
+ """
90
+
91
+ # Common configuration files and their indicators
92
+ CONFIG_FILE_PATTERNS = {
93
+ 'package.json': 'node_js',
94
+ 'requirements.txt': 'python',
95
+ 'pyproject.toml': 'python',
96
+ 'setup.py': 'python',
97
+ 'Cargo.toml': 'rust',
98
+ 'pom.xml': 'java',
99
+ 'build.gradle': 'java',
100
+ 'composer.json': 'php',
101
+ 'Gemfile': 'ruby',
102
+ 'go.mod': 'go',
103
+ 'CMakeLists.txt': 'cpp',
104
+ 'Makefile': 'c_cpp',
105
+ }
106
+
107
+ # Framework detection patterns
108
+ FRAMEWORK_PATTERNS = {
109
+ 'flask': ['from flask', 'Flask(', 'app.route'],
110
+ 'django': ['from django', 'DJANGO_SETTINGS', 'django.contrib'],
111
+ 'fastapi': ['from fastapi', 'FastAPI(', '@app.'],
112
+ 'express': ['express()', 'app.get(', 'app.post('],
113
+ 'react': ['import React', 'from react', 'ReactDOM'],
114
+ 'vue': ['Vue.createApp', 'new Vue(', 'vue-'],
115
+ 'angular': ['@Component', '@Injectable', 'Angular'],
116
+ 'spring': ['@SpringBootApplication', '@RestController', 'Spring'],
117
+ 'rails': ['Rails.application', 'ApplicationController'],
118
+ }
119
+
120
+ # Database detection patterns
121
+ DATABASE_PATTERNS = {
122
+ 'postgresql': ['psycopg2', 'postgresql:', 'postgres:', 'pg_'],
123
+ 'mysql': ['mysql-connector', 'mysql:', 'MySQLdb'],
124
+ 'sqlite': ['sqlite3', 'sqlite:', '.db', '.sqlite'],
125
+ 'mongodb': ['pymongo', 'mongodb:', 'mongoose'],
126
+ 'redis': ['redis:', 'redis-py', 'RedisClient'],
127
+ 'elasticsearch': ['elasticsearch:', 'elastic'],
128
+ }
129
+
130
+ def __init__(self, config: Optional[Config] = None, working_directory: Optional[Path] = None):
131
+ """Initialize the project analyzer.
132
+
133
+ Args:
134
+ config: Optional Config object
135
+ working_directory: Optional working directory path. If not provided, uses current.
136
+ """
137
+ self.config = config or Config()
138
+ self.working_directory = working_directory or PathResolver.get_project_root()
139
+ self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
140
+
141
+ # Cache for analysis results
142
+ self._analysis_cache: Optional[ProjectCharacteristics] = None
143
+ self._cache_timestamp: Optional[float] = None
144
+
145
+ def analyze_project(self, force_refresh: bool = False) -> ProjectCharacteristics:
146
+ """Analyze the current project and return characteristics.
147
+
148
+ WHY: Comprehensive project analysis enables agents to create memories
149
+ that are specific to the actual project context, tech stack, and patterns.
150
+
151
+ Args:
152
+ force_refresh: If True, ignores cache and performs fresh analysis
153
+
154
+ Returns:
155
+ ProjectCharacteristics: Structured project analysis results
156
+ """
157
+ try:
158
+ # Check cache first (unless force refresh)
159
+ if not force_refresh and self._analysis_cache and self._cache_timestamp:
160
+ # Cache is valid for 5 minutes
161
+ import time
162
+ if time.time() - self._cache_timestamp < 300:
163
+ self.logger.debug("Using cached project analysis")
164
+ return self._analysis_cache
165
+
166
+ self.logger.info(f"Analyzing project at: {self.working_directory}")
167
+
168
+ # Initialize characteristics with basic info
169
+ characteristics = ProjectCharacteristics(
170
+ project_name=self.working_directory.name,
171
+ primary_language=None,
172
+ languages=[],
173
+ frameworks=[],
174
+ architecture_type="unknown",
175
+ main_modules=[],
176
+ key_directories=[],
177
+ entry_points=[],
178
+ testing_framework=None,
179
+ test_patterns=[],
180
+ package_manager=None,
181
+ build_tools=[],
182
+ databases=[],
183
+ web_frameworks=[],
184
+ api_patterns=[],
185
+ key_dependencies=[],
186
+ code_conventions=[],
187
+ configuration_patterns=[],
188
+ project_terminology=[],
189
+ documentation_files=[],
190
+ important_configs=[]
191
+ )
192
+
193
+ # Perform various analyses
194
+ self._analyze_config_files(characteristics)
195
+ self._analyze_directory_structure(characteristics)
196
+ self._analyze_source_code(characteristics)
197
+ self._analyze_dependencies(characteristics)
198
+ self._analyze_testing_patterns(characteristics)
199
+ self._analyze_documentation(characteristics)
200
+ self._infer_architecture_type(characteristics)
201
+ self._extract_project_terminology(characteristics)
202
+
203
+ # Cache the results
204
+ self._analysis_cache = characteristics
205
+ import time
206
+ self._cache_timestamp = time.time()
207
+
208
+ self.logger.info(f"Project analysis complete: {characteristics.primary_language} project with {len(characteristics.frameworks)} frameworks")
209
+ return characteristics
210
+
211
+ except Exception as e:
212
+ self.logger.error(f"Error analyzing project: {e}")
213
+ # Return minimal characteristics on error
214
+ return ProjectCharacteristics(
215
+ project_name=self.working_directory.name,
216
+ primary_language="unknown",
217
+ languages=[],
218
+ frameworks=[],
219
+ architecture_type="unknown",
220
+ main_modules=[],
221
+ key_directories=[],
222
+ entry_points=[],
223
+ testing_framework=None,
224
+ test_patterns=[],
225
+ package_manager=None,
226
+ build_tools=[],
227
+ databases=[],
228
+ web_frameworks=[],
229
+ api_patterns=[],
230
+ key_dependencies=[],
231
+ code_conventions=[],
232
+ configuration_patterns=[],
233
+ project_terminology=[],
234
+ documentation_files=[],
235
+ important_configs=[]
236
+ )
237
+
238
+ def _analyze_config_files(self, characteristics: ProjectCharacteristics) -> None:
239
+ """Analyze configuration files to determine tech stack.
240
+
241
+ WHY: Configuration files are the most reliable indicators of project
242
+ technology stack and dependencies. They provide definitive information
243
+ about what technologies are actually used.
244
+
245
+ Args:
246
+ characteristics: ProjectCharacteristics object to update
247
+ """
248
+ config_files = []
249
+ languages_found = set()
250
+
251
+ for config_file, language in self.CONFIG_FILE_PATTERNS.items():
252
+ config_path = self.working_directory / config_file
253
+ if config_path.exists():
254
+ config_files.append(config_file)
255
+ languages_found.add(language)
256
+ characteristics.important_configs.append(str(config_path.relative_to(self.working_directory)))
257
+
258
+ # Parse specific config files for more details
259
+ try:
260
+ if config_file == 'package.json':
261
+ self._parse_package_json(config_path, characteristics)
262
+ elif config_file in ['requirements.txt', 'pyproject.toml']:
263
+ self._parse_python_dependencies(config_path, characteristics)
264
+ elif config_file == 'Cargo.toml':
265
+ self._parse_cargo_toml(config_path, characteristics)
266
+ except Exception as e:
267
+ self.logger.warning(f"Error parsing {config_file}: {e}")
268
+
269
+ # Set primary language (prefer more specific indicators)
270
+ language_priority = ['python', 'node_js', 'rust', 'java', 'go', 'php', 'ruby']
271
+ for lang in language_priority:
272
+ if lang in languages_found:
273
+ characteristics.primary_language = lang
274
+ break
275
+
276
+ characteristics.languages = list(languages_found)
277
+
278
+ # Determine package manager
279
+ if 'package.json' in config_files:
280
+ if (self.working_directory / 'yarn.lock').exists():
281
+ characteristics.package_manager = 'yarn'
282
+ elif (self.working_directory / 'pnpm-lock.yaml').exists():
283
+ characteristics.package_manager = 'pnpm'
284
+ else:
285
+ characteristics.package_manager = 'npm'
286
+ elif 'requirements.txt' in config_files or 'pyproject.toml' in config_files:
287
+ characteristics.package_manager = 'pip'
288
+ elif 'Cargo.toml' in config_files:
289
+ characteristics.package_manager = 'cargo'
290
+
291
+ def _parse_package_json(self, package_path: Path, characteristics: ProjectCharacteristics) -> None:
292
+ """Parse package.json for Node.js project details."""
293
+ try:
294
+ with open(package_path, 'r') as f:
295
+ package_data = json.load(f)
296
+
297
+ # Extract dependencies
298
+ all_deps = {}
299
+ all_deps.update(package_data.get('dependencies', {}))
300
+ all_deps.update(package_data.get('devDependencies', {}))
301
+
302
+ # Identify frameworks and tools
303
+ for dep_name in all_deps.keys():
304
+ dep_lower = dep_name.lower()
305
+
306
+ # Web frameworks
307
+ if any(fw in dep_lower for fw in ['express', 'koa', 'hapi']):
308
+ characteristics.web_frameworks.append(dep_name)
309
+ elif any(fw in dep_lower for fw in ['react', 'vue', 'angular', 'svelte']):
310
+ characteristics.frameworks.append(dep_name)
311
+ elif any(db in dep_lower for db in ['mysql', 'postgres', 'mongodb', 'redis']):
312
+ characteristics.databases.append(dep_name)
313
+ elif any(test in dep_lower for test in ['jest', 'mocha', 'cypress', 'playwright']):
314
+ if not characteristics.testing_framework:
315
+ characteristics.testing_framework = dep_name
316
+
317
+ characteristics.key_dependencies.append(dep_name)
318
+
319
+ # Check scripts for build tools
320
+ scripts = package_data.get('scripts', {})
321
+ for script_name, script_cmd in scripts.items():
322
+ if any(tool in script_cmd for tool in ['webpack', 'rollup', 'vite', 'parcel']):
323
+ characteristics.build_tools.append(script_name)
324
+
325
+ except Exception as e:
326
+ self.logger.warning(f"Error parsing package.json: {e}")
327
+
328
+ def _parse_python_dependencies(self, deps_path: Path, characteristics: ProjectCharacteristics) -> None:
329
+ """Parse Python dependency files."""
330
+ try:
331
+ if deps_path.name == 'requirements.txt':
332
+ content = deps_path.read_text()
333
+ deps = [line.strip().split('=')[0].split('>')[0].split('<')[0]
334
+ for line in content.splitlines()
335
+ if line.strip() and not line.startswith('#')]
336
+ elif deps_path.name == 'pyproject.toml':
337
+ try:
338
+ import tomllib
339
+ except ImportError:
340
+ try:
341
+ import tomli as tomllib
342
+ except ImportError:
343
+ self.logger.warning(f"TOML parsing not available for {deps_path}")
344
+ return
345
+ with open(deps_path, 'rb') as f:
346
+ data = tomllib.load(f)
347
+ deps = list(data.get('project', {}).get('dependencies', []))
348
+ deps.extend(list(data.get('tool', {}).get('poetry', {}).get('dependencies', {}).keys()))
349
+ else:
350
+ return
351
+
352
+ # Identify frameworks and tools
353
+ for dep in deps:
354
+ dep_lower = dep.lower()
355
+
356
+ # Web frameworks
357
+ if dep_lower in ['flask', 'django', 'fastapi', 'tornado']:
358
+ characteristics.web_frameworks.append(dep)
359
+ elif dep_lower in ['pytest', 'unittest2', 'nose']:
360
+ if not characteristics.testing_framework:
361
+ characteristics.testing_framework = dep
362
+ elif any(db in dep_lower for db in ['psycopg2', 'mysql', 'sqlite', 'redis', 'mongo']):
363
+ characteristics.databases.append(dep)
364
+
365
+ characteristics.key_dependencies.append(dep)
366
+
367
+ except Exception as e:
368
+ self.logger.warning(f"Error parsing Python dependencies: {e}")
369
+
370
+ def _parse_cargo_toml(self, cargo_path: Path, characteristics: ProjectCharacteristics) -> None:
371
+ """Parse Cargo.toml for Rust project details."""
372
+ try:
373
+ try:
374
+ import tomllib
375
+ except ImportError:
376
+ try:
377
+ import tomli as tomllib
378
+ except ImportError:
379
+ self.logger.warning(f"TOML parsing not available for {cargo_path}")
380
+ return
381
+ with open(cargo_path, 'rb') as f:
382
+ cargo_data = tomllib.load(f)
383
+
384
+ deps = cargo_data.get('dependencies', {})
385
+ for dep_name in deps.keys():
386
+ characteristics.key_dependencies.append(dep_name)
387
+
388
+ # Identify common Rust frameworks
389
+ if dep_name in ['actix-web', 'warp', 'rocket']:
390
+ characteristics.web_frameworks.append(dep_name)
391
+ elif dep_name in ['tokio', 'async-std']:
392
+ characteristics.frameworks.append(dep_name)
393
+
394
+ except Exception as e:
395
+ self.logger.warning(f"Error parsing Cargo.toml: {e}")
396
+
397
+ def _analyze_directory_structure(self, characteristics: ProjectCharacteristics) -> None:
398
+ """Analyze directory structure for architecture patterns.
399
+
400
+ WHY: Directory structure reveals architectural decisions and project
401
+ organization patterns that agents should understand and follow.
402
+
403
+ Args:
404
+ characteristics: ProjectCharacteristics object to update
405
+ """
406
+ # Common important directories to look for
407
+ important_dirs = [
408
+ 'src', 'lib', 'app', 'components', 'services', 'models', 'views',
409
+ 'controllers', 'routes', 'api', 'web', 'static', 'templates',
410
+ 'tests', 'test', '__tests__', 'spec', 'docs', 'documentation',
411
+ 'config', 'configs', 'settings', 'utils', 'helpers', 'core',
412
+ 'modules', 'packages', 'plugins', 'extensions'
413
+ ]
414
+
415
+ # Check which directories exist
416
+ existing_dirs = []
417
+ for dir_name in important_dirs:
418
+ dir_path = self.working_directory / dir_name
419
+ if dir_path.exists() and dir_path.is_dir():
420
+ existing_dirs.append(dir_name)
421
+
422
+ # Special handling for certain directories
423
+ if dir_name in ['src', 'lib', 'app']:
424
+ # These are likely main module directories
425
+ characteristics.main_modules.extend(self._get_subdirectories(dir_path))
426
+
427
+ characteristics.key_directories = existing_dirs
428
+
429
+ # Look for entry points
430
+ entry_point_patterns = [
431
+ 'main.py', 'app.py', 'server.py', 'index.js', 'main.js',
432
+ 'app.js', 'server.js', 'main.rs', 'lib.rs', 'Main.java',
433
+ 'main.go', 'index.php', 'application.rb'
434
+ ]
435
+
436
+ for pattern in entry_point_patterns:
437
+ entry_path = self.working_directory / pattern
438
+ if entry_path.exists():
439
+ characteristics.entry_points.append(pattern)
440
+
441
+ # Also check in src/ directory
442
+ src_entry_path = self.working_directory / 'src' / pattern
443
+ if src_entry_path.exists():
444
+ characteristics.entry_points.append(f'src/{pattern}')
445
+
446
+ def _get_subdirectories(self, path: Path, max_depth: int = 2) -> List[str]:
447
+ """Get subdirectory names up to a certain depth."""
448
+ subdirs = []
449
+ try:
450
+ for item in path.iterdir():
451
+ if item.is_dir() and not item.name.startswith('.'):
452
+ subdirs.append(item.name)
453
+ if max_depth > 1:
454
+ for subitem in item.iterdir():
455
+ if subitem.is_dir() and not subitem.name.startswith('.'):
456
+ subdirs.append(f"{item.name}/{subitem.name}")
457
+ except PermissionError:
458
+ pass
459
+ return subdirs[:10] # Limit to prevent overwhelming output
460
+
461
+ def _analyze_source_code(self, characteristics: ProjectCharacteristics) -> None:
462
+ """Analyze source code files for patterns and conventions.
463
+
464
+ WHY: Source code contains the actual implementation patterns that agents
465
+ should understand and follow. This analysis extracts coding conventions
466
+ and architectural patterns from the codebase.
467
+
468
+ Args:
469
+ characteristics: ProjectCharacteristics object to update
470
+ """
471
+ source_extensions = {
472
+ '.py': 'python',
473
+ '.js': 'javascript',
474
+ '.ts': 'typescript',
475
+ '.jsx': 'react',
476
+ '.tsx': 'react',
477
+ '.rs': 'rust',
478
+ '.java': 'java',
479
+ '.go': 'go',
480
+ '.php': 'php',
481
+ '.rb': 'ruby',
482
+ '.cpp': 'cpp',
483
+ '.cc': 'cpp',
484
+ '.c': 'c'
485
+ }
486
+
487
+ # Find source files
488
+ source_files = []
489
+ languages_found = set()
490
+
491
+ for ext, lang in source_extensions.items():
492
+ files = list(self.working_directory.rglob(f'*{ext}'))
493
+ # Filter out node_modules, .git, etc.
494
+ files = [f for f in files if not any(part.startswith('.') or part == 'node_modules'
495
+ for part in f.parts)]
496
+ source_files.extend(files)
497
+ if files:
498
+ languages_found.add(lang)
499
+
500
+ # Update languages found
501
+ characteristics.languages.extend([lang for lang in languages_found
502
+ if lang not in characteristics.languages])
503
+
504
+ # Analyze a sample of source files for patterns
505
+ sample_files = source_files[:20] # Don't analyze too many files
506
+
507
+ framework_mentions = Counter()
508
+ pattern_mentions = Counter()
509
+
510
+ for file_path in sample_files:
511
+ try:
512
+ content = file_path.read_text(encoding='utf-8', errors='ignore')
513
+
514
+ # Look for framework patterns
515
+ for framework, patterns in self.FRAMEWORK_PATTERNS.items():
516
+ if any(pattern in content for pattern in patterns):
517
+ framework_mentions[framework] += 1
518
+
519
+ # Look for database patterns
520
+ for db, patterns in self.DATABASE_PATTERNS.items():
521
+ if any(pattern in content for pattern in patterns):
522
+ if db not in characteristics.databases:
523
+ characteristics.databases.append(db)
524
+
525
+ # Look for common patterns
526
+ if 'class ' in content and 'def __init__' in content:
527
+ pattern_mentions['object_oriented'] += 1
528
+ if '@app.route' in content or 'app.get(' in content:
529
+ pattern_mentions['web_routes'] += 1
530
+ if 'async def' in content or 'async function' in content:
531
+ pattern_mentions['async_programming'] += 1
532
+ if 'import pytest' in content or 'describe(' in content:
533
+ pattern_mentions['unit_testing'] += 1
534
+
535
+ except Exception as e:
536
+ self.logger.debug(f"Error analyzing {file_path}: {e}")
537
+ continue
538
+
539
+ # Add discovered frameworks
540
+ for framework, count in framework_mentions.most_common(5):
541
+ if framework not in characteristics.frameworks:
542
+ characteristics.frameworks.append(framework)
543
+
544
+ # Add coding conventions based on patterns found
545
+ for pattern, count in pattern_mentions.most_common():
546
+ if count >= 2: # Pattern appears in multiple files
547
+ characteristics.code_conventions.append(pattern.replace('_', ' ').title())
548
+
549
+ def _analyze_dependencies(self, characteristics: ProjectCharacteristics) -> None:
550
+ """Analyze dependencies for integration patterns.
551
+
552
+ Args:
553
+ characteristics: ProjectCharacteristics object to update
554
+ """
555
+ # This is partially covered by config file analysis
556
+ # Here we can add more sophisticated dependency analysis
557
+
558
+ # Look for common integration patterns in dependencies
559
+ api_indicators = [
560
+ 'requests', 'axios', 'fetch', 'http', 'urllib',
561
+ 'rest', 'graphql', 'grpc', 'soap'
562
+ ]
563
+
564
+ for dep in characteristics.key_dependencies:
565
+ dep_lower = dep.lower()
566
+ for indicator in api_indicators:
567
+ if indicator in dep_lower:
568
+ if 'REST API' not in characteristics.api_patterns:
569
+ characteristics.api_patterns.append('REST API')
570
+ break
571
+
572
+ def _analyze_testing_patterns(self, characteristics: ProjectCharacteristics) -> None:
573
+ """Analyze testing patterns and frameworks.
574
+
575
+ Args:
576
+ characteristics: ProjectCharacteristics object to update
577
+ """
578
+ test_dirs = ['tests', 'test', '__tests__', 'spec']
579
+ test_patterns = []
580
+
581
+ for test_dir in test_dirs:
582
+ test_path = self.working_directory / test_dir
583
+ if test_path.exists() and test_path.is_dir():
584
+ test_patterns.append(f"Tests in /{test_dir}/ directory")
585
+
586
+ # Look for test files to understand patterns
587
+ test_files = list(test_path.rglob('*.py')) + list(test_path.rglob('*.js')) + list(test_path.rglob('*.ts'))
588
+
589
+ for test_file in test_files[:5]: # Sample a few test files
590
+ try:
591
+ content = test_file.read_text(encoding='utf-8', errors='ignore')
592
+
593
+ if 'def test_' in content:
594
+ test_patterns.append("Python unittest pattern")
595
+ if 'describe(' in content and 'it(' in content:
596
+ test_patterns.append("BDD test pattern")
597
+ if '@pytest.fixture' in content:
598
+ test_patterns.append("pytest fixtures")
599
+ if 'beforeEach(' in content or 'beforeAll(' in content:
600
+ test_patterns.append("Setup/teardown patterns")
601
+
602
+ except Exception:
603
+ continue
604
+
605
+ characteristics.test_patterns = list(set(test_patterns))
606
+
607
+ def _analyze_documentation(self, characteristics: ProjectCharacteristics) -> None:
608
+ """Analyze documentation files.
609
+
610
+ Args:
611
+ characteristics: ProjectCharacteristics object to update
612
+ """
613
+ doc_patterns = [
614
+ 'README.md', 'README.rst', 'README.txt',
615
+ 'CONTRIBUTING.md', 'CHANGELOG.md', 'HISTORY.md',
616
+ 'docs/', 'documentation/', 'wiki/'
617
+ ]
618
+
619
+ doc_files = []
620
+ for pattern in doc_patterns:
621
+ doc_path = self.working_directory / pattern
622
+ if doc_path.exists():
623
+ if doc_path.is_file():
624
+ doc_files.append(pattern)
625
+ elif doc_path.is_dir():
626
+ # Find markdown files in doc directories
627
+ md_files = list(doc_path.rglob('*.md'))[:10]
628
+ doc_files.extend([str(f.relative_to(self.working_directory)) for f in md_files])
629
+
630
+ characteristics.documentation_files = doc_files
631
+
632
+ def _infer_architecture_type(self, characteristics: ProjectCharacteristics) -> None:
633
+ """Infer architecture type based on discovered patterns.
634
+
635
+ Args:
636
+ characteristics: ProjectCharacteristics object to update
637
+ """
638
+ # Simple architecture inference based on patterns
639
+ if any(fw in characteristics.web_frameworks for fw in ['flask', 'django', 'express', 'fastapi']):
640
+ if 'api' in characteristics.key_directories:
641
+ characteristics.architecture_type = "REST API Service"
642
+ else:
643
+ characteristics.architecture_type = "Web Application"
644
+ elif 'services' in characteristics.key_directories:
645
+ characteristics.architecture_type = "Service-Oriented Architecture"
646
+ elif 'modules' in characteristics.key_directories or 'packages' in characteristics.key_directories:
647
+ characteristics.architecture_type = "Modular Architecture"
648
+ elif characteristics.primary_language == 'python' and 'cli' in characteristics.main_modules:
649
+ characteristics.architecture_type = "CLI Application"
650
+ elif any('react' in fw.lower() for fw in characteristics.frameworks):
651
+ characteristics.architecture_type = "Single Page Application"
652
+ else:
653
+ characteristics.architecture_type = "Standard Application"
654
+
655
+ def _extract_project_terminology(self, characteristics: ProjectCharacteristics) -> None:
656
+ """Extract project-specific terminology from various sources.
657
+
658
+ WHY: Projects often have domain-specific terminology that agents should
659
+ understand and use consistently.
660
+
661
+ Args:
662
+ characteristics: ProjectCharacteristics object to update
663
+ """
664
+ terminology = set()
665
+
666
+ # Extract from project name
667
+ project_words = re.findall(r'[A-Z][a-z]+|[a-z]+', characteristics.project_name)
668
+ terminology.update(project_words)
669
+
670
+ # Extract from directory names
671
+ for dir_name in characteristics.key_directories:
672
+ words = re.findall(r'[A-Z][a-z]+|[a-z]+', dir_name)
673
+ terminology.update(words)
674
+
675
+ # Extract from main modules
676
+ for module in characteristics.main_modules:
677
+ words = re.findall(r'[A-Z][a-z]+|[a-z]+', module)
678
+ terminology.update(words)
679
+
680
+ # Filter out common words and keep domain-specific terms
681
+ common_words = {
682
+ 'src', 'lib', 'app', 'main', 'test', 'tests', 'docs', 'config',
683
+ 'utils', 'helpers', 'core', 'base', 'common', 'shared', 'public',
684
+ 'private', 'static', 'assets', 'build', 'dist', 'node', 'modules'
685
+ }
686
+
687
+ domain_terms = [term for term in terminology
688
+ if len(term) > 3 and term.lower() not in common_words]
689
+
690
+ characteristics.project_terminology = list(set(domain_terms))[:10] # Limit to most relevant
691
+
692
+ def get_project_context_summary(self) -> str:
693
+ """Get a concise summary of project context for memory templates.
694
+
695
+ WHY: Provides a formatted summary specifically designed for inclusion
696
+ in agent memory templates, focusing on the most relevant characteristics.
697
+
698
+ Returns:
699
+ str: Formatted project context summary
700
+ """
701
+ characteristics = self.analyze_project()
702
+
703
+ summary_parts = []
704
+
705
+ # Basic project info
706
+ lang_info = characteristics.primary_language or "mixed"
707
+ if characteristics.languages and len(characteristics.languages) > 1:
708
+ lang_info = f"{lang_info} (with {', '.join(characteristics.languages[1:3])})"
709
+
710
+ summary_parts.append(f"{characteristics.project_name}: {lang_info} {characteristics.architecture_type.lower()}")
711
+
712
+ # Key directories and modules
713
+ if characteristics.main_modules:
714
+ modules_str = ", ".join(characteristics.main_modules[:4])
715
+ summary_parts.append(f"- Main modules: {modules_str}")
716
+
717
+ # Frameworks and tools
718
+ if characteristics.frameworks or characteristics.web_frameworks:
719
+ all_frameworks = characteristics.frameworks + characteristics.web_frameworks
720
+ frameworks_str = ", ".join(all_frameworks[:3])
721
+ summary_parts.append(f"- Uses: {frameworks_str}")
722
+
723
+ # Testing
724
+ if characteristics.testing_framework:
725
+ summary_parts.append(f"- Testing: {characteristics.testing_framework}")
726
+ elif characteristics.test_patterns:
727
+ summary_parts.append(f"- Testing: {characteristics.test_patterns[0]}")
728
+
729
+ # Key patterns
730
+ if characteristics.code_conventions:
731
+ patterns_str = ", ".join(characteristics.code_conventions[:2])
732
+ summary_parts.append(f"- Key patterns: {patterns_str}")
733
+
734
+ return "\n".join(summary_parts)
735
+
736
+ def get_important_files_for_context(self) -> List[str]:
737
+ """Get list of important files that should be considered for memory context.
738
+
739
+ WHY: Instead of hardcoding which files to analyze for memory creation,
740
+ this method dynamically determines the most relevant files based on
741
+ the actual project structure.
742
+
743
+ Returns:
744
+ List[str]: List of file paths relative to project root
745
+ """
746
+ characteristics = self.analyze_project()
747
+ important_files = []
748
+
749
+ # Always include standard documentation
750
+ standard_docs = ['README.md', 'CONTRIBUTING.md', 'CHANGELOG.md']
751
+ for doc in standard_docs:
752
+ if (self.working_directory / doc).exists():
753
+ important_files.append(doc)
754
+
755
+ # Include configuration files
756
+ important_files.extend(characteristics.important_configs)
757
+
758
+ # Include project-specific documentation
759
+ important_files.extend(characteristics.documentation_files[:5])
760
+
761
+ # Include entry points
762
+ important_files.extend(characteristics.entry_points)
763
+
764
+ # Look for architecture documentation
765
+ arch_patterns = ['ARCHITECTURE.md', 'docs/architecture.md', 'docs/STRUCTURE.md']
766
+ for pattern in arch_patterns:
767
+ if (self.working_directory / pattern).exists():
768
+ important_files.append(pattern)
769
+
770
+ # Remove duplicates and return
771
+ return list(set(important_files))