codebase-context 1.0.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 (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -0
  3. package/dist/analyzers/angular/index.d.ts +44 -0
  4. package/dist/analyzers/angular/index.d.ts.map +1 -0
  5. package/dist/analyzers/angular/index.js +922 -0
  6. package/dist/analyzers/angular/index.js.map +1 -0
  7. package/dist/analyzers/generic/index.d.ts +23 -0
  8. package/dist/analyzers/generic/index.d.ts.map +1 -0
  9. package/dist/analyzers/generic/index.js +354 -0
  10. package/dist/analyzers/generic/index.js.map +1 -0
  11. package/dist/core/analyzer-registry.d.ts +36 -0
  12. package/dist/core/analyzer-registry.d.ts.map +1 -0
  13. package/dist/core/analyzer-registry.js +78 -0
  14. package/dist/core/analyzer-registry.js.map +1 -0
  15. package/dist/core/file-watcher.d.ts +63 -0
  16. package/dist/core/file-watcher.d.ts.map +1 -0
  17. package/dist/core/file-watcher.js +210 -0
  18. package/dist/core/file-watcher.js.map +1 -0
  19. package/dist/core/indexer.d.ts +29 -0
  20. package/dist/core/indexer.d.ts.map +1 -0
  21. package/dist/core/indexer.js +507 -0
  22. package/dist/core/indexer.js.map +1 -0
  23. package/dist/core/search.d.ts +31 -0
  24. package/dist/core/search.d.ts.map +1 -0
  25. package/dist/core/search.js +307 -0
  26. package/dist/core/search.js.map +1 -0
  27. package/dist/embeddings/index.d.ts +5 -0
  28. package/dist/embeddings/index.d.ts.map +1 -0
  29. package/dist/embeddings/index.js +33 -0
  30. package/dist/embeddings/index.js.map +1 -0
  31. package/dist/embeddings/openai.d.ts +19 -0
  32. package/dist/embeddings/openai.d.ts.map +1 -0
  33. package/dist/embeddings/openai.js +59 -0
  34. package/dist/embeddings/openai.js.map +1 -0
  35. package/dist/embeddings/transformers.d.ts +17 -0
  36. package/dist/embeddings/transformers.d.ts.map +1 -0
  37. package/dist/embeddings/transformers.js +83 -0
  38. package/dist/embeddings/transformers.js.map +1 -0
  39. package/dist/embeddings/types.d.ts +20 -0
  40. package/dist/embeddings/types.d.ts.map +1 -0
  41. package/dist/embeddings/types.js +9 -0
  42. package/dist/embeddings/types.js.map +1 -0
  43. package/dist/index.d.ts +41 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +790 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/lib.d.ts +58 -0
  48. package/dist/lib.d.ts.map +1 -0
  49. package/dist/lib.js +81 -0
  50. package/dist/lib.js.map +1 -0
  51. package/dist/storage/index.d.ts +12 -0
  52. package/dist/storage/index.d.ts.map +1 -0
  53. package/dist/storage/index.js +18 -0
  54. package/dist/storage/index.js.map +1 -0
  55. package/dist/storage/lancedb.d.ts +24 -0
  56. package/dist/storage/lancedb.d.ts.map +1 -0
  57. package/dist/storage/lancedb.js +197 -0
  58. package/dist/storage/lancedb.js.map +1 -0
  59. package/dist/storage/types.d.ts +45 -0
  60. package/dist/storage/types.d.ts.map +1 -0
  61. package/dist/storage/types.js +8 -0
  62. package/dist/storage/types.js.map +1 -0
  63. package/dist/types/index.d.ts +367 -0
  64. package/dist/types/index.d.ts.map +1 -0
  65. package/dist/types/index.js +6 -0
  66. package/dist/types/index.js.map +1 -0
  67. package/dist/utils/chunking.d.ts +23 -0
  68. package/dist/utils/chunking.d.ts.map +1 -0
  69. package/dist/utils/chunking.js +226 -0
  70. package/dist/utils/chunking.js.map +1 -0
  71. package/dist/utils/language-detection.d.ts +29 -0
  72. package/dist/utils/language-detection.d.ts.map +1 -0
  73. package/dist/utils/language-detection.js +127 -0
  74. package/dist/utils/language-detection.js.map +1 -0
  75. package/dist/utils/pattern-detector.d.ts +41 -0
  76. package/dist/utils/pattern-detector.d.ts.map +1 -0
  77. package/dist/utils/pattern-detector.js +101 -0
  78. package/dist/utils/pattern-detector.js.map +1 -0
  79. package/dist/utils/usage-tracker.d.ts +120 -0
  80. package/dist/utils/usage-tracker.d.ts.map +1 -0
  81. package/dist/utils/usage-tracker.js +336 -0
  82. package/dist/utils/usage-tracker.js.map +1 -0
  83. package/package.json +98 -0
@@ -0,0 +1,922 @@
1
+ /**
2
+ * Angular Analyzer - Comprehensive Angular-specific code analysis
3
+ * Understands components, services, directives, pipes, modules, guards, interceptors, etc.
4
+ * Detects state management patterns, architectural layers, and Angular-specific patterns
5
+ */
6
+ import { promises as fs } from "fs";
7
+ import path from "path";
8
+ import { parse } from "@typescript-eslint/typescript-estree";
9
+ import { createChunksFromCode } from "../../utils/chunking.js";
10
+ export class AngularAnalyzer {
11
+ name = "angular";
12
+ version = "1.0.0";
13
+ supportedExtensions = [
14
+ ".ts",
15
+ ".js",
16
+ ".html",
17
+ ".scss",
18
+ ".css",
19
+ ".sass",
20
+ ".less",
21
+ ];
22
+ priority = 100; // Highest priority for Angular files
23
+ angularPatterns = {
24
+ component: /@Component\s*\(/,
25
+ service: /@Injectable\s*\(/,
26
+ directive: /@Directive\s*\(/,
27
+ pipe: /@Pipe\s*\(/,
28
+ module: /@NgModule\s*\(/,
29
+ // Guards: Check for interface implementation OR method signature OR functional guard
30
+ guard: /(?:implements\s+(?:CanActivate|CanDeactivate|CanLoad|CanMatch)|canActivate\s*\(|canDeactivate\s*\(|canLoad\s*\(|canMatch\s*\(|CanActivateFn|CanDeactivateFn|CanMatchFn)/,
31
+ interceptor: /(?:implements\s+HttpInterceptor|intercept\s*\(|HttpInterceptorFn)/,
32
+ resolver: /(?:implements\s+Resolve|resolve\s*\(|ResolveFn)/,
33
+ validator: /(?:implements\s+(?:Validator|AsyncValidator)|validate\s*\()/,
34
+ };
35
+ stateManagementPatterns = {
36
+ ngrx: /@ngrx\/store|createAction|createReducer|createSelector/,
37
+ akita: /@datorama\/akita|Query|Store\.update/,
38
+ elf: /@ngneat\/elf|createStore|withEntities/,
39
+ signals: /\bsignal\s*[<(]|\bcomputed\s*[<(]|\beffect\s*\(|\blinkedSignal\s*[<(]/,
40
+ rxjsState: /BehaviorSubject|ReplaySubject|shareReplay/,
41
+ };
42
+ modernAngularPatterns = {
43
+ signalInput: /\binput\s*[<(]|\binput\.required\s*[<(]/,
44
+ signalOutput: /\boutput\s*[<(]/,
45
+ signalModel: /\bmodel\s*[<(]|\bmodel\.required\s*[<(]/,
46
+ signalViewChild: /\bviewChild\s*[<(]|\bviewChild\.required\s*[<(]/,
47
+ signalViewChildren: /\bviewChildren\s*[<(]/,
48
+ signalContentChild: /\bcontentChild\s*[<(]|\bcontentChild\.required\s*[<(]/,
49
+ signalContentChildren: /\bcontentChildren\s*[<(]/,
50
+ controlFlowIf: /@if\s*\(/,
51
+ controlFlowFor: /@for\s*\(/,
52
+ controlFlowSwitch: /@switch\s*\(/,
53
+ controlFlowDefer: /@defer\s*[({]/,
54
+ injectFunction: /\binject\s*[<(]/,
55
+ };
56
+ canAnalyze(filePath, content) {
57
+ const ext = path.extname(filePath).toLowerCase();
58
+ if (!this.supportedExtensions.includes(ext)) {
59
+ return false;
60
+ }
61
+ // For TypeScript files, check if it contains Angular decorators
62
+ if (ext === ".ts" && content) {
63
+ return Object.values(this.angularPatterns).some((pattern) => pattern.test(content));
64
+ }
65
+ // Angular component templates and styles
66
+ if ([".html", ".scss", ".css", ".sass", ".less"].includes(ext)) {
67
+ // Check if there's a corresponding .ts file
68
+ const baseName = filePath.replace(/\.(html|scss|css|sass|less)$/, "");
69
+ return true; // We'll verify during analysis
70
+ }
71
+ return false;
72
+ }
73
+ async analyze(filePath, content) {
74
+ const ext = path.extname(filePath).toLowerCase();
75
+ const relativePath = path.relative(process.cwd(), filePath);
76
+ if (ext === ".ts") {
77
+ return this.analyzeTypeScriptFile(filePath, content, relativePath);
78
+ }
79
+ else if (ext === ".html") {
80
+ return this.analyzeTemplateFile(filePath, content, relativePath);
81
+ }
82
+ else if ([".scss", ".css", ".sass", ".less"].includes(ext)) {
83
+ return this.analyzeStyleFile(filePath, content, relativePath);
84
+ }
85
+ // Fallback
86
+ return {
87
+ filePath,
88
+ language: "unknown",
89
+ framework: "angular",
90
+ components: [],
91
+ imports: [],
92
+ exports: [],
93
+ dependencies: [],
94
+ metadata: {},
95
+ chunks: [],
96
+ };
97
+ }
98
+ async analyzeTypeScriptFile(filePath, content, relativePath) {
99
+ const components = [];
100
+ const imports = [];
101
+ const exports = [];
102
+ const dependencies = [];
103
+ try {
104
+ const ast = parse(content, {
105
+ loc: true,
106
+ range: true,
107
+ comment: true,
108
+ });
109
+ // Extract imports
110
+ for (const node of ast.body) {
111
+ if (node.type === "ImportDeclaration" && node.source.value) {
112
+ const source = node.source.value;
113
+ imports.push({
114
+ source,
115
+ imports: node.specifiers.map((s) => {
116
+ if (s.type === "ImportDefaultSpecifier")
117
+ return "default";
118
+ if (s.type === "ImportNamespaceSpecifier")
119
+ return "*";
120
+ return s.imported?.name || s.local.name;
121
+ }),
122
+ isDefault: node.specifiers.some((s) => s.type === "ImportDefaultSpecifier"),
123
+ isDynamic: false,
124
+ line: node.loc?.start.line,
125
+ });
126
+ // Track dependencies
127
+ if (!source.startsWith(".") && !source.startsWith("/")) {
128
+ dependencies.push(source.split("/")[0]);
129
+ }
130
+ }
131
+ // Extract class declarations with decorators
132
+ if (node.type === "ExportNamedDeclaration" &&
133
+ node.declaration?.type === "ClassDeclaration") {
134
+ const classNode = node.declaration;
135
+ if (classNode.id && classNode.decorators) {
136
+ const component = await this.extractAngularComponent(classNode, content);
137
+ if (component) {
138
+ components.push(component);
139
+ }
140
+ }
141
+ }
142
+ // Handle direct class exports
143
+ if (node.type === "ClassDeclaration" && node.id && node.decorators) {
144
+ const component = await this.extractAngularComponent(node, content);
145
+ if (component) {
146
+ components.push(component);
147
+ }
148
+ }
149
+ // Extract exports
150
+ if (node.type === "ExportNamedDeclaration") {
151
+ if (node.declaration) {
152
+ if (node.declaration.type === "ClassDeclaration" &&
153
+ node.declaration.id) {
154
+ exports.push({
155
+ name: node.declaration.id.name,
156
+ isDefault: false,
157
+ type: "class",
158
+ });
159
+ }
160
+ }
161
+ }
162
+ if (node.type === "ExportDefaultDeclaration") {
163
+ const name = node.declaration.type === "Identifier"
164
+ ? node.declaration.name
165
+ : "default";
166
+ exports.push({
167
+ name,
168
+ isDefault: true,
169
+ type: "default",
170
+ });
171
+ }
172
+ }
173
+ }
174
+ catch (error) {
175
+ console.warn(`Failed to parse Angular TypeScript file ${filePath}:`, error);
176
+ }
177
+ // Detect state management
178
+ const statePattern = this.detectStateManagement(content);
179
+ // Detect Angular v17+ modern patterns
180
+ const modernPatterns = this.detectModernAngularPatterns(content);
181
+ // Determine architectural layer
182
+ const layer = this.determineLayer(filePath, components);
183
+ // Create chunks with Angular-specific metadata
184
+ const chunks = await createChunksFromCode(content, filePath, relativePath, "typescript", components, {
185
+ framework: "angular",
186
+ layer,
187
+ statePattern,
188
+ dependencies,
189
+ modernPatterns,
190
+ });
191
+ // Build detected patterns for the indexer to forward
192
+ const detectedPatterns = [];
193
+ // Dependency Injection pattern
194
+ if (modernPatterns.includes("injectFunction")) {
195
+ detectedPatterns.push({ category: "dependencyInjection", name: "inject() function" });
196
+ }
197
+ else if (content.includes("constructor(") && content.includes("private") &&
198
+ (relativePath.endsWith(".service.ts") || relativePath.endsWith(".component.ts"))) {
199
+ detectedPatterns.push({ category: "dependencyInjection", name: "Constructor injection" });
200
+ }
201
+ // State Management pattern
202
+ if (/BehaviorSubject|ReplaySubject|Subject|Observable/.test(content)) {
203
+ detectedPatterns.push({ category: "stateManagement", name: "RxJS" });
204
+ }
205
+ if (modernPatterns.some((p) => p.startsWith("signal"))) {
206
+ detectedPatterns.push({ category: "stateManagement", name: "Signals" });
207
+ }
208
+ // Reactivity patterns
209
+ if (/\beffect\s*\(/.test(content)) {
210
+ detectedPatterns.push({ category: "reactivity", name: "Effect" });
211
+ }
212
+ if (/\bcomputed\s*[<(]/.test(content)) {
213
+ detectedPatterns.push({ category: "reactivity", name: "Computed" });
214
+ }
215
+ // Component Style pattern detection
216
+ // Logic: explicit standalone: true → Standalone
217
+ // explicit standalone: false → NgModule-based
218
+ // no explicit flag + uses modern patterns (inject, signals) → likely Standalone (Angular v19+ default)
219
+ // no explicit flag + no modern patterns → ambiguous, don't classify
220
+ const hasExplicitStandalone = content.includes("standalone: true");
221
+ const hasExplicitNgModule = content.includes("standalone: false");
222
+ const usesModernPatterns = modernPatterns.includes("injectFunction") ||
223
+ modernPatterns.some(p => p.startsWith("signal"));
224
+ if (relativePath.endsWith("component.ts") || relativePath.endsWith("directive.ts") || relativePath.endsWith("pipe.ts")) {
225
+ if (hasExplicitStandalone) {
226
+ detectedPatterns.push({ category: "componentStyle", name: "Standalone" });
227
+ }
228
+ else if (hasExplicitNgModule) {
229
+ detectedPatterns.push({ category: "componentStyle", name: "NgModule-based" });
230
+ }
231
+ else if (usesModernPatterns) {
232
+ // No explicit flag but uses modern patterns → likely v19+ standalone default
233
+ detectedPatterns.push({ category: "componentStyle", name: "Standalone" });
234
+ }
235
+ // If no explicit flag and no modern patterns, don't classify (ambiguous)
236
+ }
237
+ // Input style pattern
238
+ if (modernPatterns.includes("signalInput")) {
239
+ detectedPatterns.push({ category: "componentInputs", name: "Signal-based inputs" });
240
+ }
241
+ else if (content.includes("@Input()")) {
242
+ detectedPatterns.push({ category: "componentInputs", name: "Decorator-based @Input" });
243
+ }
244
+ return {
245
+ filePath,
246
+ language: "typescript",
247
+ framework: "angular",
248
+ components,
249
+ imports,
250
+ exports,
251
+ dependencies: dependencies.map((name) => ({
252
+ name,
253
+ category: this.categorizeDependency(name),
254
+ layer,
255
+ })),
256
+ metadata: {
257
+ analyzer: this.name,
258
+ layer,
259
+ statePattern,
260
+ modernPatterns,
261
+ // isStandalone: true if explicit standalone: true, or if uses modern patterns (implying v19+ default)
262
+ isStandalone: content.includes("standalone: true") ||
263
+ (!content.includes("standalone: false") &&
264
+ (modernPatterns.includes("injectFunction") || modernPatterns.some(p => p.startsWith("signal")))),
265
+ hasRoutes: content.includes("RouterModule") || content.includes("routes"),
266
+ usesSignals: modernPatterns.length > 0 &&
267
+ modernPatterns.some((p) => p.startsWith("signal")),
268
+ usesControlFlow: modernPatterns.some((p) => p.startsWith("controlFlow")),
269
+ usesInject: modernPatterns.includes("injectFunction"),
270
+ usesRxJS: /BehaviorSubject|ReplaySubject|Subject|Observable/.test(content),
271
+ usesEffect: /\beffect\s*\(/.test(content),
272
+ usesComputed: /\bcomputed\s*[<(]/.test(content),
273
+ componentType: components.length > 0 ? components[0].metadata.angularType : undefined,
274
+ // NEW: Patterns for the indexer to forward generically
275
+ detectedPatterns,
276
+ },
277
+ chunks,
278
+ };
279
+ }
280
+ /**
281
+ * Detect Angular v17+ modern patterns in the code
282
+ */
283
+ detectModernAngularPatterns(content) {
284
+ const detected = [];
285
+ for (const [patternName, regex] of Object.entries(this.modernAngularPatterns)) {
286
+ if (regex.test(content)) {
287
+ detected.push(patternName);
288
+ }
289
+ }
290
+ return detected;
291
+ }
292
+ async extractAngularComponent(classNode, content) {
293
+ if (!classNode.decorators || classNode.decorators.length === 0) {
294
+ return null;
295
+ }
296
+ const decorator = classNode.decorators[0];
297
+ const decoratorName = decorator.expression.callee?.name || decorator.expression.name;
298
+ let componentType;
299
+ let angularType;
300
+ // Determine Angular component type
301
+ if (decoratorName === "Component") {
302
+ componentType = "component";
303
+ angularType = "component";
304
+ }
305
+ else if (decoratorName === "Directive") {
306
+ componentType = "directive";
307
+ angularType = "directive";
308
+ }
309
+ else if (decoratorName === "Pipe") {
310
+ componentType = "pipe";
311
+ angularType = "pipe";
312
+ }
313
+ else if (decoratorName === "NgModule") {
314
+ componentType = "module";
315
+ angularType = "module";
316
+ }
317
+ else if (decoratorName === "Injectable") {
318
+ // For @Injectable, check if it's actually a guard/interceptor/resolver/validator
319
+ // before defaulting to 'service'
320
+ const classContent = content.substring(classNode.range[0], classNode.range[1]);
321
+ if (this.angularPatterns.guard.test(classContent)) {
322
+ componentType = "guard";
323
+ angularType = "guard";
324
+ }
325
+ else if (this.angularPatterns.interceptor.test(classContent)) {
326
+ componentType = "interceptor";
327
+ angularType = "interceptor";
328
+ }
329
+ else if (this.angularPatterns.resolver.test(classContent)) {
330
+ componentType = "resolver";
331
+ angularType = "resolver";
332
+ }
333
+ else if (this.angularPatterns.validator.test(classContent)) {
334
+ componentType = "validator";
335
+ angularType = "validator";
336
+ }
337
+ else {
338
+ // Default to service if no specific pattern matches
339
+ componentType = "service";
340
+ angularType = "service";
341
+ }
342
+ }
343
+ // If still no type, check patterns one more time (for classes without decorators)
344
+ if (!componentType) {
345
+ const classContent = content.substring(classNode.range[0], classNode.range[1]);
346
+ if (this.angularPatterns.guard.test(classContent)) {
347
+ componentType = "guard";
348
+ angularType = "guard";
349
+ }
350
+ else if (this.angularPatterns.interceptor.test(classContent)) {
351
+ componentType = "interceptor";
352
+ angularType = "interceptor";
353
+ }
354
+ else if (this.angularPatterns.resolver.test(classContent)) {
355
+ componentType = "resolver";
356
+ angularType = "resolver";
357
+ }
358
+ else if (this.angularPatterns.validator.test(classContent)) {
359
+ componentType = "validator";
360
+ angularType = "validator";
361
+ }
362
+ }
363
+ // Extract decorator metadata
364
+ const decoratorMetadata = this.extractDecoratorMetadata(decorator);
365
+ // Extract lifecycle hooks
366
+ const lifecycle = this.extractLifecycleHooks(classNode);
367
+ // Extract injected dependencies
368
+ const injectedServices = this.extractInjectedServices(classNode);
369
+ // Extract inputs and outputs
370
+ const inputs = this.extractInputs(classNode);
371
+ const outputs = this.extractOutputs(classNode);
372
+ return {
373
+ name: classNode.id.name,
374
+ type: "class",
375
+ componentType,
376
+ startLine: classNode.loc.start.line,
377
+ endLine: classNode.loc.end.line,
378
+ decorators: [
379
+ {
380
+ name: decoratorName,
381
+ properties: decoratorMetadata,
382
+ },
383
+ ],
384
+ lifecycle,
385
+ dependencies: injectedServices,
386
+ properties: [...inputs, ...outputs],
387
+ metadata: {
388
+ angularType,
389
+ selector: decoratorMetadata.selector,
390
+ isStandalone: decoratorMetadata.standalone === true,
391
+ template: decoratorMetadata.template,
392
+ templateUrl: decoratorMetadata.templateUrl,
393
+ styleUrls: decoratorMetadata.styleUrls,
394
+ inputs: inputs.map((i) => i.name),
395
+ outputs: outputs.map((o) => o.name),
396
+ },
397
+ };
398
+ }
399
+ extractDecoratorMetadata(decorator) {
400
+ const metadata = {};
401
+ try {
402
+ if (decorator.expression.arguments && decorator.expression.arguments[0]) {
403
+ const arg = decorator.expression.arguments[0];
404
+ if (arg.type === "ObjectExpression") {
405
+ for (const prop of arg.properties) {
406
+ if (prop.key && prop.value) {
407
+ const key = prop.key.name || prop.key.value;
408
+ if (prop.value.type === "Literal") {
409
+ metadata[key] = prop.value.value;
410
+ }
411
+ else if (prop.value.type === "ArrayExpression") {
412
+ metadata[key] = prop.value.elements
413
+ .map((el) => (el.type === "Literal" ? el.value : null))
414
+ .filter(Boolean);
415
+ }
416
+ else if (prop.value.type === "Identifier") {
417
+ metadata[key] = prop.value.name;
418
+ }
419
+ }
420
+ }
421
+ }
422
+ }
423
+ }
424
+ catch (error) {
425
+ console.warn("Failed to extract decorator metadata:", error);
426
+ }
427
+ return metadata;
428
+ }
429
+ extractLifecycleHooks(classNode) {
430
+ const hooks = [];
431
+ const lifecycleHooks = [
432
+ "ngOnChanges",
433
+ "ngOnInit",
434
+ "ngDoCheck",
435
+ "ngAfterContentInit",
436
+ "ngAfterContentChecked",
437
+ "ngAfterViewInit",
438
+ "ngAfterViewChecked",
439
+ "ngOnDestroy",
440
+ ];
441
+ if (classNode.body && classNode.body.body) {
442
+ for (const member of classNode.body.body) {
443
+ if (member.type === "MethodDefinition" && member.key) {
444
+ const methodName = member.key.name;
445
+ if (lifecycleHooks.includes(methodName)) {
446
+ hooks.push(methodName);
447
+ }
448
+ }
449
+ }
450
+ }
451
+ return hooks;
452
+ }
453
+ extractInjectedServices(classNode) {
454
+ const services = [];
455
+ // Look for constructor parameters
456
+ if (classNode.body && classNode.body.body) {
457
+ for (const member of classNode.body.body) {
458
+ if (member.type === "MethodDefinition" &&
459
+ member.kind === "constructor") {
460
+ if (member.value.params) {
461
+ for (const param of member.value.params) {
462
+ if (param.typeAnnotation?.typeAnnotation?.typeName) {
463
+ services.push(param.typeAnnotation.typeAnnotation.typeName.name);
464
+ }
465
+ }
466
+ }
467
+ }
468
+ }
469
+ }
470
+ return services;
471
+ }
472
+ extractInputs(classNode) {
473
+ const inputs = [];
474
+ if (classNode.body && classNode.body.body) {
475
+ for (const member of classNode.body.body) {
476
+ if (member.type === "PropertyDefinition") {
477
+ // Check for decorator-based @Input()
478
+ if (member.decorators) {
479
+ const hasInput = member.decorators.some((d) => d.expression?.callee?.name === "Input" ||
480
+ d.expression?.name === "Input");
481
+ if (hasInput && member.key) {
482
+ inputs.push({
483
+ name: member.key.name,
484
+ type: member.typeAnnotation?.typeAnnotation?.type || "any",
485
+ style: "decorator",
486
+ });
487
+ }
488
+ }
489
+ // Check for signal-based input() (Angular v17.1+)
490
+ if (member.value && member.key) {
491
+ const valueStr = member.value.type === "CallExpression"
492
+ ? member.value.callee?.name || member.value.callee?.object?.name
493
+ : null;
494
+ if (valueStr === "input") {
495
+ inputs.push({
496
+ name: member.key.name,
497
+ type: "InputSignal",
498
+ style: "signal",
499
+ required: member.value.callee?.property?.name === "required",
500
+ });
501
+ }
502
+ }
503
+ }
504
+ }
505
+ }
506
+ return inputs;
507
+ }
508
+ extractOutputs(classNode) {
509
+ const outputs = [];
510
+ if (classNode.body && classNode.body.body) {
511
+ for (const member of classNode.body.body) {
512
+ if (member.type === "PropertyDefinition") {
513
+ // Check for decorator-based @Output()
514
+ if (member.decorators) {
515
+ const hasOutput = member.decorators.some((d) => d.expression?.callee?.name === "Output" ||
516
+ d.expression?.name === "Output");
517
+ if (hasOutput && member.key) {
518
+ outputs.push({
519
+ name: member.key.name,
520
+ type: "EventEmitter",
521
+ style: "decorator",
522
+ });
523
+ }
524
+ }
525
+ // Check for signal-based output() (Angular v17.1+)
526
+ if (member.value && member.key) {
527
+ const valueStr = member.value.type === "CallExpression"
528
+ ? member.value.callee?.name
529
+ : null;
530
+ if (valueStr === "output") {
531
+ outputs.push({
532
+ name: member.key.name,
533
+ type: "OutputEmitterRef",
534
+ style: "signal",
535
+ });
536
+ }
537
+ }
538
+ }
539
+ }
540
+ }
541
+ return outputs;
542
+ }
543
+ async analyzeTemplateFile(filePath, content, relativePath) {
544
+ // Find corresponding component file
545
+ const componentPath = filePath.replace(/\.html$/, ".ts");
546
+ // Detect legacy vs modern control flow
547
+ const hasLegacyDirectives = /\*ng(?:If|For|Switch)/.test(content);
548
+ const hasModernControlFlow = /@(?:if|for|switch|defer)\s*[({]/.test(content);
549
+ return {
550
+ filePath,
551
+ language: "html",
552
+ framework: "angular",
553
+ components: [],
554
+ imports: [],
555
+ exports: [],
556
+ dependencies: [],
557
+ metadata: {
558
+ analyzer: this.name,
559
+ type: "template",
560
+ componentPath,
561
+ hasLegacyDirectives,
562
+ hasModernControlFlow,
563
+ hasBindings: /\[|\(|{{/.test(content),
564
+ hasDefer: /@defer\s*[({]/.test(content),
565
+ },
566
+ chunks: await createChunksFromCode(content, filePath, relativePath, "html", []),
567
+ };
568
+ }
569
+ async analyzeStyleFile(filePath, content, relativePath) {
570
+ const ext = path.extname(filePath).toLowerCase();
571
+ const language = ext.substring(1); // Remove the dot
572
+ return {
573
+ filePath,
574
+ language,
575
+ framework: "angular",
576
+ components: [],
577
+ imports: [],
578
+ exports: [],
579
+ dependencies: [],
580
+ metadata: {
581
+ analyzer: this.name,
582
+ type: "style",
583
+ },
584
+ chunks: await createChunksFromCode(content, filePath, relativePath, language, []),
585
+ };
586
+ }
587
+ detectStateManagement(content) {
588
+ for (const [pattern, regex] of Object.entries(this.stateManagementPatterns)) {
589
+ if (regex.test(content)) {
590
+ return pattern;
591
+ }
592
+ }
593
+ return undefined;
594
+ }
595
+ determineLayer(filePath, components) {
596
+ const lowerPath = filePath.toLowerCase();
597
+ // Check path-based patterns
598
+ if (lowerPath.includes("/component") ||
599
+ lowerPath.includes("/view") ||
600
+ lowerPath.includes("/page")) {
601
+ return "presentation";
602
+ }
603
+ if (lowerPath.includes("/service")) {
604
+ return "business";
605
+ }
606
+ if (lowerPath.includes("/data") ||
607
+ lowerPath.includes("/repository") ||
608
+ lowerPath.includes("/api")) {
609
+ return "data";
610
+ }
611
+ if (lowerPath.includes("/store") ||
612
+ lowerPath.includes("/state") ||
613
+ lowerPath.includes("/ngrx")) {
614
+ return "state";
615
+ }
616
+ if (lowerPath.includes("/core")) {
617
+ return "core";
618
+ }
619
+ if (lowerPath.includes("/shared")) {
620
+ return "shared";
621
+ }
622
+ if (lowerPath.includes("/feature")) {
623
+ return "feature";
624
+ }
625
+ // Check component types
626
+ for (const component of components) {
627
+ if (component.componentType === "component" ||
628
+ component.componentType === "directive" ||
629
+ component.componentType === "pipe") {
630
+ return "presentation";
631
+ }
632
+ if (component.componentType === "service") {
633
+ return lowerPath.includes("http") || lowerPath.includes("api")
634
+ ? "data"
635
+ : "business";
636
+ }
637
+ if (component.componentType === "guard" ||
638
+ component.componentType === "interceptor") {
639
+ return "core";
640
+ }
641
+ }
642
+ return "unknown";
643
+ }
644
+ categorizeDependency(name) {
645
+ if (name.startsWith("@angular/")) {
646
+ return "framework";
647
+ }
648
+ if (name.includes("ngrx") ||
649
+ name.includes("akita") ||
650
+ name.includes("elf")) {
651
+ return "state";
652
+ }
653
+ if (name.includes("material") ||
654
+ name.includes("primeng") ||
655
+ name.includes("ng-bootstrap")) {
656
+ return "ui";
657
+ }
658
+ if (name.includes("router")) {
659
+ return "routing";
660
+ }
661
+ if (name.includes("http") || name.includes("common/http")) {
662
+ return "http";
663
+ }
664
+ if (name.includes("test") ||
665
+ name.includes("jest") ||
666
+ name.includes("jasmine") ||
667
+ name.includes("karma")) {
668
+ return "testing";
669
+ }
670
+ return "other";
671
+ }
672
+ async detectCodebaseMetadata(rootPath) {
673
+ const metadata = {
674
+ name: path.basename(rootPath),
675
+ rootPath,
676
+ languages: [],
677
+ dependencies: [],
678
+ architecture: {
679
+ type: "feature-based",
680
+ layers: {
681
+ presentation: 0,
682
+ business: 0,
683
+ data: 0,
684
+ state: 0,
685
+ core: 0,
686
+ shared: 0,
687
+ feature: 0,
688
+ infrastructure: 0,
689
+ unknown: 0,
690
+ },
691
+ patterns: [],
692
+ },
693
+ styleGuides: [],
694
+ documentation: [],
695
+ projectStructure: {
696
+ type: "single-app",
697
+ },
698
+ statistics: {
699
+ totalFiles: 0,
700
+ totalLines: 0,
701
+ totalComponents: 0,
702
+ componentsByType: {},
703
+ componentsByLayer: {
704
+ presentation: 0,
705
+ business: 0,
706
+ data: 0,
707
+ state: 0,
708
+ core: 0,
709
+ shared: 0,
710
+ feature: 0,
711
+ infrastructure: 0,
712
+ unknown: 0,
713
+ },
714
+ },
715
+ customMetadata: {},
716
+ };
717
+ try {
718
+ // Read package.json
719
+ const packageJsonPath = path.join(rootPath, "package.json");
720
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
721
+ metadata.name = packageJson.name || metadata.name;
722
+ // Extract Angular version and dependencies
723
+ const allDeps = {
724
+ ...packageJson.dependencies,
725
+ ...packageJson.devDependencies,
726
+ };
727
+ const angularVersion = allDeps["@angular/core"]?.replace(/[\^~]/, "") || "unknown";
728
+ // Detect state management
729
+ const stateManagement = [];
730
+ if (allDeps["@ngrx/store"])
731
+ stateManagement.push("ngrx");
732
+ if (allDeps["@datorama/akita"])
733
+ stateManagement.push("akita");
734
+ if (allDeps["@ngneat/elf"])
735
+ stateManagement.push("elf");
736
+ // Detect UI libraries
737
+ const uiLibraries = [];
738
+ if (allDeps["@angular/material"])
739
+ uiLibraries.push("Angular Material");
740
+ if (allDeps["primeng"])
741
+ uiLibraries.push("PrimeNG");
742
+ if (allDeps["@ng-bootstrap/ng-bootstrap"])
743
+ uiLibraries.push("ng-bootstrap");
744
+ // Detect testing frameworks
745
+ const testingFrameworks = [];
746
+ if (allDeps["jasmine-core"])
747
+ testingFrameworks.push("Jasmine");
748
+ if (allDeps["karma"])
749
+ testingFrameworks.push("Karma");
750
+ if (allDeps["jest"])
751
+ testingFrameworks.push("Jest");
752
+ metadata.framework = {
753
+ name: "Angular",
754
+ version: angularVersion,
755
+ type: "angular",
756
+ variant: "unknown", // Will be determined during analysis
757
+ stateManagement,
758
+ uiLibraries,
759
+ testingFrameworks,
760
+ };
761
+ // Convert dependencies
762
+ metadata.dependencies = Object.entries(allDeps).map(([name, version]) => ({
763
+ name,
764
+ version: version,
765
+ category: this.categorizeDependency(name),
766
+ }));
767
+ }
768
+ catch (error) {
769
+ console.warn("Failed to read Angular project metadata:", error);
770
+ }
771
+ // Calculate statistics from existing index if available
772
+ try {
773
+ const indexPath = path.join(rootPath, ".codebase-index.json");
774
+ const indexContent = await fs.readFile(indexPath, "utf-8");
775
+ const chunks = JSON.parse(indexContent);
776
+ console.error(`Loading statistics from ${indexPath}: ${chunks.length} chunks`);
777
+ if (Array.isArray(chunks) && chunks.length > 0) {
778
+ metadata.statistics.totalFiles = new Set(chunks.map((c) => c.filePath)).size;
779
+ metadata.statistics.totalLines = chunks.reduce((sum, c) => sum + (c.endLine - c.startLine + 1), 0);
780
+ // Count components by type
781
+ const componentCounts = {};
782
+ const layerCounts = {
783
+ presentation: 0,
784
+ business: 0,
785
+ data: 0,
786
+ state: 0,
787
+ core: 0,
788
+ shared: 0,
789
+ feature: 0,
790
+ infrastructure: 0,
791
+ unknown: 0,
792
+ };
793
+ for (const chunk of chunks) {
794
+ if (chunk.componentType) {
795
+ componentCounts[chunk.componentType] =
796
+ (componentCounts[chunk.componentType] || 0) + 1;
797
+ metadata.statistics.totalComponents++;
798
+ }
799
+ if (chunk.layer) {
800
+ layerCounts[chunk.layer] =
801
+ (layerCounts[chunk.layer] || 0) + 1;
802
+ }
803
+ }
804
+ metadata.statistics.componentsByType = componentCounts;
805
+ metadata.statistics.componentsByLayer = layerCounts;
806
+ metadata.architecture.layers = layerCounts;
807
+ }
808
+ }
809
+ catch (error) {
810
+ // Index doesn't exist yet, keep statistics at 0
811
+ console.warn("Failed to calculate statistics from index:", error);
812
+ }
813
+ return metadata;
814
+ }
815
+ /**
816
+ * Generate Angular-specific summary for a code chunk
817
+ */
818
+ summarize(chunk) {
819
+ const { componentType, metadata, content } = chunk;
820
+ const fileName = path.basename(chunk.filePath);
821
+ // Extract class/component name
822
+ const classMatch = content.match(/(?:export\s+)?class\s+(\w+)/);
823
+ const className = classMatch ? classMatch[1] : fileName;
824
+ switch (componentType) {
825
+ case "component":
826
+ const selector = metadata.decorator?.selector || "unknown";
827
+ const inputs = metadata.decorator?.inputs?.length || 0;
828
+ const outputs = metadata.decorator?.outputs?.length || 0;
829
+ const lifecycle = this.extractLifecycleMethods(content);
830
+ return `Angular component '${className}' (selector: ${selector})${lifecycle ? ` with ${lifecycle}` : ""}${inputs ? `, ${inputs} inputs` : ""}${outputs ? `, ${outputs} outputs` : ""}.`;
831
+ case "service":
832
+ const providedIn = metadata.decorator?.providedIn || "unknown";
833
+ const methods = this.extractPublicMethods(content);
834
+ return `Angular service '${className}' (providedIn: ${providedIn})${methods ? ` providing ${methods}` : ""}.`;
835
+ case "guard":
836
+ const guardType = this.detectGuardType(content);
837
+ return `Angular ${guardType} guard '${className}' protecting routes.`;
838
+ case "directive":
839
+ const directiveSelector = metadata.decorator?.selector || "unknown";
840
+ return `Angular directive '${className}' (selector: ${directiveSelector}).`;
841
+ case "pipe":
842
+ const pipeName = metadata.decorator?.name || "unknown";
843
+ return `Angular pipe '${className}' (name: ${pipeName}) for data transformation.`;
844
+ case "module":
845
+ const imports = metadata.decorator?.imports?.length || 0;
846
+ const declarations = metadata.decorator?.declarations?.length || 0;
847
+ return `Angular module '${className}' with ${declarations} declarations and ${imports} imports.`;
848
+ case "interceptor":
849
+ return `Angular HTTP interceptor '${className}' modifying HTTP requests/responses.`;
850
+ case "resolver":
851
+ return `Angular resolver '${className}' pre-fetching route data.`;
852
+ case "validator":
853
+ return `Angular validator '${className}' for form validation.`;
854
+ default:
855
+ // Try to provide a meaningful fallback
856
+ if (className && className !== fileName) {
857
+ // Check for common patterns
858
+ if (content.includes("signal(") ||
859
+ content.includes("computed(") ||
860
+ content.includes("effect(")) {
861
+ return `Angular code '${className}' using signals.`;
862
+ }
863
+ if (content.includes("inject(")) {
864
+ return `Angular code '${className}' using dependency injection.`;
865
+ }
866
+ if (content.includes("Observable") || content.includes("Subject")) {
867
+ return `Angular code '${className}' with reactive streams.`;
868
+ }
869
+ return `Angular code '${className}' in ${fileName}.`;
870
+ }
871
+ // Extract first meaningful export or declaration
872
+ const exportMatch = content.match(/export\s+(?:const|function|class|interface|type|enum)\s+(\w+)/);
873
+ if (exportMatch) {
874
+ return `Exports '${exportMatch[1]}' from ${fileName}.`;
875
+ }
876
+ return `Angular code in ${fileName}.`;
877
+ }
878
+ }
879
+ extractLifecycleMethods(content) {
880
+ const lifecycles = [
881
+ "ngOnInit",
882
+ "ngOnChanges",
883
+ "ngOnDestroy",
884
+ "ngAfterViewInit",
885
+ "ngAfterContentInit",
886
+ ];
887
+ const found = lifecycles.filter((method) => content.includes(method));
888
+ return found.length > 0 ? found.join(", ") : "";
889
+ }
890
+ extractPublicMethods(content) {
891
+ const methodMatches = content.match(/public\s+(\w+)\s*\(/g);
892
+ if (!methodMatches || methodMatches.length === 0)
893
+ return "";
894
+ const methods = methodMatches
895
+ .slice(0, 3)
896
+ .map((m) => m.match(/public\s+(\w+)/)?.[1])
897
+ .filter(Boolean);
898
+ return methods.length > 0 ? `methods: ${methods.join(", ")}` : "";
899
+ }
900
+ detectGuardType(content) {
901
+ if (content.includes("CanActivate"))
902
+ return "CanActivate";
903
+ if (content.includes("CanDeactivate"))
904
+ return "CanDeactivate";
905
+ if (content.includes("CanLoad"))
906
+ return "CanLoad";
907
+ if (content.includes("CanMatch"))
908
+ return "CanMatch";
909
+ return "route";
910
+ }
911
+ extractFirstComment(content) {
912
+ const commentMatch = content.match(/\/\*\*\s*\n?\s*\*\s*(.+?)(?:\n|\*\/)/);
913
+ return commentMatch ? commentMatch[1].trim() : "";
914
+ }
915
+ extractFirstLine(content) {
916
+ const firstLine = content
917
+ .split("\n")
918
+ .find((line) => line.trim() && !line.trim().startsWith("import"));
919
+ return firstLine ? firstLine.trim().slice(0, 60) + "..." : "";
920
+ }
921
+ }
922
+ //# sourceMappingURL=index.js.map