claude-dev-cli 0.16.2__py3-none-any.whl → 0.18.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.
Potentially problematic release.
This version of claude-dev-cli might be problematic. Click here for more details.
- claude_dev_cli/__init__.py +1 -1
- claude_dev_cli/cli.py +424 -0
- claude_dev_cli/logging/__init__.py +6 -0
- claude_dev_cli/logging/logger.py +84 -0
- claude_dev_cli/logging/markdown_logger.py +131 -0
- claude_dev_cli/notifications/__init__.py +6 -0
- claude_dev_cli/notifications/notifier.py +69 -0
- claude_dev_cli/notifications/ntfy.py +87 -0
- claude_dev_cli/project/__init__.py +10 -0
- claude_dev_cli/project/bug_tracker.py +458 -0
- claude_dev_cli/project/context_gatherer.py +535 -0
- claude_dev_cli/project/executor.py +370 -0
- claude_dev_cli/tickets/__init__.py +7 -0
- claude_dev_cli/tickets/backend.py +229 -0
- claude_dev_cli/tickets/markdown.py +309 -0
- claude_dev_cli/tickets/repo_tickets.py +361 -0
- claude_dev_cli/vcs/__init__.py +6 -0
- claude_dev_cli/vcs/git.py +172 -0
- claude_dev_cli/vcs/manager.py +90 -0
- {claude_dev_cli-0.16.2.dist-info → claude_dev_cli-0.18.0.dist-info}/METADATA +335 -4
- {claude_dev_cli-0.16.2.dist-info → claude_dev_cli-0.18.0.dist-info}/RECORD +25 -8
- {claude_dev_cli-0.16.2.dist-info → claude_dev_cli-0.18.0.dist-info}/WHEEL +0 -0
- {claude_dev_cli-0.16.2.dist-info → claude_dev_cli-0.18.0.dist-info}/entry_points.txt +0 -0
- {claude_dev_cli-0.16.2.dist-info → claude_dev_cli-0.18.0.dist-info}/licenses/LICENSE +0 -0
- {claude_dev_cli-0.16.2.dist-info → claude_dev_cli-0.18.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
"""Intelligent context gathering for ticket execution.
|
|
2
|
+
|
|
3
|
+
Pre-processes tickets to gather relevant context from the codebase,
|
|
4
|
+
including similar code, dependencies, framework patterns, and project structure.
|
|
5
|
+
This context helps the AI generate better, more consistent implementations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import List, Dict, Any, Optional, Set
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from collections import defaultdict
|
|
14
|
+
|
|
15
|
+
from claude_dev_cli.tickets.backend import Ticket
|
|
16
|
+
from claude_dev_cli.core import ClaudeClient
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class CodeContext:
|
|
21
|
+
"""Context gathered from existing codebase."""
|
|
22
|
+
|
|
23
|
+
# Project structure
|
|
24
|
+
project_root: Path
|
|
25
|
+
language: str
|
|
26
|
+
framework: Optional[str] = None
|
|
27
|
+
|
|
28
|
+
# Dependencies
|
|
29
|
+
dependencies: List[str] = field(default_factory=list)
|
|
30
|
+
installed_packages: Dict[str, str] = field(default_factory=dict) # name: version
|
|
31
|
+
|
|
32
|
+
# Similar code
|
|
33
|
+
similar_files: List[Dict[str, Any]] = field(default_factory=list) # path, similarity, purpose
|
|
34
|
+
similar_functions: List[Dict[str, Any]] = field(default_factory=list) # name, file, signature
|
|
35
|
+
similar_patterns: List[str] = field(default_factory=list) # patterns found in codebase
|
|
36
|
+
|
|
37
|
+
# Project conventions
|
|
38
|
+
naming_conventions: Dict[str, str] = field(default_factory=dict) # type: pattern
|
|
39
|
+
directory_structure: Dict[str, List[str]] = field(default_factory=dict) # purpose: paths
|
|
40
|
+
common_imports: List[str] = field(default_factory=list)
|
|
41
|
+
|
|
42
|
+
# Related files
|
|
43
|
+
related_models: List[str] = field(default_factory=list)
|
|
44
|
+
related_views: List[str] = field(default_factory=list)
|
|
45
|
+
related_controllers: List[str] = field(default_factory=list)
|
|
46
|
+
related_tests: List[str] = field(default_factory=list)
|
|
47
|
+
|
|
48
|
+
# Configuration
|
|
49
|
+
config_files: List[str] = field(default_factory=list)
|
|
50
|
+
env_variables: List[str] = field(default_factory=list)
|
|
51
|
+
|
|
52
|
+
def format_for_prompt(self) -> str:
|
|
53
|
+
"""Format context for AI prompt."""
|
|
54
|
+
sections = []
|
|
55
|
+
|
|
56
|
+
sections.append(f"## Project Context\n")
|
|
57
|
+
sections.append(f"**Language:** {self.language}")
|
|
58
|
+
if self.framework:
|
|
59
|
+
sections.append(f"**Framework:** {self.framework}")
|
|
60
|
+
sections.append(f"**Root:** {self.project_root}\n")
|
|
61
|
+
|
|
62
|
+
if self.dependencies:
|
|
63
|
+
sections.append(f"\n## Dependencies ({len(self.dependencies)})")
|
|
64
|
+
for dep in self.dependencies[:20]: # Limit to 20
|
|
65
|
+
version = self.installed_packages.get(dep, "unknown")
|
|
66
|
+
sections.append(f"- {dep} ({version})")
|
|
67
|
+
|
|
68
|
+
if self.directory_structure:
|
|
69
|
+
sections.append(f"\n## Project Structure")
|
|
70
|
+
for purpose, paths in self.directory_structure.items():
|
|
71
|
+
sections.append(f"**{purpose}:** {', '.join(paths[:5])}")
|
|
72
|
+
|
|
73
|
+
if self.naming_conventions:
|
|
74
|
+
sections.append(f"\n## Naming Conventions")
|
|
75
|
+
for type_name, pattern in self.naming_conventions.items():
|
|
76
|
+
sections.append(f"- {type_name}: {pattern}")
|
|
77
|
+
|
|
78
|
+
if self.similar_files:
|
|
79
|
+
sections.append(f"\n## Similar Existing Code")
|
|
80
|
+
for file_info in self.similar_files[:5]:
|
|
81
|
+
sections.append(f"- {file_info['path']}: {file_info['purpose']}")
|
|
82
|
+
|
|
83
|
+
if self.similar_functions:
|
|
84
|
+
sections.append(f"\n## Related Functions")
|
|
85
|
+
for func in self.similar_functions[:10]:
|
|
86
|
+
sections.append(f"- {func['name']} in {func['file']}")
|
|
87
|
+
|
|
88
|
+
if self.common_imports:
|
|
89
|
+
sections.append(f"\n## Common Imports")
|
|
90
|
+
sections.append(", ".join(self.common_imports[:15]))
|
|
91
|
+
|
|
92
|
+
if self.related_models or self.related_views or self.related_controllers:
|
|
93
|
+
sections.append(f"\n## Related Files")
|
|
94
|
+
if self.related_models:
|
|
95
|
+
sections.append(f"Models: {', '.join(self.related_models[:5])}")
|
|
96
|
+
if self.related_views:
|
|
97
|
+
sections.append(f"Views: {', '.join(self.related_views[:5])}")
|
|
98
|
+
if self.related_controllers:
|
|
99
|
+
sections.append(f"Controllers: {', '.join(self.related_controllers[:5])}")
|
|
100
|
+
|
|
101
|
+
return "\n".join(sections)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TicketContextGatherer:
|
|
105
|
+
"""Gathers relevant context for ticket implementation.
|
|
106
|
+
|
|
107
|
+
Analyzes the existing codebase to provide context that helps
|
|
108
|
+
the AI generate better, more consistent code.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
def __init__(self, project_root: Optional[Path] = None):
|
|
112
|
+
"""Initialize context gatherer.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
project_root: Root of the project (default: current directory)
|
|
116
|
+
"""
|
|
117
|
+
self.project_root = project_root or Path.cwd()
|
|
118
|
+
self._file_cache: Dict[str, str] = {}
|
|
119
|
+
self._extension_map = {
|
|
120
|
+
'.py': 'Python',
|
|
121
|
+
'.js': 'JavaScript',
|
|
122
|
+
'.ts': 'TypeScript',
|
|
123
|
+
'.go': 'Go',
|
|
124
|
+
'.rs': 'Rust',
|
|
125
|
+
'.java': 'Java',
|
|
126
|
+
'.rb': 'Ruby',
|
|
127
|
+
'.php': 'PHP',
|
|
128
|
+
'.cs': 'C#',
|
|
129
|
+
'.cpp': 'C++',
|
|
130
|
+
'.c': 'C'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
def gather_context(self, ticket: Ticket, ai_client: Optional[ClaudeClient] = None) -> CodeContext:
|
|
134
|
+
"""Gather all relevant context for implementing a ticket.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
ticket: Ticket to gather context for
|
|
138
|
+
ai_client: Optional AI client for semantic analysis
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
CodeContext with all gathered information
|
|
142
|
+
"""
|
|
143
|
+
context = CodeContext(project_root=self.project_root, language=self._detect_language())
|
|
144
|
+
|
|
145
|
+
# Gather different types of context
|
|
146
|
+
context.framework = self._detect_framework(context.language)
|
|
147
|
+
context.dependencies = self._find_dependencies(context.language)
|
|
148
|
+
context.installed_packages = self._get_installed_packages(context.language)
|
|
149
|
+
context.directory_structure = self._analyze_directory_structure()
|
|
150
|
+
context.naming_conventions = self._detect_naming_conventions(context.language)
|
|
151
|
+
context.common_imports = self._find_common_imports(context.language)
|
|
152
|
+
context.config_files = self._find_config_files()
|
|
153
|
+
|
|
154
|
+
# Find similar code based on ticket description
|
|
155
|
+
if ticket.description or ticket.title:
|
|
156
|
+
search_terms = self._extract_search_terms(ticket)
|
|
157
|
+
context.similar_files = self._find_similar_files(search_terms, context.language)
|
|
158
|
+
context.similar_functions = self._find_similar_functions(search_terms, context.language)
|
|
159
|
+
context.similar_patterns = self._find_patterns(search_terms)
|
|
160
|
+
|
|
161
|
+
# Find related files based on ticket type
|
|
162
|
+
if ticket.ticket_type in ['feature', 'bug', 'refactor']:
|
|
163
|
+
context.related_models = self._find_models(context.language, context.framework)
|
|
164
|
+
context.related_views = self._find_views(context.language, context.framework)
|
|
165
|
+
context.related_controllers = self._find_controllers(context.language, context.framework)
|
|
166
|
+
context.related_tests = self._find_tests(context.language)
|
|
167
|
+
|
|
168
|
+
# Use AI for semantic similarity (optional)
|
|
169
|
+
if ai_client:
|
|
170
|
+
context = self._enhance_with_ai(context, ticket, ai_client)
|
|
171
|
+
|
|
172
|
+
return context
|
|
173
|
+
|
|
174
|
+
def _detect_language(self) -> str:
|
|
175
|
+
"""Detect primary programming language."""
|
|
176
|
+
extensions_count = defaultdict(int)
|
|
177
|
+
|
|
178
|
+
for file_path in self.project_root.rglob('*'):
|
|
179
|
+
if file_path.is_file() and not self._should_ignore(file_path):
|
|
180
|
+
ext = file_path.suffix.lower()
|
|
181
|
+
if ext in self._extension_map:
|
|
182
|
+
extensions_count[ext] += 1
|
|
183
|
+
|
|
184
|
+
if not extensions_count:
|
|
185
|
+
return "Unknown"
|
|
186
|
+
|
|
187
|
+
primary_ext = max(extensions_count, key=extensions_count.get)
|
|
188
|
+
return self._extension_map.get(primary_ext, "Unknown")
|
|
189
|
+
|
|
190
|
+
def _detect_framework(self, language: str) -> Optional[str]:
|
|
191
|
+
"""Detect framework being used."""
|
|
192
|
+
framework_indicators = {
|
|
193
|
+
'Python': {
|
|
194
|
+
'Django': ['manage.py', 'settings.py', 'wsgi.py'],
|
|
195
|
+
'Flask': ['app.py', 'wsgi.py', '__init__.py'],
|
|
196
|
+
'FastAPI': ['main.py', 'app.py'],
|
|
197
|
+
},
|
|
198
|
+
'JavaScript': {
|
|
199
|
+
'React': ['package.json'], # Check for "react" in package.json
|
|
200
|
+
'Vue': ['vue.config.js', 'package.json'],
|
|
201
|
+
'Express': ['package.json'], # Check for "express"
|
|
202
|
+
},
|
|
203
|
+
'TypeScript': {
|
|
204
|
+
'Next.js': ['next.config.js', 'next.config.ts'],
|
|
205
|
+
'NestJS': ['nest-cli.json'],
|
|
206
|
+
},
|
|
207
|
+
'Go': {
|
|
208
|
+
'Gin': ['go.mod'], # Check imports
|
|
209
|
+
'Echo': ['go.mod'],
|
|
210
|
+
},
|
|
211
|
+
'Ruby': {
|
|
212
|
+
'Rails': ['Gemfile', 'config.ru', 'app/'],
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if language not in framework_indicators:
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
for framework, files in framework_indicators[language].items():
|
|
220
|
+
if all((self.project_root / f).exists() or
|
|
221
|
+
any(self.project_root.rglob(f)) for f in files):
|
|
222
|
+
return framework
|
|
223
|
+
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
def _find_dependencies(self, language: str) -> List[str]:
|
|
227
|
+
"""Find project dependencies."""
|
|
228
|
+
dep_files = {
|
|
229
|
+
'Python': ['requirements.txt', 'pyproject.toml', 'setup.py', 'Pipfile'],
|
|
230
|
+
'JavaScript': ['package.json'],
|
|
231
|
+
'TypeScript': ['package.json'],
|
|
232
|
+
'Go': ['go.mod'],
|
|
233
|
+
'Ruby': ['Gemfile'],
|
|
234
|
+
'PHP': ['composer.json'],
|
|
235
|
+
'Rust': ['Cargo.toml'],
|
|
236
|
+
'Java': ['pom.xml', 'build.gradle'],
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
dependencies = []
|
|
240
|
+
|
|
241
|
+
for dep_file in dep_files.get(language, []):
|
|
242
|
+
file_path = self.project_root / dep_file
|
|
243
|
+
if file_path.exists():
|
|
244
|
+
deps = self._parse_dependency_file(file_path, language)
|
|
245
|
+
dependencies.extend(deps)
|
|
246
|
+
|
|
247
|
+
return list(set(dependencies)) # Remove duplicates
|
|
248
|
+
|
|
249
|
+
def _parse_dependency_file(self, file_path: Path, language: str) -> List[str]:
|
|
250
|
+
"""Parse a dependency file to extract package names."""
|
|
251
|
+
try:
|
|
252
|
+
content = file_path.read_text()
|
|
253
|
+
deps = []
|
|
254
|
+
|
|
255
|
+
if file_path.name == 'requirements.txt':
|
|
256
|
+
for line in content.splitlines():
|
|
257
|
+
line = line.strip()
|
|
258
|
+
if line and not line.startswith('#'):
|
|
259
|
+
# Extract package name (before ==, >=, etc.)
|
|
260
|
+
pkg = re.split(r'[=<>!]', line)[0].strip()
|
|
261
|
+
deps.append(pkg)
|
|
262
|
+
|
|
263
|
+
elif file_path.name == 'package.json':
|
|
264
|
+
import json
|
|
265
|
+
data = json.loads(content)
|
|
266
|
+
deps.extend(data.get('dependencies', {}).keys())
|
|
267
|
+
deps.extend(data.get('devDependencies', {}).keys())
|
|
268
|
+
|
|
269
|
+
elif file_path.name == 'pyproject.toml':
|
|
270
|
+
# Simple parsing - look for dependencies section
|
|
271
|
+
in_deps = False
|
|
272
|
+
for line in content.splitlines():
|
|
273
|
+
if 'dependencies' in line:
|
|
274
|
+
in_deps = True
|
|
275
|
+
elif in_deps and '=' in line:
|
|
276
|
+
pkg = line.split('=')[0].strip(' "\'')
|
|
277
|
+
if pkg and not pkg.startswith('['):
|
|
278
|
+
deps.append(pkg)
|
|
279
|
+
elif in_deps and line.strip().startswith('['):
|
|
280
|
+
in_deps = False
|
|
281
|
+
|
|
282
|
+
return deps
|
|
283
|
+
|
|
284
|
+
except Exception:
|
|
285
|
+
return []
|
|
286
|
+
|
|
287
|
+
def _get_installed_packages(self, language: str) -> Dict[str, str]:
|
|
288
|
+
"""Get currently installed packages with versions."""
|
|
289
|
+
# This would ideally check the actual environment
|
|
290
|
+
# For now, return empty dict (can be enhanced later)
|
|
291
|
+
return {}
|
|
292
|
+
|
|
293
|
+
def _analyze_directory_structure(self) -> Dict[str, List[str]]:
|
|
294
|
+
"""Analyze project directory structure."""
|
|
295
|
+
structure = defaultdict(list)
|
|
296
|
+
|
|
297
|
+
common_patterns = {
|
|
298
|
+
'models': ['models/', 'model/', 'entities/', 'domain/'],
|
|
299
|
+
'views': ['views/', 'templates/', 'pages/'],
|
|
300
|
+
'controllers': ['controllers/', 'handlers/', 'routes/'],
|
|
301
|
+
'services': ['services/', 'business/', 'logic/'],
|
|
302
|
+
'utils': ['utils/', 'helpers/', 'common/'],
|
|
303
|
+
'tests': ['tests/', 'test/', '__tests__/', 'spec/'],
|
|
304
|
+
'config': ['config/', 'settings/', 'conf/'],
|
|
305
|
+
'static': ['static/', 'public/', 'assets/'],
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
for purpose, patterns in common_patterns.items():
|
|
309
|
+
for pattern in patterns:
|
|
310
|
+
paths = list(self.project_root.glob(f"**/{pattern}"))
|
|
311
|
+
if paths:
|
|
312
|
+
structure[purpose].extend([str(p.relative_to(self.project_root)) for p in paths[:5]])
|
|
313
|
+
|
|
314
|
+
return dict(structure)
|
|
315
|
+
|
|
316
|
+
def _detect_naming_conventions(self, language: str) -> Dict[str, str]:
|
|
317
|
+
"""Detect naming conventions used in the project."""
|
|
318
|
+
conventions = {}
|
|
319
|
+
|
|
320
|
+
# Sample files to detect patterns
|
|
321
|
+
sample_files = list(self.project_root.rglob('*.py' if language == 'Python' else '*'))[:50]
|
|
322
|
+
|
|
323
|
+
# Detect function naming
|
|
324
|
+
func_names = []
|
|
325
|
+
class_names = []
|
|
326
|
+
|
|
327
|
+
for file_path in sample_files:
|
|
328
|
+
if file_path.is_file() and not self._should_ignore(file_path):
|
|
329
|
+
try:
|
|
330
|
+
content = file_path.read_text()
|
|
331
|
+
|
|
332
|
+
# Extract function names
|
|
333
|
+
func_names.extend(re.findall(r'def\s+(\w+)\s*\(', content))
|
|
334
|
+
func_names.extend(re.findall(r'function\s+(\w+)\s*\(', content))
|
|
335
|
+
|
|
336
|
+
# Extract class names
|
|
337
|
+
class_names.extend(re.findall(r'class\s+(\w+)', content))
|
|
338
|
+
|
|
339
|
+
except Exception:
|
|
340
|
+
continue
|
|
341
|
+
|
|
342
|
+
# Analyze patterns
|
|
343
|
+
if func_names:
|
|
344
|
+
if all('_' in name or name.islower() for name in func_names[:10]):
|
|
345
|
+
conventions['functions'] = 'snake_case'
|
|
346
|
+
elif all(name[0].islower() and any(c.isupper() for c in name[1:]) for name in func_names[:10] if len(name) > 1):
|
|
347
|
+
conventions['functions'] = 'camelCase'
|
|
348
|
+
|
|
349
|
+
if class_names:
|
|
350
|
+
if all(name[0].isupper() for name in class_names[:10]):
|
|
351
|
+
conventions['classes'] = 'PascalCase'
|
|
352
|
+
|
|
353
|
+
return conventions
|
|
354
|
+
|
|
355
|
+
def _find_common_imports(self, language: str) -> List[str]:
|
|
356
|
+
"""Find most commonly used imports."""
|
|
357
|
+
import_counts = defaultdict(int)
|
|
358
|
+
|
|
359
|
+
sample_files = list(self.project_root.rglob('*.py' if language == 'Python' else '*'))[:100]
|
|
360
|
+
|
|
361
|
+
for file_path in sample_files:
|
|
362
|
+
if file_path.is_file() and not self._should_ignore(file_path):
|
|
363
|
+
try:
|
|
364
|
+
content = file_path.read_text()
|
|
365
|
+
|
|
366
|
+
if language == 'Python':
|
|
367
|
+
imports = re.findall(r'(?:from|import)\s+([\w.]+)', content)
|
|
368
|
+
for imp in imports:
|
|
369
|
+
import_counts[imp.split('.')[0]] += 1
|
|
370
|
+
|
|
371
|
+
except Exception:
|
|
372
|
+
continue
|
|
373
|
+
|
|
374
|
+
# Return top 15 most common
|
|
375
|
+
sorted_imports = sorted(import_counts.items(), key=lambda x: x[1], reverse=True)
|
|
376
|
+
return [imp[0] for imp in sorted_imports[:15]]
|
|
377
|
+
|
|
378
|
+
def _find_config_files(self) -> List[str]:
|
|
379
|
+
"""Find configuration files."""
|
|
380
|
+
config_patterns = [
|
|
381
|
+
'config.*', 'settings.*', '.env*', '*.config.*',
|
|
382
|
+
'docker-compose.yml', 'Dockerfile', 'Makefile'
|
|
383
|
+
]
|
|
384
|
+
|
|
385
|
+
config_files = []
|
|
386
|
+
for pattern in config_patterns:
|
|
387
|
+
config_files.extend(str(p.relative_to(self.project_root))
|
|
388
|
+
for p in self.project_root.glob(pattern))
|
|
389
|
+
|
|
390
|
+
return config_files[:10]
|
|
391
|
+
|
|
392
|
+
def _extract_search_terms(self, ticket: Ticket) -> List[str]:
|
|
393
|
+
"""Extract key search terms from ticket."""
|
|
394
|
+
text = f"{ticket.title} {ticket.description}"
|
|
395
|
+
|
|
396
|
+
# Extract important words (exclude common words)
|
|
397
|
+
stop_words = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with', 'by'}
|
|
398
|
+
words = re.findall(r'\b\w{4,}\b', text.lower())
|
|
399
|
+
|
|
400
|
+
return [w for w in words if w not in stop_words][:10]
|
|
401
|
+
|
|
402
|
+
def _find_similar_files(self, search_terms: List[str], language: str) -> List[Dict[str, Any]]:
|
|
403
|
+
"""Find files with similar purpose."""
|
|
404
|
+
similar = []
|
|
405
|
+
|
|
406
|
+
ext = '.py' if language == 'Python' else '.*'
|
|
407
|
+
files = list(self.project_root.rglob(f'*{ext}'))[:200]
|
|
408
|
+
|
|
409
|
+
for file_path in files:
|
|
410
|
+
if file_path.is_file() and not self._should_ignore(file_path):
|
|
411
|
+
rel_path = str(file_path.relative_to(self.project_root))
|
|
412
|
+
|
|
413
|
+
# Check if filename or path contains search terms
|
|
414
|
+
matches = sum(1 for term in search_terms if term in rel_path.lower())
|
|
415
|
+
|
|
416
|
+
if matches > 0:
|
|
417
|
+
similar.append({
|
|
418
|
+
'path': rel_path,
|
|
419
|
+
'similarity': matches,
|
|
420
|
+
'purpose': self._guess_file_purpose(file_path)
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
# Sort by similarity
|
|
424
|
+
similar.sort(key=lambda x: x['similarity'], reverse=True)
|
|
425
|
+
return similar[:5]
|
|
426
|
+
|
|
427
|
+
def _find_similar_functions(self, search_terms: List[str], language: str) -> List[Dict[str, Any]]:
|
|
428
|
+
"""Find functions with similar names or purposes."""
|
|
429
|
+
functions = []
|
|
430
|
+
|
|
431
|
+
ext = '.py' if language == 'Python' else '.*'
|
|
432
|
+
files = list(self.project_root.rglob(f'*{ext}'))[:100]
|
|
433
|
+
|
|
434
|
+
for file_path in files:
|
|
435
|
+
if file_path.is_file() and not self._should_ignore(file_path):
|
|
436
|
+
try:
|
|
437
|
+
content = file_path.read_text()
|
|
438
|
+
|
|
439
|
+
# Extract function definitions
|
|
440
|
+
func_matches = re.findall(r'def\s+(\w+)\s*\((.*?)\):', content)
|
|
441
|
+
|
|
442
|
+
for func_name, params in func_matches:
|
|
443
|
+
if any(term in func_name.lower() for term in search_terms):
|
|
444
|
+
functions.append({
|
|
445
|
+
'name': func_name,
|
|
446
|
+
'file': str(file_path.relative_to(self.project_root)),
|
|
447
|
+
'signature': f"{func_name}({params})"
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
except Exception:
|
|
451
|
+
continue
|
|
452
|
+
|
|
453
|
+
return functions[:10]
|
|
454
|
+
|
|
455
|
+
def _find_patterns(self, search_terms: List[str]) -> List[str]:
|
|
456
|
+
"""Find code patterns related to search terms."""
|
|
457
|
+
# This could be enhanced with more sophisticated pattern detection
|
|
458
|
+
patterns = []
|
|
459
|
+
|
|
460
|
+
# Look for common patterns like decorators, base classes, etc.
|
|
461
|
+
# For now, return empty (can be enhanced)
|
|
462
|
+
|
|
463
|
+
return patterns
|
|
464
|
+
|
|
465
|
+
def _find_models(self, language: str, framework: Optional[str]) -> List[str]:
|
|
466
|
+
"""Find model files."""
|
|
467
|
+
return self._find_files_by_pattern(['models/', 'model/', 'entities/'], language)
|
|
468
|
+
|
|
469
|
+
def _find_views(self, language: str, framework: Optional[str]) -> List[str]:
|
|
470
|
+
"""Find view/template files."""
|
|
471
|
+
return self._find_files_by_pattern(['views/', 'templates/', 'pages/'], language)
|
|
472
|
+
|
|
473
|
+
def _find_controllers(self, language: str, framework: Optional[str]) -> List[str]:
|
|
474
|
+
"""Find controller/handler files."""
|
|
475
|
+
return self._find_files_by_pattern(['controllers/', 'handlers/', 'routes/'], language)
|
|
476
|
+
|
|
477
|
+
def _find_tests(self, language: str) -> List[str]:
|
|
478
|
+
"""Find test files."""
|
|
479
|
+
return self._find_files_by_pattern(['tests/', 'test/', '__tests__/'], language)
|
|
480
|
+
|
|
481
|
+
def _find_files_by_pattern(self, patterns: List[str], language: str) -> List[str]:
|
|
482
|
+
"""Find files matching directory patterns."""
|
|
483
|
+
files = []
|
|
484
|
+
|
|
485
|
+
for pattern in patterns:
|
|
486
|
+
paths = list(self.project_root.glob(f"**/{pattern}**"))
|
|
487
|
+
files.extend(str(p.relative_to(self.project_root))
|
|
488
|
+
for p in paths if p.is_file())
|
|
489
|
+
|
|
490
|
+
return files[:10]
|
|
491
|
+
|
|
492
|
+
def _guess_file_purpose(self, file_path: Path) -> str:
|
|
493
|
+
"""Guess the purpose of a file from its path and content."""
|
|
494
|
+
path_str = str(file_path).lower()
|
|
495
|
+
|
|
496
|
+
if 'model' in path_str:
|
|
497
|
+
return 'Data model'
|
|
498
|
+
elif 'view' in path_str or 'template' in path_str:
|
|
499
|
+
return 'View/Template'
|
|
500
|
+
elif 'controller' in path_str or 'handler' in path_str:
|
|
501
|
+
return 'Controller/Handler'
|
|
502
|
+
elif 'service' in path_str:
|
|
503
|
+
return 'Business logic'
|
|
504
|
+
elif 'util' in path_str or 'helper' in path_str:
|
|
505
|
+
return 'Utility functions'
|
|
506
|
+
elif 'test' in path_str:
|
|
507
|
+
return 'Tests'
|
|
508
|
+
else:
|
|
509
|
+
return 'General code'
|
|
510
|
+
|
|
511
|
+
def _should_ignore(self, path: Path) -> bool:
|
|
512
|
+
"""Check if path should be ignored."""
|
|
513
|
+
ignore_patterns = [
|
|
514
|
+
'.git', '__pycache__', 'node_modules', '.venv', 'venv',
|
|
515
|
+
'dist', 'build', '.pytest_cache', '.mypy_cache', '.ruff_cache',
|
|
516
|
+
'.tox', 'htmlcov', '.coverage', '*.pyc', '*.pyo', '*.egg-info'
|
|
517
|
+
]
|
|
518
|
+
|
|
519
|
+
path_str = str(path)
|
|
520
|
+
return any(pattern in path_str for pattern in ignore_patterns)
|
|
521
|
+
|
|
522
|
+
def _enhance_with_ai(
|
|
523
|
+
self,
|
|
524
|
+
context: CodeContext,
|
|
525
|
+
ticket: Ticket,
|
|
526
|
+
ai_client: ClaudeClient
|
|
527
|
+
) -> CodeContext:
|
|
528
|
+
"""Use AI to enhance context with semantic understanding."""
|
|
529
|
+
# Could use AI to:
|
|
530
|
+
# 1. Identify most relevant files semantically
|
|
531
|
+
# 2. Suggest architectural patterns
|
|
532
|
+
# 3. Find non-obvious related code
|
|
533
|
+
|
|
534
|
+
# For now, return as-is (can be enhanced later)
|
|
535
|
+
return context
|