ai-first-cli 1.3.5 → 1.3.8

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 (126) hide show
  1. package/CHANGELOG.md +186 -0
  2. package/README.es.md +68 -0
  3. package/README.md +53 -15
  4. package/ai/graph/knowledge-graph.json +1 -1
  5. package/ai-context/index-state.json +86 -2
  6. package/dist/analyzers/architecture.d.ts.map +1 -1
  7. package/dist/analyzers/architecture.js +72 -5
  8. package/dist/analyzers/architecture.js.map +1 -1
  9. package/dist/analyzers/entrypoints.d.ts.map +1 -1
  10. package/dist/analyzers/entrypoints.js +253 -0
  11. package/dist/analyzers/entrypoints.js.map +1 -1
  12. package/dist/analyzers/symbols.d.ts.map +1 -1
  13. package/dist/analyzers/symbols.js +47 -2
  14. package/dist/analyzers/symbols.js.map +1 -1
  15. package/dist/analyzers/techStack.d.ts.map +1 -1
  16. package/dist/analyzers/techStack.js +86 -0
  17. package/dist/analyzers/techStack.js.map +1 -1
  18. package/dist/commands/ai-first.d.ts.map +1 -1
  19. package/dist/commands/ai-first.js +78 -4
  20. package/dist/commands/ai-first.js.map +1 -1
  21. package/dist/config/configLoader.d.ts +6 -0
  22. package/dist/config/configLoader.d.ts.map +1 -0
  23. package/dist/config/configLoader.js +232 -0
  24. package/dist/config/configLoader.js.map +1 -0
  25. package/dist/config/index.d.ts +3 -0
  26. package/dist/config/index.d.ts.map +1 -0
  27. package/dist/config/index.js +2 -0
  28. package/dist/config/index.js.map +1 -0
  29. package/dist/config/types.d.ts +101 -0
  30. package/dist/config/types.d.ts.map +1 -0
  31. package/dist/config/types.js +2 -0
  32. package/dist/config/types.js.map +1 -0
  33. package/dist/core/content/contentProcessor.d.ts +4 -0
  34. package/dist/core/content/contentProcessor.d.ts.map +1 -0
  35. package/dist/core/content/contentProcessor.js +235 -0
  36. package/dist/core/content/contentProcessor.js.map +1 -0
  37. package/dist/core/content/index.d.ts +3 -0
  38. package/dist/core/content/index.d.ts.map +1 -0
  39. package/dist/core/content/index.js +2 -0
  40. package/dist/core/content/index.js.map +1 -0
  41. package/dist/core/content/types.d.ts +32 -0
  42. package/dist/core/content/types.d.ts.map +1 -0
  43. package/dist/core/content/types.js +2 -0
  44. package/dist/core/content/types.js.map +1 -0
  45. package/dist/core/gitAnalyzer.d.ts +14 -0
  46. package/dist/core/gitAnalyzer.d.ts.map +1 -1
  47. package/dist/core/gitAnalyzer.js +98 -0
  48. package/dist/core/gitAnalyzer.js.map +1 -1
  49. package/dist/core/multiRepo/index.d.ts +3 -0
  50. package/dist/core/multiRepo/index.d.ts.map +1 -0
  51. package/dist/core/multiRepo/index.js +2 -0
  52. package/dist/core/multiRepo/index.js.map +1 -0
  53. package/dist/core/multiRepo/multiRepoScanner.d.ts +18 -0
  54. package/dist/core/multiRepo/multiRepoScanner.d.ts.map +1 -0
  55. package/dist/core/multiRepo/multiRepoScanner.js +131 -0
  56. package/dist/core/multiRepo/multiRepoScanner.js.map +1 -0
  57. package/dist/core/rag/index.d.ts +3 -0
  58. package/dist/core/rag/index.d.ts.map +1 -0
  59. package/dist/core/rag/index.js +2 -0
  60. package/dist/core/rag/index.js.map +1 -0
  61. package/dist/core/rag/vectorIndex.d.ts +28 -0
  62. package/dist/core/rag/vectorIndex.d.ts.map +1 -0
  63. package/dist/core/rag/vectorIndex.js +71 -0
  64. package/dist/core/rag/vectorIndex.js.map +1 -0
  65. package/dist/mcp/index.d.ts +2 -0
  66. package/dist/mcp/index.d.ts.map +1 -0
  67. package/dist/mcp/index.js +2 -0
  68. package/dist/mcp/index.js.map +1 -0
  69. package/dist/mcp/server.d.ts +7 -0
  70. package/dist/mcp/server.d.ts.map +1 -0
  71. package/dist/mcp/server.js +154 -0
  72. package/dist/mcp/server.js.map +1 -0
  73. package/dist/utils/fileUtils.d.ts.map +1 -1
  74. package/dist/utils/fileUtils.js +5 -0
  75. package/dist/utils/fileUtils.js.map +1 -1
  76. package/docs/planning/evaluator-v1.0.0/README.md +112 -0
  77. package/docs/planning/evaluator-v1.0.0/improvements_plan_2026-03-28.md +237 -0
  78. package/package.json +13 -3
  79. package/src/analyzers/architecture.ts +75 -6
  80. package/src/analyzers/entrypoints.ts +285 -0
  81. package/src/analyzers/symbols.ts +52 -2
  82. package/src/analyzers/techStack.ts +90 -0
  83. package/src/commands/ai-first.ts +83 -4
  84. package/src/config/configLoader.ts +274 -0
  85. package/src/config/index.ts +27 -0
  86. package/src/config/types.ts +117 -0
  87. package/src/core/content/contentProcessor.ts +292 -0
  88. package/src/core/content/index.ts +9 -0
  89. package/src/core/content/types.ts +35 -0
  90. package/src/core/gitAnalyzer.ts +130 -0
  91. package/src/core/multiRepo/index.ts +2 -0
  92. package/src/core/multiRepo/multiRepoScanner.ts +177 -0
  93. package/src/core/rag/index.ts +2 -0
  94. package/src/core/rag/vectorIndex.ts +105 -0
  95. package/src/mcp/index.ts +1 -0
  96. package/src/mcp/server.ts +179 -0
  97. package/src/utils/fileUtils.ts +5 -0
  98. package/tests/entrypoints-languages.test.ts +373 -0
  99. package/tests/framework-detection.test.ts +296 -0
  100. package/tests/v1.3.8-integration.test.ts +361 -0
  101. package/BETA_EVALUATION_REPORT.md +0 -151
  102. package/ai-context/context/flows/App.json +0 -17
  103. package/ai-context/context/flows/DashboardPage.json +0 -14
  104. package/ai-context/context/flows/LoginPage.json +0 -14
  105. package/ai-context/context/flows/admin.json +0 -10
  106. package/ai-context/context/flows/androidresources.json +0 -11
  107. package/ai-context/context/flows/authController.json +0 -14
  108. package/ai-context/context/flows/entrypoints.json +0 -9
  109. package/ai-context/context/flows/fastapiAdapter.json +0 -14
  110. package/ai-context/context/flows/fastapiadapter.json +0 -11
  111. package/ai-context/context/flows/index.json +0 -19
  112. package/ai-context/context/flows/indexer.json +0 -9
  113. package/ai-context/context/flows/indexstate.json +0 -9
  114. package/ai-context/context/flows/init.json +0 -22
  115. package/ai-context/context/flows/main.json +0 -18
  116. package/ai-context/context/flows/mainactivity.json +0 -9
  117. package/ai-context/context/flows/models.json +0 -15
  118. package/ai-context/context/flows/posts.json +0 -15
  119. package/ai-context/context/flows/repoMapper.json +0 -20
  120. package/ai-context/context/flows/repomapper.json +0 -11
  121. package/ai-context/context/flows/serializers.json +0 -10
  122. package/ai-context-evaluation-report-1774223059505.md +0 -206
  123. package/dist/scripts/ai-context-evaluator.js +0 -367
  124. package/quick-evaluation-report-1774396002305.md +0 -64
  125. package/quick-evaluator.ts +0 -200
  126. package/scripts/ai-context-evaluator.ts +0 -440
@@ -0,0 +1,292 @@
1
+ import { ContentProcessorOptions, ProcessedContent, DetailLevel, InclusionLevel } from './types.js';
2
+
3
+ export function processContent(
4
+ content: string,
5
+ options: ContentProcessorOptions = {}
6
+ ): ProcessedContent {
7
+ const {
8
+ inclusionLevel = 'full',
9
+ detailLevel = 'full',
10
+ language = detectLanguage(content),
11
+ preserveComments = true,
12
+ preserveImports = true
13
+ } = options;
14
+
15
+ const originalLength = content.length;
16
+ let processedContent = content;
17
+
18
+ if (inclusionLevel === 'directory') {
19
+ return {
20
+ originalLength,
21
+ processedLength: 0,
22
+ compressionRatio: 100,
23
+ content: '',
24
+ tokens: 0
25
+ };
26
+ }
27
+
28
+ if (inclusionLevel === 'exclude') {
29
+ return {
30
+ originalLength,
31
+ processedLength: 0,
32
+ compressionRatio: 100,
33
+ content: '',
34
+ tokens: 0
35
+ };
36
+ }
37
+
38
+ if (inclusionLevel === 'compress' || detailLevel !== 'full') {
39
+ processedContent = compressContent(content, detailLevel, language, preserveComments, preserveImports);
40
+ }
41
+
42
+ const processedLength = processedContent.length;
43
+ const compressionRatio = originalLength > 0
44
+ ? ((originalLength - processedLength) / originalLength) * 100
45
+ : 0;
46
+ const tokens = estimateTokens(processedContent);
47
+
48
+ return {
49
+ originalLength,
50
+ processedLength,
51
+ compressionRatio,
52
+ content: processedContent,
53
+ tokens
54
+ };
55
+ }
56
+
57
+ function compressContent(
58
+ content: string,
59
+ detailLevel: DetailLevel,
60
+ language: string,
61
+ preserveComments: boolean,
62
+ preserveImports: boolean
63
+ ): string {
64
+ const lines = content.split('\n');
65
+ const result: string[] = [];
66
+ let inFunction = false;
67
+ let braceCount = 0;
68
+ let functionSignature: string | null = null;
69
+ let inComment = false;
70
+ let commentBlock: string[] = [];
71
+
72
+ for (let i = 0; i < lines.length; i++) {
73
+ const line = lines[i];
74
+ const trimmed = line.trim();
75
+
76
+ if (trimmed.startsWith('/*')) {
77
+ inComment = true;
78
+ }
79
+
80
+ if (inComment) {
81
+ if (preserveComments && detailLevel !== 'skeleton') {
82
+ commentBlock.push(line);
83
+ }
84
+ if (trimmed.includes('*/')) {
85
+ inComment = false;
86
+ if (commentBlock.length > 0) {
87
+ result.push(...commentBlock);
88
+ commentBlock = [];
89
+ }
90
+ }
91
+ continue;
92
+ }
93
+
94
+ if (trimmed.startsWith('//') || trimmed.startsWith('#')) {
95
+ if (preserveComments && detailLevel !== 'skeleton') {
96
+ result.push(line);
97
+ }
98
+ continue;
99
+ }
100
+
101
+ if (preserveImports && isImportLine(trimmed, language)) {
102
+ result.push(line);
103
+ continue;
104
+ }
105
+
106
+ if (isExportLine(trimmed, language)) {
107
+ if (detailLevel === 'skeleton') {
108
+ const simplified = simplifyExport(line, language);
109
+ if (simplified) result.push(simplified);
110
+ continue;
111
+ }
112
+ }
113
+
114
+ if (isFunctionSignature(line, language)) {
115
+ if (inFunction) {
116
+ braceCount = 0;
117
+ }
118
+ inFunction = true;
119
+ functionSignature = line;
120
+ braceCount += countBraces(line);
121
+
122
+ if (detailLevel === 'signatures' || detailLevel === 'skeleton') {
123
+ const signature = extractSignature(line, language);
124
+ if (signature) result.push(signature);
125
+ }
126
+ continue;
127
+ }
128
+
129
+ if (inFunction) {
130
+ braceCount += countBraces(line);
131
+ if (braceCount <= 0) {
132
+ inFunction = false;
133
+ functionSignature = null;
134
+ }
135
+
136
+ if (detailLevel === 'full') {
137
+ result.push(line);
138
+ }
139
+ continue;
140
+ }
141
+
142
+ if (detailLevel === 'full') {
143
+ result.push(line);
144
+ }
145
+ }
146
+
147
+ return result.join('\n');
148
+ }
149
+
150
+ function isImportLine(line: string, language: string): boolean {
151
+ const importPatterns = [
152
+ /^import\s+/,
153
+ /^from\s+\w+\s+import/,
154
+ /^require\s*\(/,
155
+ /^using\s+/,
156
+ /^#include/,
157
+ /^#import/,
158
+ /^const\s+\w+\s+=\s+require\s*\(/,
159
+ ];
160
+ return importPatterns.some(pattern => pattern.test(line));
161
+ }
162
+
163
+ function isExportLine(line: string, language: string): boolean {
164
+ const exportPatterns = [
165
+ /^export\s+/,
166
+ /^module\.exports\s*=/,
167
+ /^exports\./,
168
+ /^public\s+/,
169
+ ];
170
+ return exportPatterns.some(pattern => pattern.test(line));
171
+ }
172
+
173
+ function simplifyExport(line: string, language: string): string | null {
174
+ const simplified = line.replace(/\{[\s\S]*\}/, '{ ... }');
175
+ return simplified;
176
+ }
177
+
178
+ function isFunctionSignature(line: string, language: string): boolean {
179
+ const patterns = [
180
+ /^(export\s+)?(async\s+)?function\s+\w+\s*\(/,
181
+ /^(export\s+)?(async\s+)?function\s*\(/,
182
+ /^(export\s+)?const\s+\w+\s*=\s*(async\s*)?\(/,
183
+ /^(export\s+)?(async\s+)?\w+\s*\([^)]*\)\s*{/,
184
+ /^(export\s+)?(async\s+)?\w+\s*\([^)]*\)\s*:\s*\w+\s*{/,
185
+ /^(export\s+)?class\s+\w+/,
186
+ /^(export\s+)?interface\s+\w+/,
187
+ /^(export\s+)?type\s+\w+\s*=/,
188
+ /^(public|private|protected)\s+(async\s+)?\w+\s*\(/,
189
+ /^\s*(async\s+)?def\s+\w+\s*\(/,
190
+ /^func\s+\w+\s*\(/,
191
+ ];
192
+ return patterns.some(pattern => pattern.test(line));
193
+ }
194
+
195
+ function extractSignature(line: string, language: string): string | null {
196
+ const signatureMatch = line.match(/^(.*\{)/);
197
+ if (signatureMatch) {
198
+ return signatureMatch[1] + ' ... }';
199
+ }
200
+
201
+ const arrowMatch = line.match(/^(.*=>).*$/);
202
+ if (arrowMatch) {
203
+ return arrowMatch[1] + ' ...;';
204
+ }
205
+
206
+ const classMatch = line.match(/^(export\s+)?class\s+\w+([^{]*)(\{)?/);
207
+ if (classMatch) {
208
+ return classMatch[1] || '' + 'class ' + line.match(/class\s+(\w+)/)?.[1] + ' { ... }';
209
+ }
210
+
211
+ const interfaceMatch = line.match(/^(export\s+)?interface\s+\w+/);
212
+ if (interfaceMatch) {
213
+ return line + ' { ... }';
214
+ }
215
+
216
+ return line + ';';
217
+ }
218
+
219
+ function countBraces(line: string): number {
220
+ let count = 0;
221
+ for (const char of line) {
222
+ if (char === '{') count++;
223
+ if (char === '}') count--;
224
+ }
225
+ return count;
226
+ }
227
+
228
+ function detectLanguage(content: string): string {
229
+ if (content.includes('interface ') || content.includes(': string') || content.includes(': number')) {
230
+ return 'typescript';
231
+ }
232
+ if (content.includes('def ') || content.includes('import ') && content.includes(':')) {
233
+ return 'python';
234
+ }
235
+ if (content.includes('func ') || content.includes('package main')) {
236
+ return 'go';
237
+ }
238
+ if (content.includes('fn ') || content.includes('let mut ')) {
239
+ return 'rust';
240
+ }
241
+ return 'javascript';
242
+ }
243
+
244
+ function estimateTokens(content: string): number {
245
+ return Math.ceil(content.length / 4);
246
+ }
247
+
248
+ export function classifyFile(
249
+ filePath: string,
250
+ includePatterns: string[],
251
+ compressPatterns: string[],
252
+ directoryPatterns: string[],
253
+ excludePatterns: string[]
254
+ ): InclusionLevel {
255
+ if (matchesPatterns(filePath, excludePatterns)) {
256
+ return 'exclude';
257
+ }
258
+
259
+ if (matchesPatterns(filePath, directoryPatterns)) {
260
+ return 'directory';
261
+ }
262
+
263
+ if (matchesPatterns(filePath, compressPatterns)) {
264
+ return 'compress';
265
+ }
266
+
267
+ if (includePatterns.length === 0 || matchesPatterns(filePath, includePatterns)) {
268
+ return 'full';
269
+ }
270
+
271
+ return 'exclude';
272
+ }
273
+
274
+ function matchesPatterns(filePath: string, patterns: string[]): boolean {
275
+ if (patterns.length === 0) return false;
276
+
277
+ return patterns.some(pattern => {
278
+ const regex = globToRegex(pattern);
279
+ return regex.test(filePath);
280
+ });
281
+ }
282
+
283
+ function globToRegex(pattern: string): RegExp {
284
+ let regex = pattern
285
+ .replace(/\*\*/g, '{{GLOBSTAR}}')
286
+ .replace(/\*/g, '[^/]*')
287
+ .replace(/\?/g, '.')
288
+ .replace(/\./g, '\\.')
289
+ .replace('{{GLOBSTAR}}', '.*');
290
+
291
+ return new RegExp(`^${regex}$`);
292
+ }
@@ -0,0 +1,9 @@
1
+ export { processContent, classifyFile } from './contentProcessor.js';
2
+ export type {
3
+ InclusionLevel,
4
+ DetailLevel,
5
+ FileClassification,
6
+ ContentProcessorOptions,
7
+ ProcessedContent,
8
+ CompressionStats
9
+ } from './types.js';
@@ -0,0 +1,35 @@
1
+ export type InclusionLevel = 'full' | 'compress' | 'directory' | 'exclude';
2
+ export type DetailLevel = 'full' | 'signatures' | 'skeleton';
3
+
4
+ export interface FileClassification {
5
+ path: string;
6
+ inclusionLevel: InclusionLevel;
7
+ detailLevel: DetailLevel;
8
+ }
9
+
10
+ export interface ContentProcessorOptions {
11
+ inclusionLevel?: InclusionLevel;
12
+ detailLevel?: DetailLevel;
13
+ language?: string;
14
+ preserveComments?: boolean;
15
+ preserveImports?: boolean;
16
+ }
17
+
18
+ export interface ProcessedContent {
19
+ originalLength: number;
20
+ processedLength: number;
21
+ compressionRatio: number;
22
+ content: string;
23
+ tokens: number;
24
+ }
25
+
26
+ export interface CompressionStats {
27
+ totalFiles: number;
28
+ fullFiles: number;
29
+ compressedFiles: number;
30
+ directoryOnlyFiles: number;
31
+ excludedFiles: number;
32
+ originalTokens: number;
33
+ processedTokens: number;
34
+ savingsPercentage: number;
35
+ }
@@ -389,3 +389,133 @@ export function generateGitContext(rootDir: string, aiDir?: string): {
389
389
  activity
390
390
  };
391
391
  }
392
+
393
+ export interface GitBlameLine {
394
+ line: number;
395
+ content: string;
396
+ author: string;
397
+ date: string;
398
+ hash: string;
399
+ }
400
+
401
+ export interface GitBlameResult {
402
+ filePath: string;
403
+ lines: GitBlameLine[];
404
+ authors: Map<string, number>;
405
+ }
406
+
407
+ export function getGitBlame(rootDir: string, filePath: string): GitBlameResult {
408
+ const fullPath = path.join(rootDir, filePath);
409
+
410
+ if (!fs.existsSync(fullPath)) {
411
+ return {
412
+ filePath,
413
+ lines: [],
414
+ authors: new Map()
415
+ };
416
+ }
417
+
418
+ if (!detectGitRepository(rootDir)) {
419
+ const content = fs.readFileSync(fullPath, 'utf-8');
420
+ const lines = content.split('\n');
421
+ return {
422
+ filePath,
423
+ lines: lines.map((content, idx) => ({
424
+ line: idx + 1,
425
+ content,
426
+ author: 'unknown',
427
+ date: '',
428
+ hash: ''
429
+ })),
430
+ authors: new Map([['unknown', lines.length]])
431
+ };
432
+ }
433
+
434
+ const blameOutput = gitExec(rootDir, `git blame --line-porcelain "${filePath}"`);
435
+
436
+ if (!blameOutput) {
437
+ return {
438
+ filePath,
439
+ lines: [],
440
+ authors: new Map()
441
+ };
442
+ }
443
+
444
+ const lines: GitBlameLine[] = [];
445
+ const authors = new Map<string, number>();
446
+ const lineData: { hash?: string; author?: string; date?: string; content?: string } = {};
447
+ let lineNumber = 0;
448
+
449
+ const blameLines = blameOutput.split('\n');
450
+
451
+ for (const blameLine of blameLines) {
452
+ if (blameLine.startsWith('\t')) {
453
+ lineData.content = blameLine.slice(1);
454
+ lineNumber++;
455
+
456
+ const author = lineData.author || 'unknown';
457
+ const date = lineData.date || '';
458
+ const hash = lineData.hash || '';
459
+
460
+ lines.push({
461
+ line: lineNumber,
462
+ content: lineData.content,
463
+ author,
464
+ date,
465
+ hash
466
+ });
467
+
468
+ authors.set(author, (authors.get(author) || 0) + 1);
469
+
470
+ lineData.hash = undefined;
471
+ lineData.author = undefined;
472
+ lineData.date = undefined;
473
+ lineData.content = undefined;
474
+ } else if (blameLine.startsWith('author ')) {
475
+ lineData.author = blameLine.slice(7);
476
+ } else if (blameLine.startsWith('author-time ')) {
477
+ const timestamp = parseInt(blameLine.slice(12), 10);
478
+ lineData.date = new Date(timestamp * 1000).toISOString().split('T')[0];
479
+ } else if (!blameLine.startsWith(' ') && blameLine.length >= 40) {
480
+ lineData.hash = blameLine.split(' ')[0];
481
+ }
482
+ }
483
+
484
+ return {
485
+ filePath,
486
+ lines,
487
+ authors
488
+ };
489
+ }
490
+
491
+ export function formatGitBlame(
492
+ blameResult: GitBlameResult,
493
+ format: 'inline' | 'block' = 'inline'
494
+ ): string {
495
+ if (format === 'block') {
496
+ const sections: string[] = [];
497
+ let currentAuthor = '';
498
+ let currentSection: string[] = [];
499
+
500
+ for (const line of blameResult.lines) {
501
+ if (line.author !== currentAuthor) {
502
+ if (currentSection.length > 0) {
503
+ sections.push(`// ${currentAuthor}\n${currentSection.join('\n')}`);
504
+ }
505
+ currentAuthor = line.author;
506
+ currentSection = [];
507
+ }
508
+ currentSection.push(line.content);
509
+ }
510
+
511
+ if (currentSection.length > 0) {
512
+ sections.push(`// ${currentAuthor}\n${currentSection.join('\n')}`);
513
+ }
514
+
515
+ return sections.join('\n\n');
516
+ }
517
+
518
+ return blameResult.lines
519
+ .map(line => `[${line.author} ${line.date}] ${line.content}`)
520
+ .join('\n');
521
+ }
@@ -0,0 +1,2 @@
1
+ export { scanMultiRepo, generateMultiRepoReport } from './multiRepoScanner.js';
2
+ export type { Repository, MultiRepoContext, MultiRepoOptions } from './multiRepoScanner.js';
@@ -0,0 +1,177 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import { scanRepo, FileInfo } from '../repoScanner.js';
4
+
5
+ export interface Repository {
6
+ name: string;
7
+ path: string;
8
+ files: FileInfo[];
9
+ }
10
+
11
+ export interface MultiRepoContext {
12
+ repositories: Repository[];
13
+ totalFiles: number;
14
+ crossRepoDependencies: Map<string, string[]>;
15
+ }
16
+
17
+ export interface MultiRepoOptions {
18
+ repositories: string[];
19
+ includeSubmodules?: boolean;
20
+ }
21
+
22
+ export function scanMultiRepo(options: MultiRepoOptions): MultiRepoContext {
23
+ const repositories: Repository[] = [];
24
+ const crossRepoDependencies = new Map<string, string[]>();
25
+ let totalFiles = 0;
26
+
27
+ for (const repoPath of options.repositories) {
28
+ const resolvedPath = path.resolve(repoPath);
29
+
30
+ if (!fs.existsSync(resolvedPath)) {
31
+ console.warn(`Repository path does not exist: ${resolvedPath}`);
32
+ continue;
33
+ }
34
+
35
+ const repoName = path.basename(resolvedPath);
36
+ const scanResult = scanRepo(resolvedPath);
37
+
38
+ repositories.push({
39
+ name: repoName,
40
+ path: resolvedPath,
41
+ files: scanResult.files
42
+ });
43
+
44
+ totalFiles += scanResult.totalFiles;
45
+ }
46
+
47
+ if (options.includeSubmodules) {
48
+ for (const repo of repositories) {
49
+ const submodules = detectSubmodules(repo.path);
50
+ for (const submodule of submodules) {
51
+ const submoduleName = path.basename(submodule);
52
+ const scanResult = scanRepo(submodule);
53
+
54
+ repositories.push({
55
+ name: `${repo.name}/${submoduleName}`,
56
+ path: submodule,
57
+ files: scanResult.files
58
+ });
59
+
60
+ totalFiles += scanResult.totalFiles;
61
+ }
62
+ }
63
+ }
64
+
65
+ detectCrossRepoDependencies(repositories, crossRepoDependencies);
66
+
67
+ return {
68
+ repositories,
69
+ totalFiles,
70
+ crossRepoDependencies
71
+ };
72
+ }
73
+
74
+ function detectSubmodules(repoPath: string): string[] {
75
+ const submodules: string[] = [];
76
+ const gitmodulesPath = path.join(repoPath, '.gitmodules');
77
+
78
+ if (!fs.existsSync(gitmodulesPath)) {
79
+ return submodules;
80
+ }
81
+
82
+ try {
83
+ const content = fs.readFileSync(gitmodulesPath, 'utf-8');
84
+ const matches = content.match(/path\s*=\s*(.+)/g);
85
+
86
+ if (matches) {
87
+ for (const match of matches) {
88
+ const submodulePath = match.split('=')[1].trim();
89
+ const fullPath = path.join(repoPath, submodulePath);
90
+ if (fs.existsSync(fullPath)) {
91
+ submodules.push(fullPath);
92
+ }
93
+ }
94
+ }
95
+ } catch {
96
+ // Ignore errors reading .gitmodules
97
+ }
98
+
99
+ return submodules;
100
+ }
101
+
102
+ function detectCrossRepoDependencies(
103
+ repositories: Repository[],
104
+ dependencies: Map<string, string[]>
105
+ ): void {
106
+ for (const repo of repositories) {
107
+ const deps: string[] = [];
108
+
109
+ for (const otherRepo of repositories) {
110
+ if (repo.name === otherRepo.name) continue;
111
+
112
+ const hasDependency = checkDependency(repo, otherRepo);
113
+ if (hasDependency) {
114
+ deps.push(otherRepo.name);
115
+ }
116
+ }
117
+
118
+ if (deps.length > 0) {
119
+ dependencies.set(repo.name, deps);
120
+ }
121
+ }
122
+ }
123
+
124
+ function checkDependency(repoA: Repository, repoB: Repository): boolean {
125
+ const packageJsonA = path.join(repoA.path, 'package.json');
126
+
127
+ if (fs.existsSync(packageJsonA)) {
128
+ try {
129
+ const pkg = JSON.parse(fs.readFileSync(packageJsonA, 'utf-8'));
130
+ const deps = {
131
+ ...pkg.dependencies,
132
+ ...pkg.devDependencies,
133
+ ...pkg.peerDependencies
134
+ };
135
+
136
+ for (const dep of Object.keys(deps)) {
137
+ if (dep.includes(repoB.name.toLowerCase())) {
138
+ return true;
139
+ }
140
+ }
141
+ } catch {
142
+ // Ignore errors reading package.json
143
+ }
144
+ }
145
+
146
+ return false;
147
+ }
148
+
149
+ export function generateMultiRepoReport(context: MultiRepoContext): string {
150
+ const lines: string[] = [];
151
+
152
+ lines.push('# Multi-Repository Context\n');
153
+ lines.push(`## Summary`);
154
+ lines.push(`- **Total Repositories:** ${context.repositories.length}`);
155
+ lines.push(`- **Total Files:** ${context.totalFiles}\n`);
156
+
157
+ lines.push(`## Repositories`);
158
+ for (const repo of context.repositories) {
159
+ lines.push(`\n### ${repo.name}`);
160
+ lines.push(`- **Path:** ${repo.path}`);
161
+ lines.push(`- **Files:** ${repo.files.length}`);
162
+
163
+ const deps = context.crossRepoDependencies.get(repo.name);
164
+ if (deps && deps.length > 0) {
165
+ lines.push(`- **Dependencies:** ${deps.join(', ')}`);
166
+ }
167
+ }
168
+
169
+ if (context.crossRepoDependencies.size > 0) {
170
+ lines.push(`\n## Cross-Repository Dependencies`);
171
+ for (const [repo, deps] of context.crossRepoDependencies) {
172
+ lines.push(`- **${repo}** depends on: ${deps.join(', ')}`);
173
+ }
174
+ }
175
+
176
+ return lines.join('\n');
177
+ }
@@ -0,0 +1,2 @@
1
+ export { VectorIndex, createVectorIndex, semanticSearch } from './vectorIndex.js';
2
+ export type { VectorDocument, SearchResult } from './vectorIndex.js';