codebase-context 1.2.1 → 1.5.0
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.
- package/LICENSE +21 -21
- package/README.md +144 -87
- package/dist/analyzers/angular/index.d.ts +1 -1
- package/dist/analyzers/angular/index.d.ts.map +1 -1
- package/dist/analyzers/angular/index.js +298 -309
- package/dist/analyzers/angular/index.js.map +1 -1
- package/dist/analyzers/generic/index.d.ts +1 -1
- package/dist/analyzers/generic/index.d.ts.map +1 -1
- package/dist/analyzers/generic/index.js +93 -47
- package/dist/analyzers/generic/index.js.map +1 -1
- package/dist/constants/codebase-context.d.ts +6 -0
- package/dist/constants/codebase-context.d.ts.map +1 -0
- package/dist/constants/codebase-context.js +8 -0
- package/dist/constants/codebase-context.js.map +1 -0
- package/dist/core/analyzer-registry.d.ts.map +1 -1
- package/dist/core/analyzer-registry.js +5 -7
- package/dist/core/analyzer-registry.js.map +1 -1
- package/dist/core/indexer.d.ts +9 -1
- package/dist/core/indexer.d.ts.map +1 -1
- package/dist/core/indexer.js +206 -139
- package/dist/core/indexer.js.map +1 -1
- package/dist/core/search.d.ts +1 -1
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +63 -59
- package/dist/core/search.js.map +1 -1
- package/dist/embeddings/openai.d.ts.map +1 -1
- package/dist/embeddings/openai.js +2 -2
- package/dist/embeddings/openai.js.map +1 -1
- package/dist/embeddings/transformers.d.ts +1 -1
- package/dist/embeddings/transformers.d.ts.map +1 -1
- package/dist/embeddings/transformers.js +19 -15
- package/dist/embeddings/transformers.js.map +1 -1
- package/dist/embeddings/types.d.ts +1 -1
- package/dist/embeddings/types.d.ts.map +1 -1
- package/dist/embeddings/types.js +3 -3
- package/dist/embeddings/types.js.map +1 -1
- package/dist/errors/index.d.ts +8 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +11 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +6 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +691 -336
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +18 -18
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +23 -23
- package/dist/lib.js.map +1 -1
- package/dist/memory/store.d.ts +22 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +97 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/storage/lancedb.d.ts.map +1 -1
- package/dist/storage/lancedb.js +27 -31
- package/dist/storage/lancedb.js.map +1 -1
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js +2 -1
- package/dist/storage/types.js.map +1 -1
- package/dist/types/index.d.ts +27 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/chunking.d.ts.map +1 -1
- package/dist/utils/chunking.js +10 -9
- package/dist/utils/chunking.js.map +1 -1
- package/dist/utils/dependency-detection.d.ts +18 -0
- package/dist/utils/dependency-detection.d.ts.map +1 -0
- package/dist/utils/dependency-detection.js +102 -0
- package/dist/utils/dependency-detection.js.map +1 -0
- package/dist/utils/git-dates.d.ts.map +1 -1
- package/dist/utils/git-dates.js +3 -3
- package/dist/utils/git-dates.js.map +1 -1
- package/dist/utils/language-detection.d.ts.map +1 -1
- package/dist/utils/language-detection.js +69 -17
- package/dist/utils/language-detection.js.map +1 -1
- package/dist/utils/usage-tracker.d.ts +2 -2
- package/dist/utils/usage-tracker.d.ts.map +1 -1
- package/dist/utils/usage-tracker.js +64 -32
- package/dist/utils/usage-tracker.js.map +1 -1
- package/dist/utils/workspace-detection.d.ts +32 -0
- package/dist/utils/workspace-detection.d.ts.map +1 -0
- package/dist/utils/workspace-detection.js +107 -0
- package/dist/utils/workspace-detection.js.map +1 -0
- package/package.json +114 -97
- package/dist/core/file-watcher.d.ts +0 -63
- package/dist/core/file-watcher.d.ts.map +0 -1
- package/dist/core/file-watcher.js +0 -210
- package/dist/core/file-watcher.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -36
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -111
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/pattern-detector.d.ts +0 -41
- package/dist/utils/pattern-detector.d.ts.map +0 -1
- package/dist/utils/pattern-detector.js +0 -101
- package/dist/utils/pattern-detector.js.map +0 -1
package/dist/core/indexer.js
CHANGED
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
* Core Indexer - Orchestrates codebase indexing
|
|
3
3
|
* Scans files, delegates to analyzers, creates embeddings, stores in vector DB
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
6
|
+
import { promises as fs } from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { glob } from 'glob';
|
|
9
|
+
import ignore from 'ignore';
|
|
10
|
+
import { analyzerRegistry } from './analyzer-registry.js';
|
|
11
|
+
import { isCodeFile, isBinaryFile } from '../utils/language-detection.js';
|
|
12
|
+
import { getEmbeddingProvider } from '../embeddings/index.js';
|
|
13
|
+
import { getStorageProvider } from '../storage/index.js';
|
|
14
|
+
import { LibraryUsageTracker, PatternDetector, ImportGraph, InternalFileGraph } from '../utils/usage-tracker.js';
|
|
15
|
+
import { getFileCommitDates } from '../utils/git-dates.js';
|
|
16
|
+
import { CODEBASE_CONTEXT_DIRNAME, INTELLIGENCE_FILENAME, KEYWORD_INDEX_FILENAME, VECTOR_DB_DIRNAME } from '../constants/codebase-context.js';
|
|
15
17
|
export class CodebaseIndexer {
|
|
16
18
|
rootPath;
|
|
17
19
|
config;
|
|
@@ -22,13 +24,13 @@ export class CodebaseIndexer {
|
|
|
22
24
|
this.config = this.mergeConfig(options.config);
|
|
23
25
|
this.onProgressCallback = options.onProgress;
|
|
24
26
|
this.progress = {
|
|
25
|
-
phase:
|
|
27
|
+
phase: 'initializing',
|
|
26
28
|
percentage: 0,
|
|
27
29
|
filesProcessed: 0,
|
|
28
30
|
totalFiles: 0,
|
|
29
31
|
chunksCreated: 0,
|
|
30
32
|
errors: [],
|
|
31
|
-
startedAt: new Date()
|
|
33
|
+
startedAt: new Date()
|
|
32
34
|
};
|
|
33
35
|
}
|
|
34
36
|
mergeConfig(userConfig) {
|
|
@@ -37,44 +39,38 @@ export class CodebaseIndexer {
|
|
|
37
39
|
angular: { enabled: true, priority: 100 },
|
|
38
40
|
react: { enabled: false, priority: 90 },
|
|
39
41
|
vue: { enabled: false, priority: 90 },
|
|
40
|
-
generic: { enabled: true, priority: 10 }
|
|
42
|
+
generic: { enabled: true, priority: 10 }
|
|
41
43
|
},
|
|
42
|
-
include: [
|
|
43
|
-
exclude: [
|
|
44
|
-
"node_modules/**",
|
|
45
|
-
"dist/**",
|
|
46
|
-
"build/**",
|
|
47
|
-
".git/**",
|
|
48
|
-
"coverage/**",
|
|
49
|
-
],
|
|
44
|
+
include: ['**/*.{ts,tsx,js,jsx,html,css,scss,sass,less}'],
|
|
45
|
+
exclude: ['node_modules/**', 'dist/**', 'build/**', '.git/**', 'coverage/**'],
|
|
50
46
|
respectGitignore: true,
|
|
51
47
|
parsing: {
|
|
52
48
|
maxFileSize: 1048576, // 1MB
|
|
53
49
|
chunkSize: 100,
|
|
54
50
|
chunkOverlap: 10,
|
|
55
51
|
parseTests: true,
|
|
56
|
-
parseNodeModules: false
|
|
52
|
+
parseNodeModules: false
|
|
57
53
|
},
|
|
58
54
|
styleGuides: {
|
|
59
55
|
autoDetect: true,
|
|
60
|
-
paths: [
|
|
61
|
-
parseMarkdown: true
|
|
56
|
+
paths: ['STYLE_GUIDE.md', 'docs/style-guide.md', 'ARCHITECTURE.md'],
|
|
57
|
+
parseMarkdown: true
|
|
62
58
|
},
|
|
63
59
|
documentation: {
|
|
64
60
|
autoDetect: true,
|
|
65
61
|
includeReadmes: true,
|
|
66
|
-
includeChangelogs: false
|
|
62
|
+
includeChangelogs: false
|
|
67
63
|
},
|
|
68
64
|
embedding: {
|
|
69
|
-
provider:
|
|
70
|
-
model:
|
|
71
|
-
batchSize: 100
|
|
65
|
+
provider: 'transformers',
|
|
66
|
+
model: 'Xenova/bge-small-en-v1.5',
|
|
67
|
+
batchSize: 100
|
|
72
68
|
},
|
|
73
69
|
skipEmbedding: false,
|
|
74
70
|
storage: {
|
|
75
|
-
provider:
|
|
76
|
-
path:
|
|
77
|
-
}
|
|
71
|
+
provider: 'lancedb',
|
|
72
|
+
path: './codebase-index'
|
|
73
|
+
}
|
|
78
74
|
};
|
|
79
75
|
return {
|
|
80
76
|
...defaultConfig,
|
|
@@ -84,10 +80,10 @@ export class CodebaseIndexer {
|
|
|
84
80
|
styleGuides: { ...defaultConfig.styleGuides, ...userConfig?.styleGuides },
|
|
85
81
|
documentation: {
|
|
86
82
|
...defaultConfig.documentation,
|
|
87
|
-
...userConfig?.documentation
|
|
83
|
+
...userConfig?.documentation
|
|
88
84
|
},
|
|
89
85
|
embedding: { ...defaultConfig.embedding, ...userConfig?.embedding },
|
|
90
|
-
storage: { ...defaultConfig.storage, ...userConfig?.storage }
|
|
86
|
+
storage: { ...defaultConfig.storage, ...userConfig?.storage }
|
|
91
87
|
};
|
|
92
88
|
}
|
|
93
89
|
async index() {
|
|
@@ -110,14 +106,14 @@ export class CodebaseIndexer {
|
|
|
110
106
|
shared: 0,
|
|
111
107
|
feature: 0,
|
|
112
108
|
infrastructure: 0,
|
|
113
|
-
unknown: 0
|
|
109
|
+
unknown: 0
|
|
114
110
|
},
|
|
115
111
|
errors: [],
|
|
116
|
-
startedAt: new Date()
|
|
112
|
+
startedAt: new Date()
|
|
117
113
|
};
|
|
118
114
|
try {
|
|
119
115
|
// Phase 1: Scanning
|
|
120
|
-
this.updateProgress(
|
|
116
|
+
this.updateProgress('scanning', 0);
|
|
121
117
|
let files = await this.scanFiles();
|
|
122
118
|
// Memory safety: limit total files to prevent heap exhaustion
|
|
123
119
|
const MAX_FILES = 10000;
|
|
@@ -130,7 +126,7 @@ export class CodebaseIndexer {
|
|
|
130
126
|
this.progress.totalFiles = files.length;
|
|
131
127
|
console.error(`Found ${files.length} files to index`);
|
|
132
128
|
// Phase 2: Analyzing & Parsing
|
|
133
|
-
this.updateProgress(
|
|
129
|
+
this.updateProgress('analyzing', 0);
|
|
134
130
|
const allChunks = [];
|
|
135
131
|
const libraryTracker = new LibraryUsageTracker();
|
|
136
132
|
const patternDetector = new PatternDetector();
|
|
@@ -145,13 +141,13 @@ export class CodebaseIndexer {
|
|
|
145
141
|
this.progress.percentage = Math.round(((i + 1) / files.length) * 100);
|
|
146
142
|
try {
|
|
147
143
|
// Normalize line endings to \n for consistent cross-platform output
|
|
148
|
-
const rawContent = await fs.readFile(file,
|
|
149
|
-
const content = rawContent.replace(/\r\n/g,
|
|
144
|
+
const rawContent = await fs.readFile(file, 'utf-8');
|
|
145
|
+
const content = rawContent.replace(/\r\n/g, '\n');
|
|
150
146
|
const result = await analyzerRegistry.analyzeFile(file, content);
|
|
151
147
|
if (result) {
|
|
152
148
|
allChunks.push(...result.chunks);
|
|
153
149
|
stats.indexedFiles++;
|
|
154
|
-
stats.totalLines += content.split(
|
|
150
|
+
stats.totalLines += content.split('\n').length;
|
|
155
151
|
// Track library usage AND import graph from imports
|
|
156
152
|
for (const imp of result.imports) {
|
|
157
153
|
libraryTracker.track(imp.source, file);
|
|
@@ -176,9 +172,9 @@ export class CodebaseIndexer {
|
|
|
176
172
|
}
|
|
177
173
|
// Track exports for unused export detection
|
|
178
174
|
if (result.exports && result.exports.length > 0) {
|
|
179
|
-
const fileExports = result.exports.map(exp => ({
|
|
175
|
+
const fileExports = result.exports.map((exp) => ({
|
|
180
176
|
name: exp.name,
|
|
181
|
-
type: exp.isDefault ? 'default' : exp.type || 'other'
|
|
177
|
+
type: exp.isDefault ? 'default' : exp.type || 'other'
|
|
182
178
|
}));
|
|
183
179
|
internalFileGraph.trackExports(file, fileExports);
|
|
184
180
|
}
|
|
@@ -214,12 +210,12 @@ export class CodebaseIndexer {
|
|
|
214
210
|
// Track file for Golden File scoring (framework-agnostic based on patterns)
|
|
215
211
|
const detectedPatterns = result.metadata?.detectedPatterns || [];
|
|
216
212
|
const hasPattern = (category, name) => detectedPatterns.some((p) => p.category === category && p.name === name);
|
|
217
|
-
const patternScore = (
|
|
213
|
+
const patternScore = (hasPattern('dependencyInjection', 'inject() function') ? 1 : 0) +
|
|
218
214
|
(hasPattern('stateManagement', 'Signals') ? 1 : 0) +
|
|
219
215
|
(hasPattern('reactivity', 'Computed') ? 1 : 0) +
|
|
220
216
|
(hasPattern('reactivity', 'Effect') ? 1 : 0) +
|
|
221
217
|
(hasPattern('componentStyle', 'Standalone') ? 1 : 0) +
|
|
222
|
-
(hasPattern('componentInputs', 'Signal-based inputs') ? 1 : 0)
|
|
218
|
+
(hasPattern('componentInputs', 'Signal-based inputs') ? 1 : 0);
|
|
223
219
|
if (patternScore >= 3) {
|
|
224
220
|
patternDetector.trackGoldenFile(relPath, patternScore, {
|
|
225
221
|
inject: hasPattern('dependencyInjection', 'inject() function'),
|
|
@@ -227,7 +223,7 @@ export class CodebaseIndexer {
|
|
|
227
223
|
computed: hasPattern('reactivity', 'Computed'),
|
|
228
224
|
effect: hasPattern('reactivity', 'Effect'),
|
|
229
225
|
standalone: hasPattern('componentStyle', 'Standalone'),
|
|
230
|
-
signalInputs: hasPattern('componentInputs', 'Signal-based inputs')
|
|
226
|
+
signalInputs: hasPattern('componentInputs', 'Signal-based inputs')
|
|
231
227
|
});
|
|
232
228
|
}
|
|
233
229
|
// Update component statistics
|
|
@@ -250,8 +246,8 @@ export class CodebaseIndexer {
|
|
|
250
246
|
stats.errors.push({
|
|
251
247
|
filePath: file,
|
|
252
248
|
error: error instanceof Error ? error.message : String(error),
|
|
253
|
-
phase:
|
|
254
|
-
timestamp: new Date()
|
|
249
|
+
phase: 'analyzing',
|
|
250
|
+
timestamp: new Date()
|
|
255
251
|
});
|
|
256
252
|
}
|
|
257
253
|
if (this.onProgressCallback) {
|
|
@@ -261,8 +257,7 @@ export class CodebaseIndexer {
|
|
|
261
257
|
stats.totalChunks = allChunks.length;
|
|
262
258
|
stats.avgChunkSize =
|
|
263
259
|
allChunks.length > 0
|
|
264
|
-
? Math.round(allChunks.reduce((sum, c) => sum + c.content.length, 0) /
|
|
265
|
-
allChunks.length)
|
|
260
|
+
? Math.round(allChunks.reduce((sum, c) => sum + c.content.length, 0) / allChunks.length)
|
|
266
261
|
: 0;
|
|
267
262
|
// Memory safety: limit chunks to prevent embedding memory issues
|
|
268
263
|
const MAX_CHUNKS = 5000;
|
|
@@ -272,9 +267,9 @@ export class CodebaseIndexer {
|
|
|
272
267
|
chunksToEmbed = allChunks.slice(0, MAX_CHUNKS);
|
|
273
268
|
}
|
|
274
269
|
// Phase 3: Embedding
|
|
275
|
-
|
|
270
|
+
const chunksWithEmbeddings = [];
|
|
276
271
|
if (!this.config.skipEmbedding) {
|
|
277
|
-
this.updateProgress(
|
|
272
|
+
this.updateProgress('embedding', 50);
|
|
278
273
|
console.error(`Creating embeddings for ${chunksToEmbed.length} chunks...`);
|
|
279
274
|
// Initialize embedding provider
|
|
280
275
|
const embeddingProvider = await getEmbeddingProvider(this.config.embedding);
|
|
@@ -291,56 +286,57 @@ export class CodebaseIndexer {
|
|
|
291
286
|
if (chunk.componentType) {
|
|
292
287
|
parts.unshift(`Type: ${chunk.componentType}`);
|
|
293
288
|
}
|
|
294
|
-
return parts.join(
|
|
289
|
+
return parts.join('\n');
|
|
295
290
|
});
|
|
296
291
|
const embeddings = await embeddingProvider.embedBatch(texts);
|
|
297
292
|
for (let j = 0; j < batch.length; j++) {
|
|
298
293
|
chunksWithEmbeddings.push({
|
|
299
294
|
...batch[j],
|
|
300
|
-
embedding: embeddings[j]
|
|
295
|
+
embedding: embeddings[j]
|
|
301
296
|
});
|
|
302
297
|
}
|
|
303
298
|
// Update progress
|
|
304
299
|
const embeddingProgress = 50 + Math.round((i / chunksToEmbed.length) * 25);
|
|
305
|
-
this.updateProgress(
|
|
306
|
-
if ((i + batchSize) % 100 === 0 ||
|
|
307
|
-
i + batchSize >= chunksToEmbed.length) {
|
|
300
|
+
this.updateProgress('embedding', embeddingProgress);
|
|
301
|
+
if ((i + batchSize) % 100 === 0 || i + batchSize >= chunksToEmbed.length) {
|
|
308
302
|
console.error(`Embedded ${Math.min(i + batchSize, chunksToEmbed.length)}/${chunksToEmbed.length} chunks`);
|
|
309
303
|
}
|
|
310
304
|
}
|
|
311
305
|
}
|
|
312
306
|
else {
|
|
313
|
-
console.error(
|
|
307
|
+
console.error('Skipping embedding generation (skipEmbedding=true)');
|
|
314
308
|
}
|
|
315
309
|
// Phase 4: Storing
|
|
316
|
-
this.updateProgress(
|
|
310
|
+
this.updateProgress('storing', 75);
|
|
311
|
+
const contextDir = path.join(this.rootPath, CODEBASE_CONTEXT_DIRNAME);
|
|
312
|
+
await fs.mkdir(contextDir, { recursive: true });
|
|
317
313
|
if (!this.config.skipEmbedding) {
|
|
318
314
|
console.error(`Storing ${chunksToEmbed.length} chunks...`);
|
|
319
315
|
// Store in LanceDB for vector search
|
|
320
|
-
const storagePath = path.join(
|
|
316
|
+
const storagePath = path.join(contextDir, VECTOR_DB_DIRNAME);
|
|
321
317
|
const storageProvider = await getStorageProvider({ path: storagePath });
|
|
322
318
|
await storageProvider.clear(); // Clear existing index
|
|
323
319
|
await storageProvider.store(chunksWithEmbeddings);
|
|
324
320
|
}
|
|
325
321
|
// Also save JSON for keyword search (Fuse.js) - use chunksToEmbed for consistency
|
|
326
|
-
const indexPath = path.join(
|
|
322
|
+
const indexPath = path.join(contextDir, KEYWORD_INDEX_FILENAME);
|
|
327
323
|
// Write without pretty-printing to save memory
|
|
328
324
|
await fs.writeFile(indexPath, JSON.stringify(chunksToEmbed));
|
|
329
325
|
// Save library usage and pattern stats
|
|
330
|
-
const intelligencePath = path.join(
|
|
326
|
+
const intelligencePath = path.join(contextDir, INTELLIGENCE_FILENAME);
|
|
331
327
|
const libraryStats = libraryTracker.getStats();
|
|
332
328
|
// Extract tsconfig paths for AI to understand import aliases
|
|
333
329
|
let tsconfigPaths;
|
|
334
330
|
try {
|
|
335
|
-
const tsconfigPath = path.join(this.rootPath,
|
|
336
|
-
const tsconfigContent = await fs.readFile(tsconfigPath,
|
|
331
|
+
const tsconfigPath = path.join(this.rootPath, 'tsconfig.json');
|
|
332
|
+
const tsconfigContent = await fs.readFile(tsconfigPath, 'utf-8');
|
|
337
333
|
const tsconfig = JSON.parse(tsconfigContent);
|
|
338
334
|
if (tsconfig.compilerOptions?.paths) {
|
|
339
335
|
tsconfigPaths = tsconfig.compilerOptions.paths;
|
|
340
336
|
console.error(`Found ${Object.keys(tsconfigPaths).length} path aliases in tsconfig.json`);
|
|
341
337
|
}
|
|
342
338
|
}
|
|
343
|
-
catch (
|
|
339
|
+
catch (_error) {
|
|
344
340
|
// No tsconfig.json or no paths defined
|
|
345
341
|
}
|
|
346
342
|
const intelligence = {
|
|
@@ -352,15 +348,15 @@ export class CodebaseIndexer {
|
|
|
352
348
|
tsconfigPaths,
|
|
353
349
|
importGraph: {
|
|
354
350
|
usages: importGraph.getAllUsages(),
|
|
355
|
-
topUsed: importGraph.getTopUsed(30)
|
|
351
|
+
topUsed: importGraph.getTopUsed(30)
|
|
356
352
|
},
|
|
357
353
|
// Internal file graph for circular dependency and unused export detection
|
|
358
354
|
internalFileGraph: internalFileGraph.toJSON(),
|
|
359
|
-
generatedAt: new Date().toISOString()
|
|
355
|
+
generatedAt: new Date().toISOString()
|
|
360
356
|
};
|
|
361
357
|
await fs.writeFile(intelligencePath, JSON.stringify(intelligence, null, 2));
|
|
362
358
|
// Phase 5: Complete
|
|
363
|
-
this.updateProgress(
|
|
359
|
+
this.updateProgress('complete', 100);
|
|
364
360
|
stats.duration = Date.now() - startTime;
|
|
365
361
|
stats.completedAt = new Date();
|
|
366
362
|
console.error(`Indexing complete in ${stats.duration}ms`);
|
|
@@ -368,12 +364,12 @@ export class CodebaseIndexer {
|
|
|
368
364
|
return stats;
|
|
369
365
|
}
|
|
370
366
|
catch (error) {
|
|
371
|
-
this.progress.phase =
|
|
367
|
+
this.progress.phase = 'error';
|
|
372
368
|
stats.errors.push({
|
|
373
369
|
filePath: this.rootPath,
|
|
374
370
|
error: error instanceof Error ? error.message : String(error),
|
|
375
371
|
phase: this.progress.phase,
|
|
376
|
-
timestamp: new Date()
|
|
372
|
+
timestamp: new Date()
|
|
377
373
|
});
|
|
378
374
|
throw error;
|
|
379
375
|
}
|
|
@@ -384,23 +380,23 @@ export class CodebaseIndexer {
|
|
|
384
380
|
let ig = null;
|
|
385
381
|
if (this.config.respectGitignore) {
|
|
386
382
|
try {
|
|
387
|
-
const gitignorePath = path.join(this.rootPath,
|
|
388
|
-
const gitignoreContent = await fs.readFile(gitignorePath,
|
|
383
|
+
const gitignorePath = path.join(this.rootPath, '.gitignore');
|
|
384
|
+
const gitignoreContent = await fs.readFile(gitignorePath, 'utf-8');
|
|
389
385
|
ig = ignore.default().add(gitignoreContent);
|
|
390
386
|
}
|
|
391
|
-
catch (
|
|
387
|
+
catch (_error) {
|
|
392
388
|
// No .gitignore or couldn't read it
|
|
393
389
|
}
|
|
394
390
|
}
|
|
395
391
|
// Scan with glob
|
|
396
|
-
const includePatterns = this.config.include || [
|
|
392
|
+
const includePatterns = this.config.include || ['**/*'];
|
|
397
393
|
const excludePatterns = this.config.exclude || [];
|
|
398
394
|
for (const pattern of includePatterns) {
|
|
399
395
|
const matches = await glob(pattern, {
|
|
400
396
|
cwd: this.rootPath,
|
|
401
397
|
absolute: true,
|
|
402
398
|
ignore: excludePatterns,
|
|
403
|
-
nodir: true
|
|
399
|
+
nodir: true
|
|
404
400
|
});
|
|
405
401
|
for (const file of matches) {
|
|
406
402
|
const relativePath = path.relative(this.rootPath, file);
|
|
@@ -420,7 +416,7 @@ export class CodebaseIndexer {
|
|
|
420
416
|
continue;
|
|
421
417
|
}
|
|
422
418
|
}
|
|
423
|
-
catch (
|
|
419
|
+
catch (_error) {
|
|
424
420
|
continue;
|
|
425
421
|
}
|
|
426
422
|
files.push(file);
|
|
@@ -436,76 +432,147 @@ export class CodebaseIndexer {
|
|
|
436
432
|
}
|
|
437
433
|
}
|
|
438
434
|
async detectMetadata() {
|
|
439
|
-
//
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
core: 0,
|
|
460
|
-
shared: 0,
|
|
461
|
-
feature: 0,
|
|
462
|
-
infrastructure: 0,
|
|
463
|
-
unknown: 0,
|
|
464
|
-
},
|
|
465
|
-
patterns: [],
|
|
466
|
-
},
|
|
467
|
-
styleGuides: [],
|
|
468
|
-
documentation: [],
|
|
469
|
-
projectStructure: {
|
|
470
|
-
type: "single-app",
|
|
435
|
+
// Get all registered analyzers (sorted by priority, highest first)
|
|
436
|
+
const analyzers = analyzerRegistry.getAll();
|
|
437
|
+
// Start with base metadata template
|
|
438
|
+
let metadata = {
|
|
439
|
+
name: path.basename(this.rootPath),
|
|
440
|
+
rootPath: this.rootPath,
|
|
441
|
+
languages: [],
|
|
442
|
+
dependencies: [],
|
|
443
|
+
architecture: {
|
|
444
|
+
type: 'mixed',
|
|
445
|
+
layers: {
|
|
446
|
+
presentation: 0,
|
|
447
|
+
business: 0,
|
|
448
|
+
data: 0,
|
|
449
|
+
state: 0,
|
|
450
|
+
core: 0,
|
|
451
|
+
shared: 0,
|
|
452
|
+
feature: 0,
|
|
453
|
+
infrastructure: 0,
|
|
454
|
+
unknown: 0
|
|
471
455
|
},
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
456
|
+
patterns: []
|
|
457
|
+
},
|
|
458
|
+
styleGuides: [],
|
|
459
|
+
documentation: [],
|
|
460
|
+
projectStructure: {
|
|
461
|
+
type: 'single-app'
|
|
462
|
+
},
|
|
463
|
+
statistics: {
|
|
464
|
+
totalFiles: 0,
|
|
465
|
+
totalLines: 0,
|
|
466
|
+
totalComponents: 0,
|
|
467
|
+
componentsByType: {},
|
|
468
|
+
componentsByLayer: {
|
|
469
|
+
presentation: 0,
|
|
470
|
+
business: 0,
|
|
471
|
+
data: 0,
|
|
472
|
+
state: 0,
|
|
473
|
+
core: 0,
|
|
474
|
+
shared: 0,
|
|
475
|
+
feature: 0,
|
|
476
|
+
infrastructure: 0,
|
|
477
|
+
unknown: 0
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
customMetadata: {}
|
|
481
|
+
};
|
|
482
|
+
// Loop through all analyzers (highest priority first) and merge their metadata
|
|
483
|
+
// Higher priority analyzers' values win on conflicts
|
|
484
|
+
for (const analyzer of analyzers) {
|
|
485
|
+
try {
|
|
486
|
+
const analyzerMeta = await analyzer.detectCodebaseMetadata(this.rootPath);
|
|
487
|
+
metadata = this.mergeMetadata(metadata, analyzerMeta);
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
// Analyzer failed, continue with next
|
|
491
|
+
console.warn(`Analyzer ${analyzer.name} failed to detect metadata:`, error);
|
|
492
|
+
}
|
|
491
493
|
}
|
|
492
494
|
// Load intelligence data if available
|
|
493
495
|
try {
|
|
494
|
-
const intelligencePath = path.join(this.rootPath,
|
|
495
|
-
const intelligenceContent = await fs.readFile(intelligencePath,
|
|
496
|
+
const intelligencePath = path.join(this.rootPath, CODEBASE_CONTEXT_DIRNAME, INTELLIGENCE_FILENAME);
|
|
497
|
+
const intelligenceContent = await fs.readFile(intelligencePath, 'utf-8');
|
|
496
498
|
const intelligence = JSON.parse(intelligenceContent);
|
|
497
499
|
metadata.customMetadata = {
|
|
498
500
|
...metadata.customMetadata,
|
|
499
501
|
libraryUsage: intelligence.libraryUsage,
|
|
500
502
|
patterns: intelligence.patterns,
|
|
501
|
-
intelligenceGeneratedAt: intelligence.generatedAt
|
|
503
|
+
intelligenceGeneratedAt: intelligence.generatedAt
|
|
502
504
|
};
|
|
503
505
|
}
|
|
504
|
-
catch (
|
|
506
|
+
catch (_error) {
|
|
505
507
|
// Intelligence file doesn't exist yet (indexing not run)
|
|
506
508
|
}
|
|
507
509
|
return metadata;
|
|
508
510
|
}
|
|
511
|
+
/**
|
|
512
|
+
* Merge two CodebaseMetadata objects.
|
|
513
|
+
* The 'incoming' metadata takes precedence for non-empty values.
|
|
514
|
+
*/
|
|
515
|
+
mergeMetadata(base, incoming) {
|
|
516
|
+
return {
|
|
517
|
+
name: incoming.name || base.name,
|
|
518
|
+
rootPath: incoming.rootPath || base.rootPath,
|
|
519
|
+
languages: [...new Set([...base.languages, ...incoming.languages])], // Merge and deduplicate
|
|
520
|
+
dependencies: this.mergeDependencies(base.dependencies, incoming.dependencies),
|
|
521
|
+
framework: incoming.framework || base.framework, // Framework from higher priority analyzer wins
|
|
522
|
+
architecture: {
|
|
523
|
+
type: incoming.architecture?.type || base.architecture.type,
|
|
524
|
+
layers: this.mergeLayers(base.architecture.layers, incoming.architecture?.layers),
|
|
525
|
+
patterns: [
|
|
526
|
+
...new Set([
|
|
527
|
+
...(base.architecture.patterns || []),
|
|
528
|
+
...(incoming.architecture?.patterns || [])
|
|
529
|
+
])
|
|
530
|
+
] // Merge and deduplicate
|
|
531
|
+
},
|
|
532
|
+
styleGuides: [...new Set([...base.styleGuides, ...incoming.styleGuides])], // Merge and deduplicate
|
|
533
|
+
documentation: [...new Set([...base.documentation, ...incoming.documentation])], // Merge and deduplicate
|
|
534
|
+
projectStructure: incoming.projectStructure?.type !== 'single-app'
|
|
535
|
+
? incoming.projectStructure
|
|
536
|
+
: base.projectStructure,
|
|
537
|
+
statistics: this.mergeStatistics(base.statistics, incoming.statistics),
|
|
538
|
+
customMetadata: { ...base.customMetadata, ...incoming.customMetadata }
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
mergeDependencies(base, incoming) {
|
|
542
|
+
const seen = new Set(base.map((d) => d.name));
|
|
543
|
+
const result = [...base];
|
|
544
|
+
for (const dep of incoming) {
|
|
545
|
+
if (!seen.has(dep.name)) {
|
|
546
|
+
result.push(dep);
|
|
547
|
+
seen.add(dep.name);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return result;
|
|
551
|
+
}
|
|
552
|
+
mergeLayers(base, incoming) {
|
|
553
|
+
if (!incoming)
|
|
554
|
+
return base;
|
|
555
|
+
return {
|
|
556
|
+
presentation: Math.max(base.presentation || 0, incoming.presentation || 0),
|
|
557
|
+
business: Math.max(base.business || 0, incoming.business || 0),
|
|
558
|
+
data: Math.max(base.data || 0, incoming.data || 0),
|
|
559
|
+
state: Math.max(base.state || 0, incoming.state || 0),
|
|
560
|
+
core: Math.max(base.core || 0, incoming.core || 0),
|
|
561
|
+
shared: Math.max(base.shared || 0, incoming.shared || 0),
|
|
562
|
+
feature: Math.max(base.feature || 0, incoming.feature || 0),
|
|
563
|
+
infrastructure: Math.max(base.infrastructure || 0, incoming.infrastructure || 0),
|
|
564
|
+
unknown: Math.max(base.unknown || 0, incoming.unknown || 0)
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
mergeStatistics(base, incoming) {
|
|
568
|
+
return {
|
|
569
|
+
totalFiles: Math.max(base.totalFiles || 0, incoming.totalFiles || 0),
|
|
570
|
+
totalLines: Math.max(base.totalLines || 0, incoming.totalLines || 0),
|
|
571
|
+
totalComponents: Math.max(base.totalComponents || 0, incoming.totalComponents || 0),
|
|
572
|
+
componentsByType: { ...base.componentsByType, ...incoming.componentsByType },
|
|
573
|
+
componentsByLayer: this.mergeLayers(base.componentsByLayer, incoming.componentsByLayer)
|
|
574
|
+
};
|
|
575
|
+
}
|
|
509
576
|
/**
|
|
510
577
|
* Get regex pattern for extracting code snippets based on pattern category and name
|
|
511
578
|
* This maps abstract pattern names to actual code patterns
|
|
@@ -514,24 +581,24 @@ export class CodebaseIndexer {
|
|
|
514
581
|
const patterns = {
|
|
515
582
|
dependencyInjection: {
|
|
516
583
|
'inject() function': /\binject\s*[<(]/,
|
|
517
|
-
'Constructor injection': /constructor\s*\(
|
|
584
|
+
'Constructor injection': /constructor\s*\(/
|
|
518
585
|
},
|
|
519
586
|
stateManagement: {
|
|
520
|
-
|
|
521
|
-
|
|
587
|
+
RxJS: /BehaviorSubject|ReplaySubject|Subject|Observable/,
|
|
588
|
+
Signals: /\bsignal\s*[<(]/
|
|
522
589
|
},
|
|
523
590
|
reactivity: {
|
|
524
|
-
|
|
525
|
-
|
|
591
|
+
Effect: /\beffect\s*\(/,
|
|
592
|
+
Computed: /\bcomputed\s*[<(]/
|
|
526
593
|
},
|
|
527
594
|
componentStyle: {
|
|
528
|
-
|
|
529
|
-
'NgModule-based': /@(?:Component|Directive|Pipe)\s*\(
|
|
595
|
+
Standalone: /standalone\s*:\s*true/,
|
|
596
|
+
'NgModule-based': /@(?:Component|Directive|Pipe)\s*\(/
|
|
530
597
|
},
|
|
531
598
|
componentInputs: {
|
|
532
599
|
'Signal-based inputs': /\binput\s*[<(]/,
|
|
533
|
-
'Decorator-based @Input': /@Input\(\)
|
|
534
|
-
}
|
|
600
|
+
'Decorator-based @Input': /@Input\(\)/
|
|
601
|
+
}
|
|
535
602
|
};
|
|
536
603
|
return patterns[category]?.[name] || null;
|
|
537
604
|
}
|