codebase-context 1.2.2 → 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.
Files changed (96) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +144 -87
  3. package/dist/analyzers/angular/index.d.ts +1 -1
  4. package/dist/analyzers/angular/index.d.ts.map +1 -1
  5. package/dist/analyzers/angular/index.js +298 -309
  6. package/dist/analyzers/angular/index.js.map +1 -1
  7. package/dist/analyzers/generic/index.d.ts +1 -1
  8. package/dist/analyzers/generic/index.d.ts.map +1 -1
  9. package/dist/analyzers/generic/index.js +93 -47
  10. package/dist/analyzers/generic/index.js.map +1 -1
  11. package/dist/constants/codebase-context.d.ts +6 -0
  12. package/dist/constants/codebase-context.d.ts.map +1 -0
  13. package/dist/constants/codebase-context.js +8 -0
  14. package/dist/constants/codebase-context.js.map +1 -0
  15. package/dist/core/analyzer-registry.d.ts.map +1 -1
  16. package/dist/core/analyzer-registry.js +5 -7
  17. package/dist/core/analyzer-registry.js.map +1 -1
  18. package/dist/core/indexer.d.ts +9 -1
  19. package/dist/core/indexer.d.ts.map +1 -1
  20. package/dist/core/indexer.js +206 -139
  21. package/dist/core/indexer.js.map +1 -1
  22. package/dist/core/search.d.ts +1 -1
  23. package/dist/core/search.d.ts.map +1 -1
  24. package/dist/core/search.js +63 -59
  25. package/dist/core/search.js.map +1 -1
  26. package/dist/embeddings/openai.d.ts.map +1 -1
  27. package/dist/embeddings/openai.js +2 -2
  28. package/dist/embeddings/openai.js.map +1 -1
  29. package/dist/embeddings/transformers.d.ts +1 -1
  30. package/dist/embeddings/transformers.d.ts.map +1 -1
  31. package/dist/embeddings/transformers.js +19 -15
  32. package/dist/embeddings/transformers.js.map +1 -1
  33. package/dist/embeddings/types.d.ts +1 -1
  34. package/dist/embeddings/types.d.ts.map +1 -1
  35. package/dist/embeddings/types.js +3 -3
  36. package/dist/embeddings/types.js.map +1 -1
  37. package/dist/errors/index.d.ts +8 -0
  38. package/dist/errors/index.d.ts.map +1 -0
  39. package/dist/errors/index.js +11 -0
  40. package/dist/errors/index.js.map +1 -0
  41. package/dist/index.d.ts +6 -28
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +691 -335
  44. package/dist/index.js.map +1 -1
  45. package/dist/lib.d.ts +18 -18
  46. package/dist/lib.d.ts.map +1 -1
  47. package/dist/lib.js +23 -23
  48. package/dist/lib.js.map +1 -1
  49. package/dist/memory/store.d.ts +22 -0
  50. package/dist/memory/store.d.ts.map +1 -0
  51. package/dist/memory/store.js +97 -0
  52. package/dist/memory/store.js.map +1 -0
  53. package/dist/storage/lancedb.d.ts.map +1 -1
  54. package/dist/storage/lancedb.js +27 -31
  55. package/dist/storage/lancedb.js.map +1 -1
  56. package/dist/storage/types.d.ts.map +1 -1
  57. package/dist/storage/types.js +2 -1
  58. package/dist/storage/types.js.map +1 -1
  59. package/dist/types/index.d.ts +27 -0
  60. package/dist/types/index.d.ts.map +1 -1
  61. package/dist/types/index.js +1 -0
  62. package/dist/types/index.js.map +1 -1
  63. package/dist/utils/chunking.d.ts.map +1 -1
  64. package/dist/utils/chunking.js +10 -9
  65. package/dist/utils/chunking.js.map +1 -1
  66. package/dist/utils/dependency-detection.d.ts +18 -0
  67. package/dist/utils/dependency-detection.d.ts.map +1 -0
  68. package/dist/utils/dependency-detection.js +102 -0
  69. package/dist/utils/dependency-detection.js.map +1 -0
  70. package/dist/utils/git-dates.d.ts.map +1 -1
  71. package/dist/utils/git-dates.js +3 -3
  72. package/dist/utils/git-dates.js.map +1 -1
  73. package/dist/utils/language-detection.d.ts.map +1 -1
  74. package/dist/utils/language-detection.js +69 -17
  75. package/dist/utils/language-detection.js.map +1 -1
  76. package/dist/utils/usage-tracker.d.ts +2 -2
  77. package/dist/utils/usage-tracker.d.ts.map +1 -1
  78. package/dist/utils/usage-tracker.js +64 -32
  79. package/dist/utils/usage-tracker.js.map +1 -1
  80. package/dist/utils/workspace-detection.d.ts +32 -0
  81. package/dist/utils/workspace-detection.d.ts.map +1 -0
  82. package/dist/utils/workspace-detection.js +107 -0
  83. package/dist/utils/workspace-detection.js.map +1 -0
  84. package/package.json +114 -97
  85. package/dist/core/file-watcher.d.ts +0 -63
  86. package/dist/core/file-watcher.d.ts.map +0 -1
  87. package/dist/core/file-watcher.js +0 -210
  88. package/dist/core/file-watcher.js.map +0 -1
  89. package/dist/utils/logger.d.ts +0 -36
  90. package/dist/utils/logger.d.ts.map +0 -1
  91. package/dist/utils/logger.js +0 -111
  92. package/dist/utils/logger.js.map +0 -1
  93. package/dist/utils/pattern-detector.d.ts +0 -41
  94. package/dist/utils/pattern-detector.d.ts.map +0 -1
  95. package/dist/utils/pattern-detector.js +0 -101
  96. package/dist/utils/pattern-detector.js.map +0 -1
@@ -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
- import { promises as fs } from "fs";
6
- import path from "path";
7
- import { glob } from "glob";
8
- import ignore from "ignore";
9
- import { analyzerRegistry } from "./analyzer-registry.js";
10
- import { isCodeFile, isBinaryFile } from "../utils/language-detection.js";
11
- import { getEmbeddingProvider, } from "../embeddings/index.js";
12
- import { getStorageProvider, } from "../storage/index.js";
13
- import { LibraryUsageTracker, PatternDetector, ImportGraph, InternalFileGraph } from "../utils/usage-tracker.js";
14
- import { getFileCommitDates } from "../utils/git-dates.js";
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: "initializing",
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: ["**/*.{ts,tsx,js,jsx,html,css,scss,sass,less}"],
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: ["STYLE_GUIDE.md", "docs/style-guide.md", "ARCHITECTURE.md"],
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: "transformers",
70
- model: "Xenova/bge-base-en-v1.5",
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: "lancedb",
76
- path: "./codebase-index",
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("scanning", 0);
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("analyzing", 0);
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, "utf-8");
149
- const content = rawContent.replace(/\r\n/g, "\n");
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("\n").length;
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 = ((hasPattern('dependencyInjection', 'inject() function') ? 1 : 0) +
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: "analyzing",
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
- let chunksWithEmbeddings = [];
270
+ const chunksWithEmbeddings = [];
276
271
  if (!this.config.skipEmbedding) {
277
- this.updateProgress("embedding", 50);
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("\n");
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("embedding", embeddingProgress);
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("Skipping embedding generation (skipEmbedding=true)");
307
+ console.error('Skipping embedding generation (skipEmbedding=true)');
314
308
  }
315
309
  // Phase 4: Storing
316
- this.updateProgress("storing", 75);
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(this.rootPath, ".codebase-index");
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(this.rootPath, ".codebase-index.json");
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(this.rootPath, ".codebase-intelligence.json");
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, "tsconfig.json");
336
- const tsconfigContent = await fs.readFile(tsconfigPath, "utf-8");
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 (error) {
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("complete", 100);
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 = "error";
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, ".gitignore");
388
- const gitignoreContent = await fs.readFile(gitignorePath, "utf-8");
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 (error) {
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 (error) {
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
- // Try to use the most specific analyzer for metadata detection
440
- const primaryAnalyzer = analyzerRegistry.getAll()[0]; // Highest priority
441
- let metadata;
442
- if (primaryAnalyzer) {
443
- metadata = await primaryAnalyzer.detectCodebaseMetadata(this.rootPath);
444
- }
445
- else {
446
- // Fallback metadata
447
- metadata = {
448
- name: path.basename(this.rootPath),
449
- rootPath: this.rootPath,
450
- languages: [],
451
- dependencies: [],
452
- architecture: {
453
- type: "mixed",
454
- layers: {
455
- presentation: 0,
456
- business: 0,
457
- data: 0,
458
- state: 0,
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
- statistics: {
473
- totalFiles: 0,
474
- totalLines: 0,
475
- totalComponents: 0,
476
- componentsByType: {},
477
- componentsByLayer: {
478
- presentation: 0,
479
- business: 0,
480
- data: 0,
481
- state: 0,
482
- core: 0,
483
- shared: 0,
484
- feature: 0,
485
- infrastructure: 0,
486
- unknown: 0,
487
- },
488
- },
489
- customMetadata: {},
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, ".codebase-intelligence.json");
495
- const intelligenceContent = await fs.readFile(intelligencePath, "utf-8");
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 (error) {
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
- 'RxJS': /BehaviorSubject|ReplaySubject|Subject|Observable/,
521
- 'Signals': /\bsignal\s*[<(]/,
587
+ RxJS: /BehaviorSubject|ReplaySubject|Subject|Observable/,
588
+ Signals: /\bsignal\s*[<(]/
522
589
  },
523
590
  reactivity: {
524
- 'Effect': /\beffect\s*\(/,
525
- 'Computed': /\bcomputed\s*[<(]/,
591
+ Effect: /\beffect\s*\(/,
592
+ Computed: /\bcomputed\s*[<(]/
526
593
  },
527
594
  componentStyle: {
528
- 'Standalone': /standalone\s*:\s*true/,
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
  }