codesearch-mcp 0.2.2-alpha → 0.3.0-alpha

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 (158) hide show
  1. package/CHANGELOG.md +65 -1
  2. package/README.md +60 -33
  3. package/dist/app.js +46 -22
  4. package/dist/src/chunking/AstChunker.d.ts +12 -0
  5. package/dist/src/chunking/AstChunker.js +30 -0
  6. package/dist/src/chunking/CharacterChunkStrategy.d.ts +8 -0
  7. package/dist/src/chunking/CharacterChunkStrategy.js +15 -0
  8. package/dist/src/chunking/DeclarationChunkStrategy.d.ts +15 -0
  9. package/dist/src/chunking/DeclarationChunkStrategy.js +87 -0
  10. package/dist/src/chunking/FragmentMerger.d.ts +11 -0
  11. package/dist/src/chunking/FragmentMerger.js +61 -0
  12. package/dist/src/chunking/ImportOnlyFilter.d.ts +5 -0
  13. package/dist/src/chunking/ImportOnlyFilter.js +14 -0
  14. package/dist/src/chunking/LangchainTextSplitter.d.ts +21 -0
  15. package/dist/src/chunking/LangchainTextSplitter.js +133 -0
  16. package/dist/src/chunking/MinSizeSplitPolicy.d.ts +9 -0
  17. package/dist/src/chunking/MinSizeSplitPolicy.js +18 -0
  18. package/dist/src/chunking/RunPacker.d.ts +12 -0
  19. package/dist/src/chunking/RunPacker.js +84 -0
  20. package/dist/src/chunking/create-chunker.d.ts +3 -0
  21. package/dist/src/chunking/create-chunker.js +15 -0
  22. package/dist/src/chunking/interfaces.d.ts +27 -0
  23. package/dist/src/chunking/interfaces.js +2 -0
  24. package/dist/src/commands/ConfigCommand.js +1 -9
  25. package/dist/src/commands/EvalCommand.d.ts +11 -0
  26. package/dist/src/commands/EvalCommand.js +264 -0
  27. package/dist/src/commands/IndexCommand.js +70 -66
  28. package/dist/src/commands/McpCommand.js +2 -8
  29. package/dist/src/commands/WebCommand.js +2 -5
  30. package/dist/src/config-error.d.ts +0 -4
  31. package/dist/src/config-error.js +0 -4
  32. package/dist/src/config-store.d.ts +1 -54
  33. package/dist/src/config-store.js +8 -69
  34. package/dist/src/config.d.ts +8 -17
  35. package/dist/src/config.js +35 -34
  36. package/dist/src/constants.d.ts +2 -28
  37. package/dist/src/constants.js +6 -28
  38. package/dist/src/container.d.ts +6 -0
  39. package/dist/src/container.js +12 -3
  40. package/dist/src/db/index.d.ts +1 -1
  41. package/dist/src/db/index.js +1 -2
  42. package/dist/src/db/schema.d.ts +22 -29
  43. package/dist/src/db/schema.js +3 -14
  44. package/dist/src/declarations/DeclarationTree.d.ts +19 -0
  45. package/dist/src/declarations/DeclarationTree.js +66 -0
  46. package/dist/src/declarations/DeclarationTreeBuilder.d.ts +22 -0
  47. package/dist/src/declarations/DeclarationTreeBuilder.js +179 -0
  48. package/dist/src/declarations/SourceLines.d.ts +37 -0
  49. package/dist/src/declarations/SourceLines.js +415 -0
  50. package/dist/src/dependency-traversal.d.ts +10 -0
  51. package/dist/src/dependency-traversal.js +39 -0
  52. package/dist/src/enriched-prompt.d.ts +0 -5
  53. package/dist/src/enriched-prompt.js +0 -5
  54. package/dist/src/error-record.d.ts +12 -0
  55. package/dist/src/error-record.js +248 -0
  56. package/dist/src/eval-corpus.d.ts +16 -0
  57. package/dist/src/eval-corpus.js +169 -0
  58. package/dist/src/eval-results.d.ts +15 -0
  59. package/dist/src/eval-results.js +397 -0
  60. package/dist/src/file-utils.d.ts +10 -0
  61. package/dist/src/file-utils.js +19 -1
  62. package/dist/src/graph.d.ts +0 -25
  63. package/dist/src/graph.js +0 -28
  64. package/dist/src/help.js +4 -1
  65. package/dist/src/hnsw-settings.d.ts +3 -0
  66. package/dist/src/hnsw-settings.js +24 -0
  67. package/dist/src/interfaces/IAstParser.d.ts +5 -30
  68. package/dist/src/interfaces/IChunker.d.ts +3 -12
  69. package/dist/src/interfaces/ICorpusInspector.d.ts +7 -0
  70. package/dist/src/interfaces/ICorpusInspector.js +2 -0
  71. package/dist/src/interfaces/IDatabase.d.ts +2 -36
  72. package/dist/src/interfaces/IEmbedder.d.ts +0 -9
  73. package/dist/src/interfaces/ILogger.d.ts +1 -11
  74. package/dist/src/interfaces/IMcpToolHandler.d.ts +0 -5
  75. package/dist/src/interfaces/IMonorepoResolver.d.ts +0 -22
  76. package/dist/src/interfaces/IProjectDiscovery.d.ts +0 -17
  77. package/dist/src/interfaces/IRetrievalEvaluator.d.ts +216 -0
  78. package/dist/src/interfaces/IRetrievalEvaluator.js +2 -0
  79. package/dist/src/interfaces/ISearcher.d.ts +26 -20
  80. package/dist/src/interfaces/index.d.ts +1 -0
  81. package/dist/src/interrupt.js +0 -2
  82. package/dist/src/owner-path.d.ts +9 -10
  83. package/dist/src/owner-path.js +11 -23
  84. package/dist/src/project-scope.d.ts +7 -0
  85. package/dist/src/project-scope.js +37 -0
  86. package/dist/src/project-selector.d.ts +1 -0
  87. package/dist/src/project-selector.js +13 -6
  88. package/dist/src/retrieval-eval.d.ts +24 -0
  89. package/dist/src/retrieval-eval.js +500 -0
  90. package/dist/src/search/result-shaping.d.ts +4 -0
  91. package/dist/src/search/result-shaping.js +19 -0
  92. package/dist/src/services/AstParserService.d.ts +28 -23
  93. package/dist/src/services/AstParserService.js +391 -98
  94. package/dist/src/services/DatabaseService.d.ts +2 -19
  95. package/dist/src/services/DatabaseService.js +44 -81
  96. package/dist/src/services/EmbedderFactory.d.ts +0 -4
  97. package/dist/src/services/EmbedderFactory.js +0 -5
  98. package/dist/src/services/IndexerService.d.ts +2 -10
  99. package/dist/src/services/IndexerService.js +29 -45
  100. package/dist/src/services/InfinityEmbedder.d.ts +0 -21
  101. package/dist/src/services/InfinityEmbedder.js +0 -15
  102. package/dist/src/services/LoggerService.d.ts +7 -5
  103. package/dist/src/services/LoggerService.js +37 -41
  104. package/dist/src/services/McpServer.d.ts +25 -10
  105. package/dist/src/services/McpServer.js +101 -64
  106. package/dist/src/services/MonorepoAwareDiscoveryService.d.ts +1 -45
  107. package/dist/src/services/MonorepoAwareDiscoveryService.js +1 -51
  108. package/dist/src/services/NxMonorepoResolver.d.ts +0 -29
  109. package/dist/src/services/NxMonorepoResolver.js +5 -56
  110. package/dist/src/services/OpenAICompatibleEmbedder.d.ts +0 -6
  111. package/dist/src/services/OpenAICompatibleEmbedder.js +1 -14
  112. package/dist/src/services/ProjectDiscoveryService.d.ts +0 -4
  113. package/dist/src/services/ProjectDiscoveryService.js +0 -4
  114. package/dist/src/services/SearcherService.d.ts +127 -70
  115. package/dist/src/services/SearcherService.js +526 -305
  116. package/dist/src/services/VoyageEmbedder.d.ts +0 -6
  117. package/dist/src/services/VoyageEmbedder.js +0 -6
  118. package/dist/src/services/WebServerService.d.ts +1 -53
  119. package/dist/src/services/WebServerService.js +11 -57
  120. package/dist/src/services/http-retry.d.ts +0 -7
  121. package/dist/src/services/http-retry.js +1 -10
  122. package/dist/src/services/infinity-provisioning.d.ts +0 -74
  123. package/dist/src/services/infinity-provisioning.js +5 -88
  124. package/dist/src/services/legacy-ollama-index.d.ts +0 -16
  125. package/dist/src/services/legacy-ollama-index.js +0 -23
  126. package/dist/src/services/reranker.d.ts +0 -9
  127. package/dist/src/services/reranker.js +46 -12
  128. package/dist/src/setup.d.ts +11 -23
  129. package/dist/src/setup.js +98 -71
  130. package/dist/src/symbol-name.d.ts +0 -12
  131. package/dist/src/symbol-name.js +0 -13
  132. package/dist/src/symbol-rows.d.ts +2 -11
  133. package/dist/src/symbol-rows.js +5 -13
  134. package/dist/src/tool-call-log.d.ts +2 -0
  135. package/dist/src/tool-call-log.js +24 -0
  136. package/dist/src/tools/FindSymbolTool.js +14 -5
  137. package/dist/src/tools/GetCallersTool.d.ts +3 -1
  138. package/dist/src/tools/GetCallersTool.js +16 -6
  139. package/dist/src/tools/GetDependenciesTool.d.ts +3 -1
  140. package/dist/src/tools/GetDependenciesTool.js +15 -8
  141. package/dist/src/tools/GetDependentsTool.d.ts +3 -1
  142. package/dist/src/tools/GetDependentsTool.js +31 -16
  143. package/dist/src/tools/ListFilesTool.d.ts +3 -1
  144. package/dist/src/tools/ListFilesTool.js +14 -12
  145. package/dist/src/tools/ListProjectsTool.js +5 -1
  146. package/dist/src/tools/ReadFileRangeTool.d.ts +3 -1
  147. package/dist/src/tools/ReadFileRangeTool.js +12 -11
  148. package/dist/src/tools/ReadFileTool.d.ts +3 -1
  149. package/dist/src/tools/ReadFileTool.js +12 -10
  150. package/dist/src/tools/SearchCodebaseTool.js +7 -2
  151. package/dist/src/tools/format-helpers.d.ts +10 -1
  152. package/dist/src/tools/format-helpers.js +145 -8
  153. package/dist/src/web/dashboard.d.ts +0 -8
  154. package/dist/src/web/dashboard.js +7 -10
  155. package/package.json +10 -9
  156. package/dist/src/services/AstChunker.d.ts +0 -42
  157. package/dist/src/services/AstChunker.js +0 -158
  158. package/docs/web-dashboard.md +0 -206
@@ -37,7 +37,15 @@ exports.AstParserService = void 0;
37
37
  const path = __importStar(require("path"));
38
38
  const fs = __importStar(require("fs"));
39
39
  const ts_morph_1 = require("ts-morph");
40
+ const DeclarationTreeBuilder_1 = require("../declarations/DeclarationTreeBuilder");
41
+ const DeclarationTree_1 = require("../declarations/DeclarationTree");
40
42
  const LoggerService_1 = require("./LoggerService");
43
+ const GRAPHQL_MODULE = '@nestjs/graphql';
44
+ const GRAPHQL_TYPE_DECORATORS = new Set(['ObjectType', 'InputType', 'InterfaceType']);
45
+ const GRAPHQL_OPERATION_KIND = {
46
+ Query: 'graphql_query', Mutation: 'graphql_mutation', Subscription: 'graphql_subscription',
47
+ };
48
+ const GRAPHQL_NAME_RE = /^[_A-Za-z][_0-9A-Za-z]*$/;
41
49
  const FALLBACK_OPTS = {
42
50
  skipAddingFilesFromTsConfig: true,
43
51
  skipFileDependencyResolution: false,
@@ -47,11 +55,13 @@ const MORPH_CACHE_MAX = 10;
47
55
  const VALID_SEGMENT = /^#?[\w$]+$/;
48
56
  class AstParserService {
49
57
  logger;
58
+ deps;
50
59
  morphProjects = new Map();
51
- constructor(logger = LoggerService_1.LoggerService.silent()) {
60
+ declarationBuilder = new DeclarationTreeBuilder_1.DeclarationTreeBuilder();
61
+ constructor(logger = LoggerService_1.LoggerService.silent(), deps = {}) {
52
62
  this.logger = logger;
63
+ this.deps = deps;
53
64
  }
54
- // ── public interface ────────────────────────────────────────────────────────
55
65
  async analyzeProject(filePaths, serviceName, projectRoot = process.cwd(), opts = {}) {
56
66
  const t0 = Date.now();
57
67
  const skipDependencyResolution = opts.skipDependencyResolution ?? false;
@@ -61,22 +71,21 @@ class AstParserService {
61
71
  const morphProject = this.createMorphProject(projectRoot, skipDependencyResolution, rootTsConfigPath);
62
72
  for (const filePath of filePaths) {
63
73
  if (!/\.(ts|tsx|js|jsx)$/.test(filePath) || /\.d\.ts$/.test(filePath)) {
64
- analysisMap.set(filePath, { edges: [], symbols: [], calls: [], members: [] });
74
+ analysisMap.set(filePath, { edges: [], symbols: [], calls: [], members: [], graphqlNames: [] });
65
75
  continue;
66
76
  }
67
77
  try {
68
78
  morphProject.addSourceFileAtPath(filePath);
69
79
  }
70
80
  catch (err) {
71
- this.logger.warn('AstParser: failed to add source file', { filePath, reason: err.message });
72
- analysisMap.set(filePath, { edges: [], symbols: [], calls: [], members: [] });
81
+ this.logger.warn('AstParser: failed to add source file', { filePath, err });
82
+ analysisMap.set(filePath, { edges: [], symbols: [], calls: [], members: [], graphqlNames: [] });
73
83
  }
74
84
  }
75
85
  for (const sourceFile of morphProject.getSourceFiles()) {
76
86
  const filePath = sourceFile.getFilePath();
77
87
  analysisMap.set(filePath, this.analyzeSourceFile(sourceFile, filePath, serviceName));
78
88
  }
79
- // Release source file memory before returning
80
89
  for (const sourceFile of morphProject.getSourceFiles()) {
81
90
  morphProject.removeSourceFile(sourceFile);
82
91
  }
@@ -101,7 +110,7 @@ class AstParserService {
101
110
  this.logger.debug('AstParser: analyzeFile start', { filePath, serviceName, projectRoot });
102
111
  if (!/\.(ts|tsx|js|jsx)$/.test(filePath) || /\.d\.ts$/.test(filePath)) {
103
112
  this.logger.debug('AstParser: analyzeFile skip (non-TS)', { filePath, ms: Date.now() - t0 });
104
- return { edges: [], symbols: [], calls: [], members: [] };
113
+ return { edges: [], symbols: [], calls: [], members: [], graphqlNames: [] };
105
114
  }
106
115
  const morphProject = this.getMorphProject(projectRoot);
107
116
  const existing = morphProject.getSourceFile(filePath);
@@ -112,8 +121,8 @@ class AstParserService {
112
121
  sourceFile = morphProject.addSourceFileAtPath(filePath);
113
122
  }
114
123
  catch (err) {
115
- this.logger.warn('AstParser: analyzeFile failed to add source file', { filePath, reason: err.message, ms: Date.now() - t0 });
116
- return { edges: [], symbols: [], calls: [], members: [] };
124
+ this.logger.warn('AstParser: analyzeFile failed to add source file', { filePath, err, ms: Date.now() - t0 });
125
+ return { edges: [], symbols: [], calls: [], members: [], graphqlNames: [] };
117
126
  }
118
127
  const result = this.analyzeSourceFile(sourceFile, filePath, serviceName);
119
128
  morphProject.removeSourceFile(sourceFile);
@@ -122,7 +131,6 @@ class AstParserService {
122
131
  });
123
132
  return result;
124
133
  }
125
- // Kept for test compatibility (extract-import-edges.test.ts).
126
134
  async extractImportEdges(filePath, serviceName, projectRoot) {
127
135
  const { edges } = await this.analyzeFile(filePath, serviceName, projectRoot);
128
136
  return edges;
@@ -132,41 +140,37 @@ class AstParserService {
132
140
  const edges = this.extractEdges(sourceFile, filePath, serviceName);
133
141
  const symbolCandidates = this.extractSymbols(sourceFile, filePath, serviceName);
134
142
  const symbols = symbolCandidates.map(c => c.entry);
135
- // Member extraction (classes anywhere, class/interface/object-literal
136
- // members, namespace-level declarations) is isolated in its own try: a
137
- // bug there must never change a file's top-level symbols, chunks or
138
- // embedding prompt (see the `symbol-index-members` retrieval-invariance
139
- // requirement).
140
143
  let memberCandidates = [];
141
144
  try {
142
145
  memberCandidates = this.extractMembers(sourceFile, filePath, serviceName);
143
146
  }
144
147
  catch (err) {
145
- this.logger.warn('AstParser: member extraction failed', { filePath, reason: err.message });
148
+ this.logger.warn('AstParser: member extraction failed', { filePath, err });
146
149
  memberCandidates = [];
147
150
  }
148
151
  const members = memberCandidates.map(c => c.entry);
149
152
  const calls = this.extractCalls(sourceFile, filePath, serviceName);
150
- const ownerRanges = this.buildOwnerRanges([...symbolCandidates, ...memberCandidates]);
151
- return { edges, symbols, members, calls, ownerRanges };
153
+ const declarations = this.buildDeclarations(sourceFile, filePath, symbolCandidates, memberCandidates);
154
+ let graphqlNames = [];
155
+ try {
156
+ graphqlNames = this.extractGraphqlNames(sourceFile, filePath, serviceName, [...symbolCandidates, ...memberCandidates]);
157
+ }
158
+ catch (err) {
159
+ this.logger.warn('AstParser: GraphQL extraction failed', { filePath, err });
160
+ graphqlNames = [];
161
+ }
162
+ return { edges, symbols, members, calls, declarations, graphqlNames };
152
163
  }
153
164
  catch (err) {
154
- this.logger.warn('AstParser: failed to analyze file', { filePath, reason: err.message });
155
- return { edges: [], symbols: [], calls: [], members: [] };
165
+ this.logger.warn('AstParser: failed to analyze file', { filePath, err });
166
+ return { edges: [], symbols: [], calls: [], members: [], graphqlNames: [] };
156
167
  }
157
168
  }
158
- // ── private: MorphProject cache (LRU, max MORPH_CACHE_MAX entries) ──────────
159
169
  createMorphProject(projectRoot, skipFileDependencyResolution = false, rootTsConfigPath) {
160
170
  const t0 = Date.now();
161
- // Determine which tsconfig to use:
162
- // 1. Project-local tsconfig.json
163
- // 2. Root tsconfig (e.g. tsconfig.base.json for Nx monorepos)
164
- // 3. Fallback options
165
171
  const localTsconfigPath = path.join(projectRoot, 'tsconfig.json');
166
172
  const hasLocalTsconfig = fs.existsSync(localTsconfigPath);
167
173
  const hasRootTsconfig = rootTsConfigPath ? fs.existsSync(rootTsConfigPath) : false;
168
- // For monorepos: prefer the root tsconfig.base.json which contains
169
- // workspace-wide path aliases (e.g. @scope/* → libs/*/src)
170
174
  const effectiveTsconfig = hasRootTsconfig ? rootTsConfigPath : (hasLocalTsconfig ? localTsconfigPath : null);
171
175
  this.logger.debug('AstParser: tsconfig lookup', {
172
176
  projectRoot,
@@ -198,7 +202,7 @@ class AstParserService {
198
202
  catch (err) {
199
203
  this.logger.warn('AstParser: tsconfig load failed, using fallback', {
200
204
  tsconfigPath: effectiveTsconfig ?? localTsconfigPath,
201
- reason: err.message,
205
+ err,
202
206
  ms: Date.now() - t0,
203
207
  });
204
208
  project = new ts_morph_1.Project(FALLBACK_OPTS);
@@ -207,7 +211,6 @@ class AstParserService {
207
211
  }
208
212
  getMorphProject(projectRoot) {
209
213
  if (this.morphProjects.has(projectRoot)) {
210
- // Move to end = most recently used
211
214
  const cached = this.morphProjects.get(projectRoot);
212
215
  this.morphProjects.delete(projectRoot);
213
216
  this.morphProjects.set(projectRoot, cached);
@@ -215,7 +218,6 @@ class AstParserService {
215
218
  return cached;
216
219
  }
217
220
  if (this.morphProjects.size >= MORPH_CACHE_MAX) {
218
- // Evict least recently used (first entry in insertion-order Map)
219
221
  const lruKey = this.morphProjects.keys().next().value;
220
222
  this.morphProjects.delete(lruKey);
221
223
  this.logger.debug('AstParser: evicted LRU project', { lruKey });
@@ -225,13 +227,20 @@ class AstParserService {
225
227
  this.morphProjects.set(projectRoot, project);
226
228
  return project;
227
229
  }
228
- // ── private: edge extraction (imports + re-exports) ────────────────────────
229
230
  extractEdges(sourceFile, filePath, serviceName) {
230
231
  const edges = [];
231
232
  const usageMap = this.buildUsageMap(sourceFile);
232
233
  for (const decl of sourceFile.getImportDeclarations()) {
234
+ const namedImports = decl.getNamedImports();
235
+ const defaultImport = decl.getDefaultImport();
236
+ const namespaceImport = decl.getNamespaceImport();
237
+ const hasClause = Boolean(defaultImport || namespaceImport || namedImports.length > 0);
233
238
  const resolved = decl.getModuleSpecifierSourceFile();
234
- if (!resolved) {
239
+ let toFile = resolved ? resolved.getFilePath() : null;
240
+ if (!toFile && !hasClause) {
241
+ toFile = this.resolveSideEffectModuleSpecifier(decl, sourceFile, filePath);
242
+ }
243
+ if (!toFile) {
235
244
  const specifier = decl.getModuleSpecifierValue();
236
245
  const isCodeImport = !/\.(json|css|scss|less|svg|png|jpg|gif|ico|woff2?|ttf|eot)(\?.*)?$/.test(specifier);
237
246
  if (specifier.startsWith('.') && isCodeImport) {
@@ -239,34 +248,37 @@ class AstParserService {
239
248
  }
240
249
  continue;
241
250
  }
242
- const toFile = resolved.getFilePath();
243
251
  if (toFile.includes('/node_modules/'))
244
252
  continue;
245
- const namedImports = decl.getNamedImports();
246
- const defaultImport = decl.getDefaultImport();
247
- const namespaceImport = decl.getNamespaceImport();
248
- if (namedImports.length > 0) {
249
- for (const named of namedImports) {
250
- const localName = named.getAliasNode()?.getText() ?? named.getName();
251
- const fromSymbol = this.resolveFromSymbol(localName, usageMap);
252
- edges.push({
253
- project: serviceName, fromFile: filePath, fromSymbol,
254
- toFile, toSymbol: named.getName(), depType: 'import',
255
- });
256
- }
257
- }
258
- else if (defaultImport) {
253
+ if (!hasClause) {
259
254
  edges.push({
260
255
  project: serviceName, fromFile: filePath, fromSymbol: null,
256
+ toFile, toSymbol: null, depType: 'import-side-effect',
257
+ });
258
+ continue;
259
+ }
260
+ if (defaultImport) {
261
+ const fromSymbol = this.resolveFromSymbol(defaultImport.getText(), usageMap);
262
+ edges.push({
263
+ project: serviceName, fromFile: filePath, fromSymbol,
261
264
  toFile, toSymbol: null, depType: 'import-default',
262
265
  });
263
266
  }
264
- else if (namespaceImport) {
267
+ if (namespaceImport) {
268
+ const fromSymbol = this.resolveFromSymbol(namespaceImport.getText(), usageMap);
265
269
  edges.push({
266
- project: serviceName, fromFile: filePath, fromSymbol: null,
270
+ project: serviceName, fromFile: filePath, fromSymbol,
267
271
  toFile, toSymbol: null, depType: 'import-namespace',
268
272
  });
269
273
  }
274
+ for (const named of namedImports) {
275
+ const localName = named.getAliasNode()?.getText() ?? named.getName();
276
+ const fromSymbol = this.resolveFromSymbol(localName, usageMap);
277
+ edges.push({
278
+ project: serviceName, fromFile: filePath, fromSymbol,
279
+ toFile, toSymbol: named.getName(), depType: 'import',
280
+ });
281
+ }
270
282
  }
271
283
  for (const decl of sourceFile.getExportDeclarations()) {
272
284
  const resolved = decl.getModuleSpecifierSourceFile();
@@ -293,6 +305,15 @@ class AstParserService {
293
305
  }
294
306
  return edges;
295
307
  }
308
+ resolveSideEffectModuleSpecifier(decl, sourceFile, filePath) {
309
+ const specifier = decl.getModuleSpecifierValue();
310
+ const compilerOptions = sourceFile.getProject().getCompilerOptions();
311
+ const result = ts_morph_1.ts.resolveModuleName(specifier, filePath, compilerOptions, ts_morph_1.ts.sys);
312
+ const resolvedFileName = result.resolvedModule?.resolvedFileName;
313
+ if (!resolvedFileName)
314
+ return null;
315
+ return sourceFile.getProject().getSourceFile(resolvedFileName)?.getFilePath() ?? null;
316
+ }
296
317
  resolveFromSymbol(localName, usageMap) {
297
318
  const users = usageMap.get(localName);
298
319
  return users?.length === 1 ? users[0] : null;
@@ -302,7 +323,6 @@ class AstParserService {
302
323
  const containers = [
303
324
  ...sourceFile.getClasses().flatMap(n => n.getName() ? [{ name: n.getName(), node: n }] : []),
304
325
  ...sourceFile.getFunctions().flatMap(n => n.getName() ? [{ name: n.getName(), node: n }] : []),
305
- // include top-level variable declarations so arrow function exports are tracked for fromSymbol
306
326
  ...sourceFile.getVariableStatements()
307
327
  .flatMap(stmt => stmt.getDeclarations())
308
328
  .flatMap(n => {
@@ -325,20 +345,15 @@ class AstParserService {
325
345
  }
326
346
  return map;
327
347
  }
328
- // ── private: position / signature helpers ───────────────────────────────────
329
348
  getPos(sourceFile, node) {
330
349
  const { line, character } = sourceFile.compilerNode
331
350
  .getLineAndCharacterOfPosition(node.getStart());
332
351
  return { line: line + 1, col: character + 1 };
333
352
  }
334
- /** Trimmed text of the source line containing `node`'s start — the signature
335
- * rule for every kind (name node for members, the position node for
336
- * constructors/anonymous default classes). */
337
353
  lineTextAt(sourceLines, sourceFile, node) {
338
354
  const { line } = this.getPos(sourceFile, node);
339
355
  return (sourceLines[line - 1] ?? node.getText().split('\n')[0]).trim();
340
356
  }
341
- // ── private: top-level symbol extraction (feeds the chunker + prompt) ──────
342
357
  extractSymbols(sourceFile, filePath, serviceName) {
343
358
  const candidates = [];
344
359
  const sourceLines = sourceFile.getFullText().split('\n');
@@ -406,8 +421,6 @@ class AstParserService {
406
421
  const nameNode = decl.getNameNode();
407
422
  const { line, col } = this.getPos(sourceFile, nameNode);
408
423
  candidates.push({
409
- // Owner ranges use the declarator's own span, not the statement's —
410
- // each declarator of `const a = 1, b = () => {…}` owns only its lines.
411
424
  declNode: decl,
412
425
  entry: {
413
426
  project: serviceName, filePath, name: decl.getName(), kind: 'variable',
@@ -420,7 +433,6 @@ class AstParserService {
420
433
  }
421
434
  return candidates;
422
435
  }
423
- // ── private: member extraction (classes anywhere, members, namespace body) ─
424
436
  extractMembers(sourceFile, filePath, serviceName) {
425
437
  const candidates = [];
426
438
  const sourceLines = sourceFile.getFullText().split('\n');
@@ -438,14 +450,10 @@ class AstParserService {
438
450
  },
439
451
  });
440
452
  };
441
- // Classes anywhere in the file — nested, expression, anonymous default.
442
453
  for (const cls of [
443
454
  ...sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.ClassDeclaration),
444
455
  ...sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.ClassExpression),
445
456
  ]) {
446
- // Named top-level classes are already handled by extractSymbols; an
447
- // anonymous top-level class (`export default class {}`) is not — it
448
- // must still be collected here.
449
457
  if (ts_morph_1.Node.isClassDeclaration(cls) && cls.getName() && ts_morph_1.Node.isSourceFile(cls.getParent()))
450
458
  continue;
451
459
  const described = this.describeIndexedNode(cls);
@@ -454,7 +462,6 @@ class AstParserService {
454
462
  const posNode = (ts_morph_1.Node.isClassDeclaration(cls) ? cls.getNameNode() : undefined) ?? cls;
455
463
  push(cls, posNode, described.name, 'class', described.container, false, true);
456
464
  }
457
- // Class / object-literal methods, accessors, constructors, function properties.
458
465
  for (const m of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.MethodDeclaration)) {
459
466
  const described = this.describeIndexedNode(m);
460
467
  if (!described)
@@ -492,7 +499,6 @@ class AstParserService {
492
499
  continue;
493
500
  push(p, p.getNameNode(), described.name, 'function_property', described.container, false, true);
494
501
  }
495
- // Interface / type-literal method and function-typed property signatures.
496
502
  for (const s of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.MethodSignature)) {
497
503
  const described = this.describeIndexedNode(s);
498
504
  if (!described)
@@ -505,7 +511,6 @@ class AstParserService {
505
511
  continue;
506
512
  push(s, s.getNameNode(), described.name, 'method_signature', described.container, false, false);
507
513
  }
508
- // Declarations directly in a namespace body.
509
514
  for (const fn of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.FunctionDeclaration)) {
510
515
  if (!ts_morph_1.Node.isModuleBlock(fn.getParent()))
511
516
  continue;
@@ -545,21 +550,327 @@ class AstParserService {
545
550
  }
546
551
  return candidates;
547
552
  }
548
- /** Chunk-ownership ranges from every indexed declaration (top-level symbols +
549
- * members) except `method_signature`. Each range spans the declaration node's
550
- * own first-to-last line — decorator-inclusive, comment-exclusive, and (for a
551
- * variable) the declarator alone, never the whole statement. */
552
- buildOwnerRanges(candidates) {
553
- return candidates
554
- .filter(c => c.entry.kind !== 'method_signature')
555
- .map(c => ({
556
- name: c.entry.name,
557
- container: c.entry.container,
558
- line: c.declNode.getStartLineNumber(),
559
- endLine: c.declNode.getEndLineNumber(),
560
- }));
561
- }
562
- // ── private: shared container resolution ────────────────────────────────────
553
+ buildDeclarations(sourceFile, filePath, symbolCandidates, memberCandidates) {
554
+ try {
555
+ return this.declarationBuilder.build(sourceFile, [...symbolCandidates, ...memberCandidates]);
556
+ }
557
+ catch (err) {
558
+ this.logger.warn('AstParser: declaration tree build failed', { filePath, err });
559
+ try {
560
+ return this.declarationBuilder.flat(symbolCandidates);
561
+ }
562
+ catch {
563
+ return DeclarationTree_1.DeclarationTree.EMPTY;
564
+ }
565
+ }
566
+ }
567
+ buildGraphqlImportMap(sourceFile) {
568
+ const map = new Map();
569
+ for (const decl of sourceFile.getImportDeclarations()) {
570
+ if (decl.getModuleSpecifierValue() !== GRAPHQL_MODULE)
571
+ continue;
572
+ for (const named of decl.getNamedImports()) {
573
+ const exportedName = named.getName();
574
+ const localName = named.getAliasNode()?.getText() ?? exportedName;
575
+ map.set(localName, exportedName);
576
+ }
577
+ }
578
+ return map;
579
+ }
580
+ static decoratorIdentifier(dec) {
581
+ const expr = dec.getExpression();
582
+ if (ts_morph_1.Node.isCallExpression(expr)) {
583
+ const callee = expr.getExpression();
584
+ return ts_morph_1.Node.isIdentifier(callee) ? callee : undefined;
585
+ }
586
+ return ts_morph_1.Node.isIdentifier(expr) ? expr : undefined;
587
+ }
588
+ recognizedDecorator(dec, importMap) {
589
+ const idNode = AstParserService.decoratorIdentifier(dec);
590
+ if (!idNode)
591
+ return null;
592
+ const exported = importMap.get(idNode.getText());
593
+ if (!exported)
594
+ return null;
595
+ const expr = dec.getExpression();
596
+ const args = ts_morph_1.Node.isCallExpression(expr) ? expr.getArguments() : [];
597
+ return { name: exported, args };
598
+ }
599
+ static literalStringValue(node) {
600
+ if (ts_morph_1.Node.isStringLiteral(node) || ts_morph_1.Node.isNoSubstitutionTemplateLiteral(node))
601
+ return node.getLiteralValue();
602
+ return null;
603
+ }
604
+ static checkGraphqlGrammar(name) {
605
+ return GRAPHQL_NAME_RE.test(name) ? { ok: true, name } : { ok: false, reason: 'invalid-name', value: name };
606
+ }
607
+ static isAbstractGraphqlType(args) {
608
+ return args.some((arg) => {
609
+ if (!ts_morph_1.Node.isObjectLiteralExpression(arg))
610
+ return false;
611
+ const prop = arg.getProperties().find((p) => {
612
+ if (!ts_morph_1.Node.isPropertyAssignment(p))
613
+ return false;
614
+ const nameNode = p.getNameNode();
615
+ if (ts_morph_1.Node.isIdentifier(nameNode))
616
+ return nameNode.getText() === 'isAbstract';
617
+ if (ts_morph_1.Node.isStringLiteral(nameNode))
618
+ return nameNode.getLiteralValue() === 'isAbstract';
619
+ return false;
620
+ });
621
+ const init = prop?.getInitializer();
622
+ return init !== undefined && ts_morph_1.Node.isTrueLiteral(init);
623
+ });
624
+ }
625
+ static findNameProperty(obj) {
626
+ return obj.getProperties().find((p) => {
627
+ if (!ts_morph_1.Node.isPropertyAssignment(p) && !ts_morph_1.Node.isShorthandPropertyAssignment(p))
628
+ return false;
629
+ const nameNode = p.getNameNode();
630
+ if (ts_morph_1.Node.isIdentifier(nameNode))
631
+ return nameNode.getText() === 'name';
632
+ if (ts_morph_1.Node.isStringLiteral(nameNode))
633
+ return nameNode.getLiteralValue() === 'name';
634
+ return false;
635
+ });
636
+ }
637
+ static peekThroughWrapper(node) {
638
+ let current = node;
639
+ let wrapped = false;
640
+ while (true) {
641
+ if (ts_morph_1.Node.isParenthesizedExpression(current) || ts_morph_1.Node.isAsExpression(current) || ts_morph_1.Node.isSatisfiesExpression(current) || ts_morph_1.Node.isNonNullExpression(current)) {
642
+ current = current.getExpression();
643
+ wrapped = true;
644
+ continue;
645
+ }
646
+ break;
647
+ }
648
+ return { inner: current, wrapped };
649
+ }
650
+ deriveGraphqlName(args, allowOptionsName, fallbackName) {
651
+ if (args.length > 0) {
652
+ const first = args[0];
653
+ const { inner, wrapped } = AstParserService.peekThroughWrapper(first);
654
+ const literal = AstParserService.literalStringValue(inner);
655
+ if (literal !== null) {
656
+ if (wrapped)
657
+ return { ok: false, reason: 'non-literal-name' };
658
+ return AstParserService.checkGraphqlGrammar(literal);
659
+ }
660
+ const isTypeFnOrOptions = ts_morph_1.Node.isArrowFunction(inner) || ts_morph_1.Node.isFunctionExpression(inner) || ts_morph_1.Node.isObjectLiteralExpression(inner);
661
+ if (!isTypeFnOrOptions)
662
+ return { ok: false, reason: 'non-literal-name' };
663
+ }
664
+ if (allowOptionsName) {
665
+ for (const arg of args) {
666
+ const { inner, wrapped } = AstParserService.peekThroughWrapper(arg);
667
+ if (!ts_morph_1.Node.isObjectLiteralExpression(inner))
668
+ continue;
669
+ if (wrapped)
670
+ return { ok: false, reason: 'non-literal-name' };
671
+ const nameProp = AstParserService.findNameProperty(inner);
672
+ if (!nameProp)
673
+ continue;
674
+ if (ts_morph_1.Node.isShorthandPropertyAssignment(nameProp))
675
+ return { ok: false, reason: 'non-literal-name' };
676
+ const init = nameProp.getInitializer();
677
+ const value = init ? AstParserService.literalStringValue(init) : null;
678
+ if (value !== null)
679
+ return AstParserService.checkGraphqlGrammar(value);
680
+ return { ok: false, reason: 'non-literal-name' };
681
+ }
682
+ }
683
+ return AstParserService.checkGraphqlGrammar(fallbackName);
684
+ }
685
+ logGraphqlSkip(filePath, declaration, decorator, reason, value) {
686
+ const fields = { filePath, declaration, decorator, reason };
687
+ if (value !== undefined)
688
+ fields.value = value;
689
+ this.logger.debug('AstParser: graphql row skipped', fields);
690
+ }
691
+ declarationNameFor(node, fallback) {
692
+ const described = this.describeIndexedNode(node);
693
+ return described ? this.qualifiedName(described) : fallback;
694
+ }
695
+ static extractResolverIdentifier(argNode) {
696
+ let target = argNode;
697
+ if (ts_morph_1.Node.isArrowFunction(target)) {
698
+ const body = target.getBody();
699
+ if (ts_morph_1.Node.isBlock(body))
700
+ return null;
701
+ target = body;
702
+ }
703
+ if (ts_morph_1.Node.isIdentifier(target))
704
+ return target;
705
+ if (ts_morph_1.Node.isArrayLiteralExpression(target)) {
706
+ const elements = target.getElements();
707
+ if (elements.length === 1 && ts_morph_1.Node.isIdentifier(elements[0]))
708
+ return elements[0];
709
+ }
710
+ return null;
711
+ }
712
+ defaultResolveDeclaration(identifier) {
713
+ const sym = identifier.getSymbol();
714
+ if (!sym)
715
+ return undefined;
716
+ const firstDecl = sym.getDeclarations()?.[0];
717
+ const resolved = firstDecl && ts_morph_1.Node.isImportSpecifier(firstDecl) ? (sym.getAliasedSymbol() ?? sym) : sym;
718
+ return resolved.getDeclarations()?.[0];
719
+ }
720
+ classGraphqlTypeName(declClass) {
721
+ const declImportMap = this.buildGraphqlImportMap(declClass.getSourceFile());
722
+ const decorators = declClass.getDecorators();
723
+ const hasAnyTypeSpelledDecorator = decorators.some((d) => {
724
+ const idNode = AstParserService.decoratorIdentifier(d);
725
+ const text = idNode?.getText();
726
+ return text === 'ObjectType' || text === 'InterfaceType';
727
+ });
728
+ const recognized = decorators
729
+ .map(d => this.recognizedDecorator(d, declImportMap))
730
+ .find(r => r?.name === 'ObjectType')
731
+ ?? decorators.map(d => this.recognizedDecorator(d, declImportMap)).find(r => r?.name === 'InterfaceType');
732
+ if (recognized) {
733
+ const fallbackName = declClass.getName() ?? (declClass.isDefaultExport() ? 'default' : '');
734
+ const nameResult = this.deriveGraphqlName(recognized.args, false, fallbackName);
735
+ return nameResult.ok ? { ok: true, name: nameResult.name } : { ok: false };
736
+ }
737
+ if (hasAnyTypeSpelledDecorator)
738
+ return { ok: false };
739
+ const name = declClass.getName();
740
+ if (!name)
741
+ return { ok: false };
742
+ return GRAPHQL_NAME_RE.test(name) ? { ok: true, name } : { ok: false, value: name };
743
+ }
744
+ resolveResolverParentType(cls, importMap) {
745
+ const resolverDec = cls.getDecorators()
746
+ .map(d => this.recognizedDecorator(d, importMap))
747
+ .find(r => r?.name === 'Resolver');
748
+ if (!resolverDec || resolverDec.args.length === 0)
749
+ return { ok: false };
750
+ const argNode = resolverDec.args[0];
751
+ const literal = AstParserService.literalStringValue(argNode);
752
+ if (literal !== null) {
753
+ return GRAPHQL_NAME_RE.test(literal) ? { ok: true, name: literal } : { ok: false, value: literal };
754
+ }
755
+ const identifier = AstParserService.extractResolverIdentifier(argNode);
756
+ if (!identifier)
757
+ return { ok: false };
758
+ const resolve = this.deps.resolveDeclaration ?? this.defaultResolveDeclaration.bind(this);
759
+ const declaration = resolve(identifier);
760
+ if (!declaration || !ts_morph_1.Node.isClassDeclaration(declaration))
761
+ return { ok: false };
762
+ return this.classGraphqlTypeName(declaration);
763
+ }
764
+ extractClassTypeRows(cls, importMap, ownByDeclNode, filePath, serviceName, entries) {
765
+ const own = ownByDeclNode.get(cls);
766
+ const declaration = this.declarationNameFor(cls, own?.entry.name ?? cls.getName() ?? 'default');
767
+ const seenNames = new Set();
768
+ for (const dec of cls.getDecorators()) {
769
+ const rec = this.recognizedDecorator(dec, importMap);
770
+ if (!rec || !GRAPHQL_TYPE_DECORATORS.has(rec.name))
771
+ continue;
772
+ if (AstParserService.isAbstractGraphqlType(rec.args)) {
773
+ this.logGraphqlSkip(filePath, declaration, rec.name, 'abstract-type');
774
+ continue;
775
+ }
776
+ if (!own) {
777
+ this.logGraphqlSkip(filePath, declaration, rec.name, 'no-symbol-row');
778
+ continue;
779
+ }
780
+ const nameResult = this.deriveGraphqlName(rec.args, false, own.entry.name);
781
+ if (!nameResult.ok) {
782
+ this.logGraphqlSkip(filePath, declaration, rec.name, nameResult.reason, nameResult.reason === 'invalid-name' ? nameResult.value : undefined);
783
+ continue;
784
+ }
785
+ if (seenNames.has(nameResult.name)) {
786
+ this.logGraphqlSkip(filePath, declaration, rec.name, 'duplicate', own.entry.name);
787
+ continue;
788
+ }
789
+ seenNames.add(nameResult.name);
790
+ entries.push({
791
+ project: serviceName, filePath, name: nameResult.name, kind: 'graphql_type',
792
+ signature: own.entry.signature, line: own.entry.line, endLine: own.entry.endLine, col: own.entry.col,
793
+ container: null, isStatic: false, target: declaration,
794
+ });
795
+ }
796
+ }
797
+ extractResolveFieldRow(method, rec, own, declaration, importMap, filePath, serviceName, entries) {
798
+ if (!own) {
799
+ this.logGraphqlSkip(filePath, declaration, 'ResolveField', 'no-symbol-row');
800
+ return;
801
+ }
802
+ const cls = method.getParent();
803
+ if (!cls || !ts_morph_1.Node.isClassDeclaration(cls)) {
804
+ this.logGraphqlSkip(filePath, declaration, 'ResolveField', 'unresolvable-parent');
805
+ return;
806
+ }
807
+ const parent = this.resolveResolverParentType(cls, importMap);
808
+ if (!parent.ok) {
809
+ this.logGraphqlSkip(filePath, declaration, 'ResolveField', 'unresolvable-parent', parent.value);
810
+ return;
811
+ }
812
+ const nameResult = this.deriveGraphqlName(rec.args, true, own.entry.name);
813
+ if (!nameResult.ok) {
814
+ this.logGraphqlSkip(filePath, declaration, 'ResolveField', nameResult.reason, nameResult.reason === 'invalid-name' ? nameResult.value : undefined);
815
+ return;
816
+ }
817
+ entries.push({
818
+ project: serviceName, filePath, name: nameResult.name, kind: 'graphql_field',
819
+ signature: own.entry.signature, line: own.entry.line, endLine: own.entry.endLine, col: own.entry.col,
820
+ container: parent.name, isStatic: own.entry.isStatic, target: declaration,
821
+ });
822
+ }
823
+ extractMethodOperationRows(method, importMap, ownByDeclNode, filePath, serviceName, entries) {
824
+ const own = ownByDeclNode.get(method);
825
+ const declaration = this.declarationNameFor(method, own?.entry.name ?? AstParserService.memberName(method.getNameNode()) ?? '<anonymous>');
826
+ for (const dec of method.getDecorators()) {
827
+ const rec = this.recognizedDecorator(dec, importMap);
828
+ if (!rec)
829
+ continue;
830
+ if (rec.name === 'ResolveField') {
831
+ try {
832
+ this.extractResolveFieldRow(method, rec, own, declaration, importMap, filePath, serviceName, entries);
833
+ }
834
+ catch (err) {
835
+ this.logGraphqlSkip(filePath, declaration, 'ResolveField', 'unresolvable-parent');
836
+ }
837
+ continue;
838
+ }
839
+ const kind = GRAPHQL_OPERATION_KIND[rec.name];
840
+ if (!kind)
841
+ continue;
842
+ if (!own) {
843
+ this.logGraphqlSkip(filePath, declaration, rec.name, 'no-symbol-row');
844
+ continue;
845
+ }
846
+ const nameResult = this.deriveGraphqlName(rec.args, true, own.entry.name);
847
+ if (!nameResult.ok) {
848
+ this.logGraphqlSkip(filePath, declaration, rec.name, nameResult.reason, nameResult.reason === 'invalid-name' ? nameResult.value : undefined);
849
+ continue;
850
+ }
851
+ entries.push({
852
+ project: serviceName, filePath, name: nameResult.name, kind,
853
+ signature: own.entry.signature, line: own.entry.line, endLine: own.entry.endLine, col: own.entry.col,
854
+ container: rec.name, isStatic: own.entry.isStatic, target: declaration,
855
+ });
856
+ }
857
+ }
858
+ extractGraphqlNames(sourceFile, filePath, serviceName, ownRows) {
859
+ const importMap = this.buildGraphqlImportMap(sourceFile);
860
+ if (importMap.size === 0)
861
+ return [];
862
+ const ownByDeclNode = new Map();
863
+ for (const c of ownRows)
864
+ ownByDeclNode.set(c.declNode, c);
865
+ const entries = [];
866
+ for (const cls of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.ClassDeclaration)) {
867
+ this.extractClassTypeRows(cls, importMap, ownByDeclNode, filePath, serviceName, entries);
868
+ }
869
+ for (const method of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.MethodDeclaration)) {
870
+ this.extractMethodOperationRows(method, importMap, ownByDeclNode, filePath, serviceName, entries);
871
+ }
872
+ return entries;
873
+ }
563
874
  static isValidSegment(s) {
564
875
  return VALID_SEGMENT.test(s);
565
876
  }
@@ -593,7 +904,6 @@ class AstParserService {
593
904
  return current;
594
905
  }
595
906
  isModuleOrNamespaceLevelVariable(varDecl) {
596
- // VariableDeclaration -> VariableDeclarationList -> VariableStatement -> (SourceFile | ModuleBlock)
597
907
  const stmt = varDecl.getParent()?.getParent();
598
908
  const stmtParent = stmt?.getParent();
599
909
  return ts_morph_1.Node.isSourceFile(stmtParent) || ts_morph_1.Node.isModuleBlock(stmtParent);
@@ -626,9 +936,6 @@ class AstParserService {
626
936
  isTopLevelOrNamespaceDirect(node) {
627
937
  return ts_morph_1.Node.isSourceFile(node) || ts_morph_1.Node.isModuleBlock(node);
628
938
  }
629
- /** One step of the ancestor walk used by `containerSegments`: does `node`
630
- * contribute a named segment, pass through transparently, or disqualify
631
- * (exclude) everything nested inside it? */
632
939
  walkStep(node) {
633
940
  if (ts_morph_1.Node.isModuleDeclaration(node)) {
634
941
  if (node.compilerNode.flags & ts_morph_1.NodeFlags.GlobalAugmentation)
@@ -681,10 +988,6 @@ class AstParserService {
681
988
  }
682
989
  return { kind: 'transparent' };
683
990
  }
684
- /** The dotted container path of `node`'s enclosing named scopes, outermost
685
- * first, or null when any contributing ancestor is invalid (a computed /
686
- * string-literal segment, a string-named module, `declare global`, or an
687
- * unrooted class expression / object literal). */
688
991
  containerSegments(node) {
689
992
  const segments = [];
690
993
  let current = node.getParent();
@@ -703,10 +1006,6 @@ class AstParserService {
703
1006
  }
704
1007
  return segments;
705
1008
  }
706
- /** If `node` is a declaration this service indexes (top-level symbol, member,
707
- * or collected class), its own name + container. Otherwise null. Shared by
708
- * member extraction, ownership ranges and the call graph so all three name a
709
- * declaration identically. */
710
1009
  describeIndexedNode(node) {
711
1010
  if (ts_morph_1.Node.isClassDeclaration(node)) {
712
1011
  const name = node.getName() ?? (node.isDefaultExport() ? 'default' : null);
@@ -799,9 +1098,6 @@ class AstParserService {
799
1098
  qualifiedName(described) {
800
1099
  return described.container.length ? `${described.container.join('.')}.${described.name}` : described.name;
801
1100
  }
802
- /** The qualified name of the nearest ancestor of `node` that is itself an
803
- * indexed declaration (method, accessor, constructor, function property,
804
- * namespace/top-level function, or module-/namespace-level variable). */
805
1101
  findFromSymbol(node) {
806
1102
  let current = node.getParent();
807
1103
  while (current) {
@@ -812,7 +1108,6 @@ class AstParserService {
812
1108
  }
813
1109
  return null;
814
1110
  }
815
- // ── private: call graph extraction ────────────────────────────────────────
816
1111
  extractCalls(sourceFile, filePath, serviceName) {
817
1112
  const calls = [];
818
1113
  for (const callExpr of sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression)) {
@@ -835,7 +1130,6 @@ class AstParserService {
835
1130
  calleeName = expr.getText();
836
1131
  const sym = expr.getSymbol();
837
1132
  if (sym) {
838
- // Imported identifiers resolve to ImportSpecifier (same file); follow the alias.
839
1133
  const firstDecl = sym.getDeclarations()?.[0];
840
1134
  const resolved = firstDecl && ts_morph_1.Node.isImportSpecifier(firstDecl)
841
1135
  ? (sym.getAliasedSymbol() ?? sym)
@@ -850,7 +1144,6 @@ class AstParserService {
850
1144
  }
851
1145
  }
852
1146
  catch {
853
- // type resolution failures are normal — skip this call
854
1147
  }
855
1148
  if (!calleeName || !calleeFile)
856
1149
  continue;