nexusforge-cli 1.1.1 → 1.2.1

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 (62) hide show
  1. package/dist/components/App.d.ts.map +1 -1
  2. package/dist/components/App.js +183 -17
  3. package/dist/components/App.js.map +1 -1
  4. package/dist/index.js +462 -10
  5. package/dist/index.js.map +1 -1
  6. package/dist/modules/commandEngine.d.ts +70 -0
  7. package/dist/modules/commandEngine.d.ts.map +1 -0
  8. package/dist/modules/commandEngine.js +672 -0
  9. package/dist/modules/commandEngine.js.map +1 -0
  10. package/dist/modules/contextBuilder.d.ts +51 -0
  11. package/dist/modules/contextBuilder.d.ts.map +1 -0
  12. package/dist/modules/contextBuilder.js +725 -0
  13. package/dist/modules/contextBuilder.js.map +1 -0
  14. package/dist/modules/domainDetector.d.ts +64 -0
  15. package/dist/modules/domainDetector.d.ts.map +1 -0
  16. package/dist/modules/domainDetector.js +722 -0
  17. package/dist/modules/domainDetector.js.map +1 -0
  18. package/dist/modules/fileOperations.d.ts +99 -0
  19. package/dist/modules/fileOperations.d.ts.map +1 -0
  20. package/dist/modules/fileOperations.js +543 -0
  21. package/dist/modules/fileOperations.js.map +1 -0
  22. package/dist/modules/forgeEngine.d.ts +153 -0
  23. package/dist/modules/forgeEngine.d.ts.map +1 -0
  24. package/dist/modules/forgeEngine.js +652 -0
  25. package/dist/modules/forgeEngine.js.map +1 -0
  26. package/dist/modules/gitManager.d.ts +151 -0
  27. package/dist/modules/gitManager.d.ts.map +1 -0
  28. package/dist/modules/gitManager.js +539 -0
  29. package/dist/modules/gitManager.js.map +1 -0
  30. package/dist/modules/index.d.ts +25 -0
  31. package/dist/modules/index.d.ts.map +1 -0
  32. package/dist/modules/index.js +25 -0
  33. package/dist/modules/index.js.map +1 -0
  34. package/dist/modules/prdProcessor.d.ts +125 -0
  35. package/dist/modules/prdProcessor.d.ts.map +1 -0
  36. package/dist/modules/prdProcessor.js +466 -0
  37. package/dist/modules/prdProcessor.js.map +1 -0
  38. package/dist/modules/projectScanner.d.ts +105 -0
  39. package/dist/modules/projectScanner.d.ts.map +1 -0
  40. package/dist/modules/projectScanner.js +859 -0
  41. package/dist/modules/projectScanner.js.map +1 -0
  42. package/dist/modules/safetyGuard.d.ts +83 -0
  43. package/dist/modules/safetyGuard.d.ts.map +1 -0
  44. package/dist/modules/safetyGuard.js +492 -0
  45. package/dist/modules/safetyGuard.js.map +1 -0
  46. package/dist/modules/templateManager.d.ts +78 -0
  47. package/dist/modules/templateManager.d.ts.map +1 -0
  48. package/dist/modules/templateManager.js +556 -0
  49. package/dist/modules/templateManager.js.map +1 -0
  50. package/dist/native/index.d.ts +125 -0
  51. package/dist/native/index.d.ts.map +1 -0
  52. package/dist/native/index.js +164 -0
  53. package/dist/native/index.js.map +1 -0
  54. package/dist/services/executor.d.ts +24 -0
  55. package/dist/services/executor.d.ts.map +1 -1
  56. package/dist/services/executor.js +149 -6
  57. package/dist/services/executor.js.map +1 -1
  58. package/dist/services/fileManager.d.ts +134 -0
  59. package/dist/services/fileManager.d.ts.map +1 -0
  60. package/dist/services/fileManager.js +489 -0
  61. package/dist/services/fileManager.js.map +1 -0
  62. package/package.json +1 -1
@@ -0,0 +1,725 @@
1
+ /**
2
+ * Context Builder for NexusForge CLI
3
+ * Manages context for LLM interactions
4
+ * Ported from Python nexusforge/modules/llm/context_builder.py
5
+ */
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ // ============================================================================
9
+ // CONTEXT BUILDER CLASS
10
+ // ============================================================================
11
+ export class ContextBuilder {
12
+ projectPath;
13
+ maxContextTokens = 30000;
14
+ tokenEstimateRatio = 1.3;
15
+ projectPatterns;
16
+ // Directories to ignore
17
+ static IGNORE_PATTERNS = new Set([
18
+ '.git', '__pycache__', 'node_modules', '.nexusforge',
19
+ 'venv', 'env', '.env', 'dist', 'build', 'target',
20
+ '.venv', 'coverage', '.pytest_cache', '.mypy_cache',
21
+ ]);
22
+ // Extension to language mapping
23
+ static EXT_MAP = {
24
+ '.py': 'python',
25
+ '.js': 'javascript',
26
+ '.ts': 'typescript',
27
+ '.jsx': 'javascript',
28
+ '.tsx': 'typescript',
29
+ '.rs': 'rust',
30
+ '.go': 'go',
31
+ '.java': 'java',
32
+ '.cpp': 'cpp',
33
+ '.c': 'c',
34
+ '.cs': 'csharp',
35
+ '.rb': 'ruby',
36
+ '.php': 'php',
37
+ };
38
+ constructor(projectPath) {
39
+ this.projectPath = path.resolve(projectPath);
40
+ this.projectPatterns = this.detectProjectPatterns();
41
+ }
42
+ // ========================================================================
43
+ // MAIN CONTEXT BUILDING
44
+ // ========================================================================
45
+ buildContextForQuery(query) {
46
+ // Extract file references from query
47
+ const referencedFiles = this.extractFileReferences(query);
48
+ // Find relevant files based on query content
49
+ const relevantFiles = this.findRelevantFiles(query, referencedFiles);
50
+ // Add project structure info for multi-file operations
51
+ const multiFileKeywords = [
52
+ 'create feature', 'add feature', 'new service', 'new component',
53
+ 'implement', 'monitoring', 'control', 'dashboard',
54
+ ];
55
+ if (multiFileKeywords.some(keyword => query.toLowerCase().includes(keyword))) {
56
+ const structureContext = this.getProjectStructureContext();
57
+ if (structureContext) {
58
+ return structureContext + '\n\n' + this.buildHierarchicalContext(relevantFiles);
59
+ }
60
+ }
61
+ // If user requested specific files that weren't found, provide feedback
62
+ if (referencedFiles.size > 0 && relevantFiles.length === 0) {
63
+ const missingFiles = Array.from(referencedFiles).slice(0, 5);
64
+ let context = 'Could not find the following files:\n';
65
+ for (const f of missingFiles) {
66
+ context += ` - ${f}\n`;
67
+ }
68
+ context += '\nSearching for similar files...\n';
69
+ // Try to find similar files
70
+ for (const ref of referencedFiles) {
71
+ const similar = this.findSimilarFiles(ref);
72
+ if (similar.length > 0) {
73
+ context += `\nFiles similar to '${ref}':\n`;
74
+ for (const s of similar.slice(0, 3)) {
75
+ context += ` - ${s}\n`;
76
+ }
77
+ }
78
+ }
79
+ return context;
80
+ }
81
+ // Build hierarchical context
82
+ return this.buildHierarchicalContext(relevantFiles);
83
+ }
84
+ // ========================================================================
85
+ // FILE REFERENCE EXTRACTION
86
+ // ========================================================================
87
+ extractFileReferences(query) {
88
+ const references = new Set();
89
+ // Pattern 1: @filename or @path/to/file
90
+ const atPattern = /@(\S+)/g;
91
+ let match;
92
+ while ((match = atPattern.exec(query)) !== null) {
93
+ references.add(match[1]);
94
+ }
95
+ // Pattern 2: "in file.py" or "from file.js"
96
+ const inPattern = /(?:in|from)\s+(\S+\.\w+)/gi;
97
+ while ((match = inPattern.exec(query)) !== null) {
98
+ references.add(match[1]);
99
+ }
100
+ // Pattern 3: Quoted filenames
101
+ const quotePattern = /["']([^"']+\.\w+)["']/g;
102
+ while ((match = quotePattern.exec(query)) !== null) {
103
+ references.add(match[1]);
104
+ }
105
+ // Pattern 4: Function/class references
106
+ const funcPattern = /(?:function|class|def)\s+(\w+)/gi;
107
+ const potentialFuncs = [];
108
+ while ((match = funcPattern.exec(query)) !== null) {
109
+ potentialFuncs.push(match[1]);
110
+ }
111
+ // Search for files containing these functions
112
+ for (const func of potentialFuncs) {
113
+ const matchingFiles = this.searchForSymbol(func);
114
+ for (const f of matchingFiles) {
115
+ references.add(f);
116
+ }
117
+ }
118
+ // Pattern 5: Common file patterns
119
+ const extensions = 'js|jsx|ts|tsx|py|rs|java|cpp|c|h|hpp|go|rb|php|swift|kt|scala|' +
120
+ 'html|htm|css|scss|sass|less|vue|svelte|' +
121
+ 'json|yaml|yml|toml|xml|env|ini|conf|cfg|' +
122
+ 'md|mdx|rst|txt|' +
123
+ 'sh|bash|ps1|bat|cmd';
124
+ const reviewPatterns = [
125
+ new RegExp(`(?:review|check|look at|examine|analyze|find|open)\\s+(?:the\\s+)?(\\S+?)(?:\\s+file)?`, 'gi'),
126
+ new RegExp(`(?:find|analyze|review)\\s+(?:the\\s+)?(\\S+\\.(?:${extensions}))\\b`, 'gi'),
127
+ new RegExp(`(\\S+\\.(?:${extensions}))\\s+file`, 'gi'),
128
+ ];
129
+ for (const pattern of reviewPatterns) {
130
+ while ((match = pattern.exec(query)) !== null) {
131
+ const cleanMatch = match[1].replace(/[.,;:!?]$/, '');
132
+ if (!['the', 'a', 'an'].some(word => cleanMatch.startsWith(word)) && cleanMatch.length > 2) {
133
+ references.add(cleanMatch);
134
+ }
135
+ }
136
+ }
137
+ return references;
138
+ }
139
+ // ========================================================================
140
+ // FILE FINDING AND RELEVANCE
141
+ // ========================================================================
142
+ findRelevantFiles(query, explicitRefs) {
143
+ const relevantFiles = [];
144
+ // First, add explicitly referenced files
145
+ for (const ref of explicitRefs) {
146
+ const filePath = path.join(this.projectPath, ref);
147
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
148
+ const content = this.readFileSafely(filePath);
149
+ if (content) {
150
+ relevantFiles.push({
151
+ path: filePath,
152
+ relevanceScore: 1.0,
153
+ content,
154
+ language: this.detectLanguage(filePath),
155
+ });
156
+ }
157
+ }
158
+ else {
159
+ // Try to find file by name in subdirectories (LIMITED SEARCH)
160
+ const foundFiles = this.findFileByName(ref);
161
+ for (const foundPath of foundFiles.slice(0, 3)) {
162
+ const content = this.readFileSafely(foundPath);
163
+ if (content) {
164
+ relevantFiles.push({
165
+ path: foundPath,
166
+ relevanceScore: 0.9,
167
+ content,
168
+ language: this.detectLanguage(foundPath),
169
+ });
170
+ }
171
+ }
172
+ }
173
+ }
174
+ // If no explicit files found, scan current directory
175
+ if (relevantFiles.length === 0) {
176
+ const keywords = this.extractKeywords(query);
177
+ const scannedFiles = this.scanCurrentDirectory(keywords);
178
+ relevantFiles.push(...scannedFiles);
179
+ }
180
+ // Extract keywords from query
181
+ const keywords = this.extractKeywords(query);
182
+ // Search for files containing keywords
183
+ for (const filePath of this.iterateProjectFiles()) {
184
+ if (Array.from(explicitRefs).some(ref => filePath.endsWith(ref))) {
185
+ continue;
186
+ }
187
+ const content = this.readFileSafely(filePath);
188
+ if (!content)
189
+ continue;
190
+ // Calculate relevance score
191
+ const score = this.calculateRelevance(content, keywords, query);
192
+ if (score > 0.3) {
193
+ relevantFiles.push({
194
+ path: filePath,
195
+ relevanceScore: score,
196
+ content,
197
+ language: this.detectLanguage(filePath),
198
+ });
199
+ }
200
+ }
201
+ // Sort by relevance
202
+ relevantFiles.sort((a, b) => b.relevanceScore - a.relevanceScore);
203
+ // Limit to top files that fit in context
204
+ return this.limitByContextSize(relevantFiles);
205
+ }
206
+ findFileByName(name) {
207
+ const results = [];
208
+ const scan = (dir, depth) => {
209
+ if (depth > 2)
210
+ return; // Only search 2 levels deep
211
+ try {
212
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
213
+ for (const entry of entries) {
214
+ const fullPath = path.join(dir, entry.name);
215
+ if (entry.isDirectory()) {
216
+ if (!this.shouldIgnore(fullPath)) {
217
+ scan(fullPath, depth + 1);
218
+ }
219
+ }
220
+ else if (entry.name.includes(name) || name.includes(entry.name)) {
221
+ results.push(fullPath);
222
+ }
223
+ }
224
+ }
225
+ catch {
226
+ // Ignore errors
227
+ }
228
+ };
229
+ scan(this.projectPath, 0);
230
+ return results;
231
+ }
232
+ scanCurrentDirectory(keywords) {
233
+ const relevantFiles = [];
234
+ // Scan project root and immediate subdirectories
235
+ const scanPaths = [this.projectPath];
236
+ try {
237
+ for (const item of fs.readdirSync(this.projectPath, { withFileTypes: true })) {
238
+ if (item.isDirectory() && !this.shouldIgnore(path.join(this.projectPath, item.name))) {
239
+ scanPaths.push(path.join(this.projectPath, item.name));
240
+ }
241
+ }
242
+ }
243
+ catch {
244
+ // Ignore errors
245
+ }
246
+ for (const scanPath of scanPaths) {
247
+ try {
248
+ for (const file of fs.readdirSync(scanPath, { withFileTypes: true })) {
249
+ if (file.isFile() && !this.shouldIgnore(path.join(scanPath, file.name))) {
250
+ const filePath = path.join(scanPath, file.name);
251
+ const content = this.readFileSafely(filePath);
252
+ if (content) {
253
+ const score = this.calculateRelevance(content, keywords, keywords.join(' '));
254
+ if (score > 0.1) {
255
+ relevantFiles.push({
256
+ path: filePath,
257
+ relevanceScore: score,
258
+ content,
259
+ language: this.detectLanguage(filePath),
260
+ });
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
266
+ catch {
267
+ // Ignore errors
268
+ }
269
+ }
270
+ return relevantFiles;
271
+ }
272
+ shouldIgnore(filepath) {
273
+ const parts = filepath.split(path.sep);
274
+ return parts.some(part => ContextBuilder.IGNORE_PATTERNS.has(part));
275
+ }
276
+ searchForSymbol(symbol) {
277
+ const matches = new Set();
278
+ try {
279
+ for (const filePath of this.iterateProjectFiles()) {
280
+ const content = this.readFileSafely(filePath);
281
+ if (!content)
282
+ continue;
283
+ try {
284
+ const regex = new RegExp(`\\b${symbol}\\b`);
285
+ if (regex.test(content)) {
286
+ matches.add(path.relative(this.projectPath, filePath));
287
+ }
288
+ }
289
+ catch {
290
+ // Fallback to simple includes
291
+ if (content.includes(symbol)) {
292
+ matches.add(path.relative(this.projectPath, filePath));
293
+ }
294
+ }
295
+ }
296
+ }
297
+ catch {
298
+ // Ignore errors
299
+ }
300
+ return matches;
301
+ }
302
+ extractKeywords(query) {
303
+ const stopWords = new Set([
304
+ 'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
305
+ 'of', 'with', 'by', 'from', 'up', 'about', 'into', 'through', 'during',
306
+ 'how', 'when', 'where', 'what', 'which', 'who', 'why', 'can', 'could',
307
+ 'should', 'would', 'will', 'add', 'create', 'make', 'build', 'implement',
308
+ 'update', 'modify', 'change', 'fix', 'improve', 'refactor',
309
+ ]);
310
+ const words = query.toLowerCase().match(/\b\w+\b/g) || [];
311
+ const keywords = [];
312
+ for (const word of words) {
313
+ if (word.length > 2 && !stopWords.has(word)) {
314
+ keywords.push(word);
315
+ if (word.includes('_')) {
316
+ keywords.push(word.replace(/_/g, ''));
317
+ keywords.push(word.split('_').map(p => p.charAt(0).toUpperCase() + p.slice(1)).join(''));
318
+ }
319
+ }
320
+ }
321
+ return keywords;
322
+ }
323
+ calculateRelevance(content, keywords, query) {
324
+ let score = 0.0;
325
+ const contentLower = content.toLowerCase();
326
+ // Keyword matches
327
+ for (const keyword of keywords) {
328
+ const count = (contentLower.match(new RegExp(keyword.toLowerCase(), 'g')) || []).length;
329
+ if (count > 0) {
330
+ score += Math.min(count * 0.1, 0.3);
331
+ }
332
+ }
333
+ // Special patterns
334
+ if (query.toLowerCase().includes('test') &&
335
+ (contentLower.includes('test') || contentLower.includes('spec'))) {
336
+ score += 0.2;
337
+ }
338
+ if (query.toLowerCase().includes('error') &&
339
+ (contentLower.includes('error') || contentLower.includes('exception'))) {
340
+ score += 0.2;
341
+ }
342
+ if (query.toLowerCase().includes('config') &&
343
+ ['config', 'settings', 'env'].some(word => contentLower.includes(word))) {
344
+ score += 0.2;
345
+ }
346
+ if (['import', 'require', 'use'].some(word => contentLower.includes(word))) {
347
+ score += 0.1;
348
+ }
349
+ return Math.min(score, 1.0);
350
+ }
351
+ // ========================================================================
352
+ // CONTEXT SIZE MANAGEMENT
353
+ // ========================================================================
354
+ limitByContextSize(files) {
355
+ const selected = [];
356
+ let currentTokens = 0;
357
+ for (const fileCtx of files) {
358
+ const tokens = fileCtx.content.split(/\s+/).length * this.tokenEstimateRatio;
359
+ if (currentTokens + tokens > this.maxContextTokens) {
360
+ if (fileCtx.relevanceScore > 0.8 && currentTokens < this.maxContextTokens * 0.9) {
361
+ const lines = fileCtx.content.split('\n');
362
+ fileCtx.content = lines.slice(0, 50).join('\n') + '\n... (truncated)';
363
+ selected.push(fileCtx);
364
+ }
365
+ break;
366
+ }
367
+ selected.push(fileCtx);
368
+ currentTokens += tokens;
369
+ }
370
+ return selected;
371
+ }
372
+ // ========================================================================
373
+ // CONTEXT BUILDING AND FORMATTING
374
+ // ========================================================================
375
+ buildHierarchicalContext(files) {
376
+ if (files.length === 0) {
377
+ return '';
378
+ }
379
+ const contextParts = [];
380
+ // High relevance files
381
+ const highRelevance = files.filter(f => f.relevanceScore >= 0.8);
382
+ if (highRelevance.length > 0) {
383
+ contextParts.push('=== Highly Relevant Files ===\n');
384
+ for (const fileCtx of highRelevance) {
385
+ const relPath = path.relative(this.projectPath, fileCtx.path);
386
+ contextParts.push(`\n--- ${relPath} ---\n`);
387
+ contextParts.push(fileCtx.content);
388
+ contextParts.push('\n');
389
+ }
390
+ }
391
+ // Medium relevance files
392
+ const mediumRelevance = files.filter(f => f.relevanceScore >= 0.5 && f.relevanceScore < 0.8);
393
+ if (mediumRelevance.length > 0) {
394
+ contextParts.push('\n=== Related Files ===\n');
395
+ for (const fileCtx of mediumRelevance) {
396
+ const relPath = path.relative(this.projectPath, fileCtx.path);
397
+ contextParts.push(`\n--- ${relPath} (key sections) ---\n`);
398
+ const keyContent = this.extractKeySections(fileCtx.content, fileCtx.language);
399
+ contextParts.push(keyContent);
400
+ contextParts.push('\n');
401
+ }
402
+ }
403
+ // Low relevance files
404
+ const lowRelevance = files.filter(f => f.relevanceScore < 0.5);
405
+ if (lowRelevance.length > 0) {
406
+ contextParts.push('\n=== Project Structure ===\n');
407
+ for (const fileCtx of lowRelevance) {
408
+ const relPath = path.relative(this.projectPath, fileCtx.path);
409
+ const structure = this.extractStructure(fileCtx.content, fileCtx.language);
410
+ if (structure) {
411
+ contextParts.push(`${relPath}: ${structure}\n`);
412
+ }
413
+ }
414
+ }
415
+ return contextParts.join('');
416
+ }
417
+ extractKeySections(content, language) {
418
+ const lines = content.split('\n');
419
+ const keyLines = [];
420
+ if (language === 'python') {
421
+ for (let i = 0; i < lines.length; i++) {
422
+ const line = lines[i];
423
+ if (line.startsWith('import ') || line.startsWith('from ') ||
424
+ line.startsWith('class ') || line.startsWith('def ') ||
425
+ line.trim().startsWith('@')) {
426
+ keyLines.push(line);
427
+ // Include docstring if present
428
+ if (i + 1 < lines.length && lines[i + 1].includes('"""')) {
429
+ let j = i + 1;
430
+ while (j < lines.length && !lines[j].slice(3).includes('"""')) {
431
+ keyLines.push(lines[j]);
432
+ j++;
433
+ if (j < lines.length) {
434
+ keyLines.push(lines[j]);
435
+ }
436
+ }
437
+ }
438
+ }
439
+ }
440
+ }
441
+ else if (language === 'javascript' || language === 'typescript') {
442
+ for (const line of lines) {
443
+ if (line.startsWith('import ') || line.startsWith('export ') ||
444
+ line.includes('class ') || line.includes('function ') || line.includes('const ')) {
445
+ keyLines.push(line);
446
+ }
447
+ }
448
+ }
449
+ else if (language === 'rust') {
450
+ for (const line of lines) {
451
+ if (line.startsWith('use ') || line.startsWith('pub ') ||
452
+ line.startsWith('struct ') || line.startsWith('trait ') ||
453
+ line.startsWith('fn ') || line.startsWith('impl ')) {
454
+ keyLines.push(line);
455
+ }
456
+ }
457
+ }
458
+ return keyLines.slice(0, 100).join('\n');
459
+ }
460
+ findSimilarFiles(target) {
461
+ const similarFiles = [];
462
+ const targetLower = target.toLowerCase();
463
+ const targetParts = targetLower.replace(/\./g, ' ').replace(/_/g, ' ').replace(/-/g, ' ').split(' ');
464
+ try {
465
+ for (const filePath of this.iterateProjectFiles()) {
466
+ const fileName = path.basename(filePath).toLowerCase();
467
+ if (targetParts.some(part => part.length > 2 && fileName.includes(part))) {
468
+ const relPath = path.relative(this.projectPath, filePath);
469
+ similarFiles.push(relPath);
470
+ }
471
+ if (targetLower.replace('.', '').includes(fileName.replace('.', ''))) {
472
+ const relPath = path.relative(this.projectPath, filePath);
473
+ if (!similarFiles.includes(relPath)) {
474
+ similarFiles.push(relPath);
475
+ }
476
+ }
477
+ }
478
+ }
479
+ catch {
480
+ // Ignore errors
481
+ }
482
+ return similarFiles.slice(0, 10);
483
+ }
484
+ extractStructure(content, language) {
485
+ const structures = [];
486
+ if (language === 'python') {
487
+ const classes = content.match(/^class\s+(\w+)/gm);
488
+ const functions = content.match(/^def\s+(\w+)/gm);
489
+ if (classes) {
490
+ const classNames = classes.map(c => c.replace('class ', '')).slice(0, 5);
491
+ structures.push(`classes: ${classNames.join(', ')}`);
492
+ }
493
+ if (functions) {
494
+ const funcNames = functions.map(f => f.replace('def ', '')).slice(0, 5);
495
+ structures.push(`functions: ${funcNames.join(', ')}`);
496
+ }
497
+ }
498
+ else if (language === 'javascript' || language === 'typescript') {
499
+ const exports = content.match(/export\s+(?:default\s+)?(?:class|function|const)\s+(\w+)/g);
500
+ if (exports) {
501
+ const exportNames = exports.map(e => e.replace(/export\s+(default\s+)?(class|function|const)\s+/, '')).slice(0, 5);
502
+ structures.push(`exports: ${exportNames.join(', ')}`);
503
+ }
504
+ }
505
+ return structures.join('; ');
506
+ }
507
+ // ========================================================================
508
+ // FILE I/O UTILITIES
509
+ // ========================================================================
510
+ readFileSafely(filePath) {
511
+ try {
512
+ const content = fs.readFileSync(filePath, 'utf-8');
513
+ const lines = content.split('\n');
514
+ return lines.slice(0, 2000).join('\n');
515
+ }
516
+ catch {
517
+ try {
518
+ const content = fs.readFileSync(filePath, 'latin1');
519
+ const lines = content.split('\n');
520
+ return lines.slice(0, 2000).join('\n');
521
+ }
522
+ catch {
523
+ return null;
524
+ }
525
+ }
526
+ }
527
+ detectLanguage(filePath) {
528
+ return ContextBuilder.EXT_MAP[path.extname(filePath).toLowerCase()] || 'text';
529
+ }
530
+ *iterateProjectFiles() {
531
+ const maxDepth = 2;
532
+ function* scan(dir, depth, self) {
533
+ if (depth > maxDepth)
534
+ return;
535
+ try {
536
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
537
+ for (const entry of entries) {
538
+ const fullPath = path.join(dir, entry.name);
539
+ if (ContextBuilder.IGNORE_PATTERNS.has(entry.name))
540
+ continue;
541
+ if (entry.isFile()) {
542
+ const ext = path.extname(entry.name).toLowerCase();
543
+ if (!['.pyc', '.exe', '.dll', '.so', '.dylib', '.jpg', '.png', '.gif', '.pdf'].includes(ext)) {
544
+ yield fullPath;
545
+ }
546
+ }
547
+ else if (entry.isDirectory() && depth < maxDepth) {
548
+ yield* scan(fullPath, depth + 1, self);
549
+ }
550
+ }
551
+ }
552
+ catch {
553
+ // Ignore errors
554
+ }
555
+ }
556
+ yield* scan(this.projectPath, 0, this);
557
+ }
558
+ // ========================================================================
559
+ // PROJECT PATTERN DETECTION
560
+ // ========================================================================
561
+ detectProjectPatterns() {
562
+ const patterns = {
563
+ serviceLocations: [],
564
+ uiLocations: [],
565
+ logicLocations: [],
566
+ configLocations: [],
567
+ styleLocations: [],
568
+ apiLocations: [],
569
+ };
570
+ try {
571
+ for (const filePath of this.iterateProjectFiles()) {
572
+ const relPath = path.relative(this.projectPath, filePath).replace(/\\/g, '/');
573
+ if (['service', 'monitor', 'reporter'].some(p => relPath.toLowerCase().includes(p))) {
574
+ patterns.serviceLocations.push(path.dirname(relPath));
575
+ }
576
+ if (['.tsx', '.jsx'].some(ext => relPath.includes(ext)) ||
577
+ ['pages', 'components'].some(p => relPath.includes(p))) {
578
+ patterns.uiLocations.push(path.dirname(relPath));
579
+ }
580
+ if (['logic', 'controller'].some(p => relPath.toLowerCase().includes(p))) {
581
+ patterns.logicLocations.push(path.dirname(relPath));
582
+ }
583
+ if (relPath.toLowerCase().includes('config') ||
584
+ ['.json', '.yaml', '.yml'].some(ext => relPath.endsWith(ext))) {
585
+ patterns.configLocations.push(path.dirname(relPath));
586
+ }
587
+ if (['.css', '.scss', '.less'].some(ext => relPath.endsWith(ext)) ||
588
+ relPath.includes('styles')) {
589
+ patterns.styleLocations.push(path.dirname(relPath));
590
+ }
591
+ if (['api', 'routes', 'server'].some(p => relPath.toLowerCase().includes(p))) {
592
+ patterns.apiLocations.push(path.dirname(relPath));
593
+ }
594
+ }
595
+ }
596
+ catch {
597
+ // Ignore errors
598
+ }
599
+ // Deduplicate and sort by frequency
600
+ for (const key of Object.keys(patterns)) {
601
+ const counts = new Map();
602
+ for (const loc of patterns[key]) {
603
+ counts.set(loc, (counts.get(loc) || 0) + 1);
604
+ }
605
+ patterns[key] = Array.from(counts.entries())
606
+ .sort((a, b) => b[1] - a[1])
607
+ .map(e => e[0])
608
+ .slice(0, 5);
609
+ }
610
+ return patterns;
611
+ }
612
+ getProjectStructureContext() {
613
+ let context = 'PROJECT STRUCTURE PATTERNS DETECTED:\n';
614
+ context += '='.repeat(50) + '\n\n';
615
+ if (this.projectPatterns.serviceLocations.length > 0) {
616
+ context += 'SERVICE FILES LOCATION:\n';
617
+ for (const loc of this.projectPatterns.serviceLocations.slice(0, 3)) {
618
+ context += ` - ${loc}/\n`;
619
+ }
620
+ context += '\n';
621
+ }
622
+ if (this.projectPatterns.uiLocations.length > 0) {
623
+ context += 'UI COMPONENTS LOCATION:\n';
624
+ for (const loc of this.projectPatterns.uiLocations.slice(0, 3)) {
625
+ context += ` - ${loc}/\n`;
626
+ }
627
+ context += '\n';
628
+ }
629
+ if (this.projectPatterns.logicLocations.length > 0) {
630
+ context += 'LOGIC/CONTROLLER FILES LOCATION:\n';
631
+ for (const loc of this.projectPatterns.logicLocations.slice(0, 3)) {
632
+ context += ` - ${loc}/\n`;
633
+ }
634
+ context += '\n';
635
+ }
636
+ if (this.projectPatterns.configLocations.length > 0) {
637
+ context += 'CONFIGURATION FILES LOCATION:\n';
638
+ for (const loc of this.projectPatterns.configLocations.slice(0, 3)) {
639
+ context += ` - ${loc}/\n`;
640
+ }
641
+ context += '\n';
642
+ }
643
+ if (this.projectPatterns.styleLocations.length > 0) {
644
+ context += 'STYLES/CSS FILES LOCATION:\n';
645
+ for (const loc of this.projectPatterns.styleLocations.slice(0, 3)) {
646
+ context += ` - ${loc}/\n`;
647
+ }
648
+ context += '\n';
649
+ }
650
+ if (this.projectPatterns.apiLocations.length > 0) {
651
+ context += 'API/SERVER FILES LOCATION:\n';
652
+ for (const loc of this.projectPatterns.apiLocations.slice(0, 3)) {
653
+ context += ` - ${loc}/\n`;
654
+ }
655
+ context += '\n';
656
+ }
657
+ context += 'EXAMPLE FILES TO FOLLOW PATTERNS FROM:\n';
658
+ try {
659
+ const examples = this.findExampleFiles();
660
+ for (const [category, files] of Object.entries(examples)) {
661
+ if (files.length > 0) {
662
+ context += `\n${category}:\n`;
663
+ for (const f of files.slice(0, 2)) {
664
+ context += ` - ${f}\n`;
665
+ }
666
+ }
667
+ }
668
+ }
669
+ catch {
670
+ // Ignore errors
671
+ }
672
+ context += '\nREMEMBER: When creating new features, create ALL necessary files following these patterns!\n';
673
+ return context;
674
+ }
675
+ findExampleFiles() {
676
+ const examples = {
677
+ 'Services': [],
678
+ 'UI Components': [],
679
+ 'Logic Files': [],
680
+ 'API Routes': [],
681
+ };
682
+ try {
683
+ for (const filePath of this.iterateProjectFiles()) {
684
+ const relPath = path.relative(this.projectPath, filePath).replace(/\\/g, '/');
685
+ const ext = path.extname(filePath);
686
+ if (relPath.toLowerCase().includes('service') && ['.js', '.ts', '.py'].includes(ext)) {
687
+ examples['Services'].push(relPath);
688
+ }
689
+ if (['.tsx', '.jsx'].includes(ext) && (['pages', 'components'].some(p => relPath.includes(p)))) {
690
+ examples['UI Components'].push(relPath);
691
+ }
692
+ if (relPath.toLowerCase().includes('logic') && ['.js', '.ts', '.py'].includes(ext)) {
693
+ examples['Logic Files'].push(relPath);
694
+ }
695
+ if (['server', 'api'].some(p => relPath.toLowerCase().includes(p)) && ['.js', '.ts', '.py'].includes(ext)) {
696
+ examples['API Routes'].push(relPath);
697
+ }
698
+ // Limit to 2 per category
699
+ for (const key of Object.keys(examples)) {
700
+ if (examples[key].length >= 2) {
701
+ examples[key] = examples[key].slice(0, 2);
702
+ }
703
+ }
704
+ }
705
+ }
706
+ catch {
707
+ // Ignore errors
708
+ }
709
+ return examples;
710
+ }
711
+ }
712
+ // ============================================================================
713
+ // SINGLETON INSTANCE
714
+ // ============================================================================
715
+ let builderInstance = null;
716
+ export function getContextBuilder(projectPath) {
717
+ if (!builderInstance || (projectPath && builderInstance['projectPath'] !== path.resolve(projectPath))) {
718
+ builderInstance = new ContextBuilder(projectPath || process.cwd());
719
+ }
720
+ return builderInstance;
721
+ }
722
+ export function resetContextBuilder() {
723
+ builderInstance = null;
724
+ }
725
+ //# sourceMappingURL=contextBuilder.js.map