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,859 @@
1
+ /**
2
+ * Project Scanner for NexusForge CLI
3
+ * Analyzes project structure, dependencies, and content
4
+ * Ported from Python nexusforge/modules/project/scanner.py
5
+ *
6
+ * Now with optional native Rust acceleration (10-20x faster)
7
+ */
8
+ import * as fs from 'fs';
9
+ import * as path from 'path';
10
+ import * as native from '../native/index.js';
11
+ // ============================================================================
12
+ // PROJECT SCANNER CLASS
13
+ // ============================================================================
14
+ export class ProjectScanner {
15
+ projectPath;
16
+ useCache;
17
+ cacheDir;
18
+ cacheFile;
19
+ files = new Map();
20
+ dependencies = {};
21
+ technologies = new Set();
22
+ stats;
23
+ // File extensions to language mapping
24
+ static LANGUAGE_MAP = {
25
+ // Python
26
+ '.py': 'python',
27
+ '.pyx': 'python',
28
+ '.pyi': 'python',
29
+ // JavaScript/TypeScript
30
+ '.js': 'javascript',
31
+ '.jsx': 'javascript',
32
+ '.ts': 'typescript',
33
+ '.tsx': 'typescript',
34
+ '.mjs': 'javascript',
35
+ '.cjs': 'javascript',
36
+ // Web
37
+ '.html': 'html',
38
+ '.htm': 'html',
39
+ '.css': 'css',
40
+ '.scss': 'scss',
41
+ '.sass': 'sass',
42
+ '.less': 'less',
43
+ '.vue': 'vue',
44
+ '.svelte': 'svelte',
45
+ // Systems
46
+ '.rs': 'rust',
47
+ '.go': 'go',
48
+ '.c': 'c',
49
+ '.cpp': 'cpp',
50
+ '.cc': 'cpp',
51
+ '.cxx': 'cpp',
52
+ '.h': 'c',
53
+ '.hpp': 'cpp',
54
+ '.hxx': 'cpp',
55
+ // JVM
56
+ '.java': 'java',
57
+ '.kt': 'kotlin',
58
+ '.kts': 'kotlin',
59
+ '.scala': 'scala',
60
+ '.groovy': 'groovy',
61
+ // .NET
62
+ '.cs': 'csharp',
63
+ '.fs': 'fsharp',
64
+ '.vb': 'vb',
65
+ // Other
66
+ '.rb': 'ruby',
67
+ '.php': 'php',
68
+ '.swift': 'swift',
69
+ '.r': 'r',
70
+ '.m': 'objective-c',
71
+ '.jl': 'julia',
72
+ '.sh': 'bash',
73
+ '.bash': 'bash',
74
+ '.zsh': 'zsh',
75
+ '.fish': 'fish',
76
+ '.ps1': 'powershell',
77
+ '.sql': 'sql',
78
+ '.lua': 'lua',
79
+ '.pl': 'perl',
80
+ '.ex': 'elixir',
81
+ '.exs': 'elixir',
82
+ '.erl': 'erlang',
83
+ '.hrl': 'erlang',
84
+ '.dart': 'dart',
85
+ };
86
+ // Directories to always ignore
87
+ static IGNORE_DIRS = new Set([
88
+ '.git', '.svn', '.hg', '.bzr',
89
+ '__pycache__', '.pytest_cache', '.mypy_cache',
90
+ 'node_modules', 'bower_components',
91
+ '.nexusforge', '.vscode', '.idea',
92
+ 'venv', 'env', '.env', 'virtualenv',
93
+ 'dist', 'build', 'target', 'out',
94
+ '.next', '.nuxt', '.cache',
95
+ 'coverage', '.nyc_output',
96
+ 'vendor', 'packages',
97
+ ]);
98
+ constructor(projectPath, useCache = true) {
99
+ this.projectPath = path.resolve(projectPath);
100
+ this.useCache = useCache;
101
+ this.cacheDir = path.join(this.projectPath, '.nexusforge');
102
+ this.cacheFile = path.join(this.cacheDir, 'project_scan.cache.json');
103
+ // Ensure cache directory exists
104
+ if (!fs.existsSync(this.cacheDir)) {
105
+ try {
106
+ fs.mkdirSync(this.cacheDir, { recursive: true });
107
+ }
108
+ catch {
109
+ // Ignore errors
110
+ }
111
+ }
112
+ this.stats = {
113
+ filesScanned: 0,
114
+ filesSkipped: 0,
115
+ totalSize: 0,
116
+ scanErrors: 0,
117
+ cacheEnabled: useCache,
118
+ };
119
+ }
120
+ // ========================================================================
121
+ // CACHING
122
+ // ========================================================================
123
+ loadCache() {
124
+ if (!this.useCache || !fs.existsSync(this.cacheFile)) {
125
+ return null;
126
+ }
127
+ try {
128
+ const data = JSON.parse(fs.readFileSync(this.cacheFile, 'utf-8'));
129
+ // Check if cache is less than 1 hour old
130
+ const cacheAge = Date.now() - (data.scanTime || 0);
131
+ if (cacheAge < 3600000) {
132
+ return data;
133
+ }
134
+ }
135
+ catch {
136
+ // Ignore errors
137
+ }
138
+ return null;
139
+ }
140
+ saveCache(result) {
141
+ if (!this.useCache)
142
+ return;
143
+ try {
144
+ fs.writeFileSync(this.cacheFile, JSON.stringify(result, null, 2));
145
+ }
146
+ catch {
147
+ // Ignore errors
148
+ }
149
+ }
150
+ // ========================================================================
151
+ // SCANNING
152
+ // ========================================================================
153
+ /**
154
+ * Scan using native Rust module (10-20x faster)
155
+ * Returns null if native module not available
156
+ */
157
+ scanNative() {
158
+ const nativeResult = native.scanDirectory({
159
+ root: this.projectPath,
160
+ extractImports: true,
161
+ extractSymbols: true,
162
+ computeHashes: false,
163
+ });
164
+ if (!nativeResult) {
165
+ return null;
166
+ }
167
+ // Convert native result to our format
168
+ for (const file of nativeResult.files) {
169
+ const relPath = file.relativePath;
170
+ const lang = file.language?.toLowerCase() || 'unknown';
171
+ this.files.set(relPath, {
172
+ path: file.path,
173
+ language: lang,
174
+ size: file.size,
175
+ lines: file.lineCount,
176
+ imports: file.imports,
177
+ functions: file.functions,
178
+ classes: file.classes,
179
+ complexity: 0,
180
+ lastModified: null,
181
+ });
182
+ }
183
+ this.stats.filesScanned = nativeResult.stats.totalFiles;
184
+ this.stats.totalSize = nativeResult.stats.totalSize;
185
+ // Build language stats
186
+ const languages = {};
187
+ for (const [lang, count] of Object.entries(nativeResult.stats.filesByLanguage)) {
188
+ languages[lang.toLowerCase()] = count;
189
+ }
190
+ return {
191
+ projectPath: this.projectPath,
192
+ fileCount: nativeResult.stats.totalFiles,
193
+ totalLines: nativeResult.stats.totalLines,
194
+ totalSize: nativeResult.stats.totalSize,
195
+ languages,
196
+ technologies: [], // Will be filled by detectTechnologies
197
+ dependencies: {},
198
+ structure: {},
199
+ entryPoints: [],
200
+ configFiles: [],
201
+ testFiles: [],
202
+ scanTime: Date.now(),
203
+ scanDuration: nativeResult.stats.scanDurationMs / 1000,
204
+ };
205
+ }
206
+ scan() {
207
+ const startTime = Date.now();
208
+ // Check cache
209
+ const cached = this.loadCache();
210
+ if (cached) {
211
+ return cached;
212
+ }
213
+ // Try native scan first (10-20x faster)
214
+ let nativeResult = null;
215
+ if (native.isNativeAvailable()) {
216
+ nativeResult = this.scanNative();
217
+ }
218
+ if (nativeResult) {
219
+ // Native scan succeeded, complete with additional analysis
220
+ this.detectTechnologies();
221
+ this.analyzeDependencies();
222
+ const result = {
223
+ ...nativeResult,
224
+ technologies: Array.from(this.technologies),
225
+ dependencies: this.dependencies,
226
+ structure: this.getDirectoryStructure(),
227
+ entryPoints: this.findEntryPoints(),
228
+ configFiles: this.findConfigFiles(),
229
+ testFiles: this.findTestFiles(),
230
+ scanDuration: (Date.now() - startTime) / 1000,
231
+ };
232
+ this.saveCache(result);
233
+ return result;
234
+ }
235
+ // Fallback to TypeScript scan
236
+ this.scanFiles();
237
+ this.detectTechnologies();
238
+ this.analyzeDependencies();
239
+ // Create result
240
+ const result = {
241
+ projectPath: this.projectPath,
242
+ fileCount: this.files.size,
243
+ totalLines: Array.from(this.files.values()).reduce((sum, f) => sum + f.lines, 0),
244
+ totalSize: this.stats.totalSize,
245
+ languages: this.getLanguageStats(),
246
+ technologies: Array.from(this.technologies),
247
+ dependencies: this.dependencies,
248
+ structure: this.getDirectoryStructure(),
249
+ entryPoints: this.findEntryPoints(),
250
+ configFiles: this.findConfigFiles(),
251
+ testFiles: this.findTestFiles(),
252
+ scanTime: Date.now(),
253
+ scanDuration: (Date.now() - startTime) / 1000,
254
+ };
255
+ // Save cache
256
+ this.saveCache(result);
257
+ return result;
258
+ }
259
+ quickScan() {
260
+ let fileCount = 0;
261
+ const techHints = new Set();
262
+ const languages = new Set();
263
+ const scan = (dir) => {
264
+ try {
265
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
266
+ for (const entry of entries) {
267
+ const fullPath = path.join(dir, entry.name);
268
+ if (entry.isDirectory()) {
269
+ if (!this.shouldIgnore(fullPath)) {
270
+ scan(fullPath);
271
+ }
272
+ }
273
+ else {
274
+ const ext = path.extname(entry.name).toLowerCase();
275
+ if (ProjectScanner.LANGUAGE_MAP[ext]) {
276
+ fileCount++;
277
+ languages.add(ProjectScanner.LANGUAGE_MAP[ext]);
278
+ }
279
+ this.quickDetectTech(entry.name, techHints);
280
+ }
281
+ }
282
+ }
283
+ catch {
284
+ // Ignore errors
285
+ }
286
+ };
287
+ scan(this.projectPath);
288
+ return {
289
+ projectPath: this.projectPath,
290
+ fileCount,
291
+ languages: Array.from(languages),
292
+ technologies: Array.from(techHints),
293
+ quickScan: true,
294
+ };
295
+ }
296
+ quickDetectTech(filename, techHints) {
297
+ const techFiles = {
298
+ 'package.json': 'nodejs',
299
+ 'yarn.lock': 'yarn',
300
+ 'pnpm-lock.yaml': 'pnpm',
301
+ 'Cargo.toml': 'rust',
302
+ 'requirements.txt': 'python',
303
+ 'Pipfile': 'pipenv',
304
+ 'pyproject.toml': 'python',
305
+ 'go.mod': 'go',
306
+ 'pom.xml': 'maven',
307
+ 'build.gradle': 'gradle',
308
+ 'Dockerfile': 'docker',
309
+ };
310
+ if (techFiles[filename]) {
311
+ techHints.add(techFiles[filename]);
312
+ }
313
+ }
314
+ shouldIgnore(filepath) {
315
+ const parts = filepath.split(path.sep);
316
+ for (const part of parts) {
317
+ if (ProjectScanner.IGNORE_DIRS.has(part)) {
318
+ return true;
319
+ }
320
+ }
321
+ const basename = path.basename(filepath);
322
+ if (basename.startsWith('.') && !['.gitignore', '.env', '.env.example', '.dockerignore'].includes(basename)) {
323
+ return true;
324
+ }
325
+ return false;
326
+ }
327
+ scanFiles() {
328
+ const scan = (dir) => {
329
+ try {
330
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
331
+ for (const entry of entries) {
332
+ const fullPath = path.join(dir, entry.name);
333
+ if (entry.isDirectory()) {
334
+ if (!this.shouldIgnore(fullPath)) {
335
+ scan(fullPath);
336
+ }
337
+ }
338
+ else {
339
+ const ext = path.extname(entry.name).toLowerCase();
340
+ if (ProjectScanner.LANGUAGE_MAP[ext]) {
341
+ this.scanFile(fullPath);
342
+ }
343
+ }
344
+ }
345
+ }
346
+ catch {
347
+ // Ignore errors
348
+ }
349
+ };
350
+ scan(this.projectPath);
351
+ }
352
+ scanFile(filePath) {
353
+ try {
354
+ const stat = fs.statSync(filePath);
355
+ // Skip very large files (>10MB)
356
+ if (stat.size > 10 * 1024 * 1024) {
357
+ this.stats.filesSkipped++;
358
+ return;
359
+ }
360
+ const relPath = path.relative(this.projectPath, filePath);
361
+ const ext = path.extname(filePath).toLowerCase();
362
+ const language = ProjectScanner.LANGUAGE_MAP[ext] || 'unknown';
363
+ const fileInfo = {
364
+ path: filePath,
365
+ language,
366
+ size: stat.size,
367
+ lines: this.countLines(filePath),
368
+ imports: this.extractImports(filePath, language),
369
+ functions: this.extractFunctions(filePath, language),
370
+ classes: this.extractClasses(filePath, language),
371
+ complexity: 0,
372
+ lastModified: stat.mtimeMs,
373
+ };
374
+ this.files.set(relPath, fileInfo);
375
+ this.stats.filesScanned++;
376
+ this.stats.totalSize += stat.size;
377
+ }
378
+ catch {
379
+ this.stats.filesSkipped++;
380
+ }
381
+ }
382
+ // ========================================================================
383
+ // FILE ANALYSIS
384
+ // ========================================================================
385
+ countLines(filePath) {
386
+ try {
387
+ const content = fs.readFileSync(filePath, 'utf-8');
388
+ return content.split('\n').length;
389
+ }
390
+ catch {
391
+ return 0;
392
+ }
393
+ }
394
+ extractImports(filePath, language) {
395
+ const imports = [];
396
+ try {
397
+ const content = fs.readFileSync(filePath, 'utf-8');
398
+ if (language === 'python') {
399
+ const importPattern = /^(?:from\s+(\S+)|import\s+(\S+))/gm;
400
+ let match;
401
+ while ((match = importPattern.exec(content)) !== null) {
402
+ imports.push((match[1] || match[2]).split('.')[0]);
403
+ }
404
+ }
405
+ else if (language === 'javascript' || language === 'typescript') {
406
+ // ES6 imports
407
+ const es6Pattern = /import\s+.*?\s+from\s+['"](.+?)['"]/g;
408
+ let match;
409
+ while ((match = es6Pattern.exec(content)) !== null) {
410
+ imports.push(match[1]);
411
+ }
412
+ // CommonJS requires
413
+ const cjsPattern = /require\s*\(\s*['"](.+?)['"]\s*\)/g;
414
+ while ((match = cjsPattern.exec(content)) !== null) {
415
+ imports.push(match[1]);
416
+ }
417
+ }
418
+ else if (language === 'rust') {
419
+ const usePattern = /use\s+(\w+(?:::\w+)*)/g;
420
+ let match;
421
+ while ((match = usePattern.exec(content)) !== null) {
422
+ imports.push(match[1]);
423
+ }
424
+ }
425
+ else if (language === 'go') {
426
+ const singlePattern = /import\s+"([^"]+)"/g;
427
+ let match;
428
+ while ((match = singlePattern.exec(content)) !== null) {
429
+ imports.push(match[1]);
430
+ }
431
+ const multiPattern = /import\s*\(([\s\S]*?)\)/g;
432
+ while ((match = multiPattern.exec(content)) !== null) {
433
+ const pkgPattern = /"([^"]+)"/g;
434
+ let pkgMatch;
435
+ while ((pkgMatch = pkgPattern.exec(match[1])) !== null) {
436
+ imports.push(pkgMatch[1]);
437
+ }
438
+ }
439
+ }
440
+ }
441
+ catch {
442
+ // Ignore errors
443
+ }
444
+ return imports;
445
+ }
446
+ extractFunctions(filePath, language) {
447
+ const functions = [];
448
+ try {
449
+ const content = fs.readFileSync(filePath, 'utf-8');
450
+ if (language === 'python') {
451
+ const funcPattern = /^def\s+(\w+)\s*\(/gm;
452
+ let match;
453
+ while ((match = funcPattern.exec(content)) !== null) {
454
+ functions.push(match[1]);
455
+ }
456
+ }
457
+ else if (language === 'javascript' || language === 'typescript') {
458
+ // Function declarations
459
+ const funcPattern = /function\s+(\w+)\s*\(/g;
460
+ let match;
461
+ while ((match = funcPattern.exec(content)) !== null) {
462
+ functions.push(match[1]);
463
+ }
464
+ // Arrow functions
465
+ const arrowPattern = /const\s+(\w+)\s*=\s*(?:\([^)]*\)|[^=])\s*=>/g;
466
+ while ((match = arrowPattern.exec(content)) !== null) {
467
+ functions.push(match[1]);
468
+ }
469
+ }
470
+ else if (language === 'rust') {
471
+ const fnPattern = /fn\s+(\w+)\s*[<(]/g;
472
+ let match;
473
+ while ((match = fnPattern.exec(content)) !== null) {
474
+ functions.push(match[1]);
475
+ }
476
+ }
477
+ }
478
+ catch {
479
+ // Ignore errors
480
+ }
481
+ return functions;
482
+ }
483
+ extractClasses(filePath, language) {
484
+ const classes = [];
485
+ try {
486
+ const content = fs.readFileSync(filePath, 'utf-8');
487
+ if (language === 'python') {
488
+ const classPattern = /^class\s+(\w+)/gm;
489
+ let match;
490
+ while ((match = classPattern.exec(content)) !== null) {
491
+ classes.push(match[1]);
492
+ }
493
+ }
494
+ else if (language === 'javascript' || language === 'typescript') {
495
+ const classPattern = /class\s+(\w+)/g;
496
+ let match;
497
+ while ((match = classPattern.exec(content)) !== null) {
498
+ classes.push(match[1]);
499
+ }
500
+ }
501
+ else if (language === 'rust') {
502
+ const structPattern = /struct\s+(\w+)/g;
503
+ const enumPattern = /enum\s+(\w+)/g;
504
+ let match;
505
+ while ((match = structPattern.exec(content)) !== null) {
506
+ classes.push(match[1]);
507
+ }
508
+ while ((match = enumPattern.exec(content)) !== null) {
509
+ classes.push(match[1]);
510
+ }
511
+ }
512
+ }
513
+ catch {
514
+ // Ignore errors
515
+ }
516
+ return classes;
517
+ }
518
+ // ========================================================================
519
+ // TECHNOLOGY DETECTION
520
+ // ========================================================================
521
+ detectTechnologies() {
522
+ const configFiles = {
523
+ 'package.json': ['nodejs', 'npm'],
524
+ 'yarn.lock': ['yarn'],
525
+ 'pnpm-lock.yaml': ['pnpm'],
526
+ 'Cargo.toml': ['rust', 'cargo'],
527
+ 'requirements.txt': ['python'],
528
+ 'Pipfile': ['python', 'pipenv'],
529
+ 'poetry.lock': ['python', 'poetry'],
530
+ 'pyproject.toml': ['python'],
531
+ 'go.mod': ['go'],
532
+ 'pom.xml': ['java', 'maven'],
533
+ 'build.gradle': ['java', 'gradle'],
534
+ 'build.gradle.kts': ['kotlin', 'gradle'],
535
+ 'composer.json': ['php', 'composer'],
536
+ 'Gemfile': ['ruby', 'bundler'],
537
+ 'Dockerfile': ['docker'],
538
+ 'docker-compose.yml': ['docker', 'docker-compose'],
539
+ 'docker-compose.yaml': ['docker', 'docker-compose'],
540
+ 'kubernetes.yaml': ['kubernetes'],
541
+ 'k8s.yaml': ['kubernetes'],
542
+ 'terraform.tf': ['terraform'],
543
+ 'Jenkinsfile': ['jenkins'],
544
+ '.gitlab-ci.yml': ['gitlab-ci'],
545
+ '.travis.yml': ['travis-ci'],
546
+ 'azure-pipelines.yml': ['azure-pipelines'],
547
+ };
548
+ for (const [configFile, techs] of Object.entries(configFiles)) {
549
+ if (fs.existsSync(path.join(this.projectPath, configFile))) {
550
+ for (const tech of techs) {
551
+ this.technologies.add(tech);
552
+ }
553
+ }
554
+ }
555
+ // Check for GitHub Actions
556
+ if (fs.existsSync(path.join(this.projectPath, '.github', 'workflows'))) {
557
+ this.technologies.add('github-actions');
558
+ }
559
+ // Check package.json for frameworks
560
+ this.detectNpmFrameworks();
561
+ // Check for Python frameworks
562
+ this.detectPythonFrameworks();
563
+ }
564
+ detectNpmFrameworks() {
565
+ const pkgJsonPath = path.join(this.projectPath, 'package.json');
566
+ if (!fs.existsSync(pkgJsonPath))
567
+ return;
568
+ try {
569
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
570
+ const deps = {
571
+ ...pkg.dependencies,
572
+ ...pkg.devDependencies,
573
+ };
574
+ const frameworkMap = {
575
+ 'react': 'react',
576
+ 'vue': 'vue',
577
+ '@angular/core': 'angular',
578
+ 'svelte': 'svelte',
579
+ 'next': 'nextjs',
580
+ 'nuxt': 'nuxt',
581
+ 'gatsby': 'gatsby',
582
+ 'express': 'express',
583
+ 'fastify': 'fastify',
584
+ 'koa': 'koa',
585
+ '@nestjs/core': 'nestjs',
586
+ 'electron': 'electron',
587
+ '@tauri-apps/api': 'tauri',
588
+ 'vite': 'vite',
589
+ 'webpack': 'webpack',
590
+ 'typescript': 'typescript',
591
+ 'jest': 'jest',
592
+ 'vitest': 'vitest',
593
+ 'cypress': 'cypress',
594
+ 'playwright': 'playwright',
595
+ };
596
+ for (const [dep, tech] of Object.entries(frameworkMap)) {
597
+ if (dep in deps) {
598
+ this.technologies.add(tech);
599
+ }
600
+ }
601
+ }
602
+ catch {
603
+ // Ignore errors
604
+ }
605
+ }
606
+ detectPythonFrameworks() {
607
+ if (!this.technologies.has('python'))
608
+ return;
609
+ const frameworkImports = {
610
+ 'django': 'django',
611
+ 'flask': 'flask',
612
+ 'fastapi': 'fastapi',
613
+ 'tornado': 'tornado',
614
+ 'pyramid': 'pyramid',
615
+ 'bottle': 'bottle',
616
+ 'pytest': 'pytest',
617
+ 'unittest': 'unittest',
618
+ };
619
+ for (const fileInfo of this.files.values()) {
620
+ if (fileInfo.language === 'python') {
621
+ for (const imp of fileInfo.imports) {
622
+ const baseImp = imp.split('.')[0];
623
+ if (frameworkImports[baseImp]) {
624
+ this.technologies.add(frameworkImports[baseImp]);
625
+ }
626
+ }
627
+ }
628
+ }
629
+ }
630
+ // ========================================================================
631
+ // DEPENDENCY ANALYSIS
632
+ // ========================================================================
633
+ analyzeDependencies() {
634
+ // Python dependencies
635
+ for (const file of ['requirements.txt', 'Pipfile', 'pyproject.toml']) {
636
+ const filePath = path.join(this.projectPath, file);
637
+ if (fs.existsSync(filePath)) {
638
+ this.dependencies['python'] = this.parsePythonDeps(filePath);
639
+ break;
640
+ }
641
+ }
642
+ // Node dependencies
643
+ const pkgJson = path.join(this.projectPath, 'package.json');
644
+ if (fs.existsSync(pkgJson)) {
645
+ this.dependencies['npm'] = this.parseNpmDeps(pkgJson);
646
+ }
647
+ // Rust dependencies
648
+ const cargoToml = path.join(this.projectPath, 'Cargo.toml');
649
+ if (fs.existsSync(cargoToml)) {
650
+ this.dependencies['cargo'] = this.parseCargoDeps(cargoToml);
651
+ }
652
+ // Go dependencies
653
+ const goMod = path.join(this.projectPath, 'go.mod');
654
+ if (fs.existsSync(goMod)) {
655
+ this.dependencies['go'] = this.parseGoDeps(goMod);
656
+ }
657
+ }
658
+ parsePythonDeps(filePath) {
659
+ const deps = [];
660
+ if (filePath.endsWith('requirements.txt')) {
661
+ try {
662
+ const content = fs.readFileSync(filePath, 'utf-8');
663
+ for (const line of content.split('\n')) {
664
+ const trimmed = line.trim();
665
+ if (trimmed && !trimmed.startsWith('#')) {
666
+ const pkg = trimmed.split('==')[0].split('>=')[0].split('~=')[0].trim();
667
+ deps.push(pkg);
668
+ }
669
+ }
670
+ }
671
+ catch {
672
+ // Ignore errors
673
+ }
674
+ }
675
+ else if (filePath.endsWith('pyproject.toml')) {
676
+ try {
677
+ const content = fs.readFileSync(filePath, 'utf-8');
678
+ // Simple parsing for dependencies
679
+ const depMatch = content.match(/dependencies\s*=\s*\[([\s\S]*?)\]/);
680
+ if (depMatch) {
681
+ const pkgPattern = /"([^">=<~!]+)/g;
682
+ let match;
683
+ while ((match = pkgPattern.exec(depMatch[1])) !== null) {
684
+ deps.push(match[1].trim());
685
+ }
686
+ }
687
+ }
688
+ catch {
689
+ // Ignore errors
690
+ }
691
+ }
692
+ return deps;
693
+ }
694
+ parseNpmDeps(filePath) {
695
+ try {
696
+ const pkg = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
697
+ return Object.keys({
698
+ ...pkg.dependencies,
699
+ ...pkg.devDependencies,
700
+ });
701
+ }
702
+ catch {
703
+ return [];
704
+ }
705
+ }
706
+ parseCargoDeps(filePath) {
707
+ try {
708
+ const content = fs.readFileSync(filePath, 'utf-8');
709
+ const deps = [];
710
+ const depSection = content.match(/\[dependencies\]([\s\S]*?)(?:\[|$)/);
711
+ if (depSection) {
712
+ const lines = depSection[1].split('\n');
713
+ for (const line of lines) {
714
+ const match = line.match(/^(\w+)\s*=/);
715
+ if (match) {
716
+ deps.push(match[1]);
717
+ }
718
+ }
719
+ }
720
+ return deps;
721
+ }
722
+ catch {
723
+ return [];
724
+ }
725
+ }
726
+ parseGoDeps(filePath) {
727
+ const deps = [];
728
+ try {
729
+ const content = fs.readFileSync(filePath, 'utf-8');
730
+ const requirePattern = /require\s+([^\s]+)/g;
731
+ let match;
732
+ while ((match = requirePattern.exec(content)) !== null) {
733
+ deps.push(match[1]);
734
+ }
735
+ }
736
+ catch {
737
+ // Ignore errors
738
+ }
739
+ return deps;
740
+ }
741
+ // ========================================================================
742
+ // PROJECT STRUCTURE
743
+ // ========================================================================
744
+ getDirectoryStructure() {
745
+ const structure = {};
746
+ for (const filePath of this.files.keys()) {
747
+ const parts = filePath.split(path.sep);
748
+ let current = structure;
749
+ for (let i = 0; i < parts.length - 1; i++) {
750
+ if (!(parts[i] in current)) {
751
+ current[parts[i]] = {};
752
+ }
753
+ current = current[parts[i]];
754
+ }
755
+ current[parts[parts.length - 1]] = 'file';
756
+ }
757
+ return structure;
758
+ }
759
+ findEntryPoints() {
760
+ const entryPoints = [];
761
+ const commonEntries = new Set([
762
+ 'main.py', 'app.py', 'index.py', '__main__.py', 'run.py', 'wsgi.py',
763
+ 'index.js', 'app.js', 'main.js', 'server.js', 'index.ts', 'main.ts',
764
+ 'main.rs', 'lib.rs',
765
+ 'main.go',
766
+ 'Main.java', 'Application.java',
767
+ 'Program.cs',
768
+ 'index.html', 'index.php',
769
+ ]);
770
+ for (const filePath of this.files.keys()) {
771
+ const fileName = path.basename(filePath);
772
+ if (commonEntries.has(fileName)) {
773
+ entryPoints.push(filePath);
774
+ }
775
+ }
776
+ return entryPoints;
777
+ }
778
+ findConfigFiles() {
779
+ const configFiles = [];
780
+ const configPatterns = [
781
+ 'config', 'settings', '.env', 'environment',
782
+ 'package.json', 'tsconfig.json', 'webpack.config',
783
+ 'Cargo.toml', 'pom.xml', 'build.gradle',
784
+ 'requirements.txt', 'setup.py', 'pyproject.toml',
785
+ 'docker-compose', 'Dockerfile', '.gitignore',
786
+ '.eslintrc', '.prettierrc', 'babel.config',
787
+ ];
788
+ for (const filePath of this.files.keys()) {
789
+ const fileName = path.basename(filePath).toLowerCase();
790
+ if (configPatterns.some(pattern => fileName.includes(pattern))) {
791
+ configFiles.push(filePath);
792
+ }
793
+ }
794
+ return configFiles;
795
+ }
796
+ findTestFiles() {
797
+ const testFiles = [];
798
+ const testPatterns = ['test', 'spec', '_test', '.test.', '.spec.', '__test__'];
799
+ const testDirs = ['test', 'tests', '__tests__'];
800
+ for (const filePath of this.files.keys()) {
801
+ const fileName = path.basename(filePath).toLowerCase();
802
+ const fileDir = path.dirname(filePath).toLowerCase();
803
+ if (testPatterns.some(pattern => fileName.includes(pattern))) {
804
+ testFiles.push(filePath);
805
+ }
806
+ else if (testDirs.some(pattern => fileDir.includes(pattern))) {
807
+ testFiles.push(filePath);
808
+ }
809
+ }
810
+ return testFiles;
811
+ }
812
+ // ========================================================================
813
+ // STATISTICS
814
+ // ========================================================================
815
+ getLanguageStats() {
816
+ const stats = {};
817
+ for (const fileInfo of this.files.values()) {
818
+ stats[fileInfo.language] = (stats[fileInfo.language] || 0) + 1;
819
+ }
820
+ return stats;
821
+ }
822
+ getProjectStatus() {
823
+ if (this.files.size === 0) {
824
+ this.scan();
825
+ }
826
+ const langStats = this.getLanguageStats();
827
+ const mainLanguage = Object.entries(langStats).sort((a, b) => b[1] - a[1])[0]?.[0] || 'unknown';
828
+ return {
829
+ totalFiles: this.files.size,
830
+ languages: Object.keys(langStats),
831
+ mainLanguage,
832
+ hasTests: Array.from(this.files.keys()).some(f => f.toLowerCase().includes('test')),
833
+ hasCi: ['github-actions', 'jenkins', 'gitlab-ci', 'travis-ci'].some(ci => this.technologies.has(ci)),
834
+ hasDocker: this.technologies.has('docker'),
835
+ isGitRepo: fs.existsSync(path.join(this.projectPath, '.git')),
836
+ technologies: Array.from(this.technologies),
837
+ };
838
+ }
839
+ getStatistics() {
840
+ return this.stats;
841
+ }
842
+ getFiles() {
843
+ return this.files;
844
+ }
845
+ }
846
+ // ============================================================================
847
+ // SINGLETON INSTANCE
848
+ // ============================================================================
849
+ let scannerInstance = null;
850
+ export function getProjectScanner(projectPath) {
851
+ if (!scannerInstance || (projectPath && scannerInstance['projectPath'] !== path.resolve(projectPath))) {
852
+ scannerInstance = new ProjectScanner(projectPath || process.cwd());
853
+ }
854
+ return scannerInstance;
855
+ }
856
+ export function resetProjectScanner() {
857
+ scannerInstance = null;
858
+ }
859
+ //# sourceMappingURL=projectScanner.js.map