autosnippet 3.2.7 → 3.2.9

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 (147) hide show
  1. package/bin/cli.js +13 -5
  2. package/dashboard/dist/assets/index-BTAsOZv2.js +128 -0
  3. package/dashboard/dist/assets/index-C_72Ct98.css +1 -0
  4. package/dashboard/dist/index.html +2 -2
  5. package/lib/cli/AiScanService.js +26 -29
  6. package/lib/cli/SetupService.js +1 -1
  7. package/lib/core/AstAnalyzer.js +27 -5
  8. package/lib/core/analysis/CallEdgeResolver.js +402 -0
  9. package/lib/core/analysis/CallGraphAnalyzer.js +367 -0
  10. package/lib/core/analysis/CallSiteExtractor.js +629 -0
  11. package/lib/core/analysis/DataFlowInferrer.js +57 -0
  12. package/lib/core/analysis/ImportPathResolver.js +189 -0
  13. package/lib/core/analysis/ImportRecord.js +105 -0
  14. package/lib/core/analysis/SymbolTableBuilder.js +211 -0
  15. package/lib/core/ast/ProjectGraph.js +8 -0
  16. package/lib/core/ast/lang-dart.js +352 -5
  17. package/lib/core/ast/lang-go.js +212 -10
  18. package/lib/core/ast/lang-java.js +205 -1
  19. package/lib/core/ast/lang-kotlin.js +330 -1
  20. package/lib/core/ast/lang-python.js +31 -2
  21. package/lib/core/ast/lang-rust.js +284 -3
  22. package/lib/core/ast/lang-swift.js +180 -1
  23. package/lib/core/ast/lang-typescript.js +290 -1
  24. package/lib/core/discovery/index.js +2 -2
  25. package/lib/external/ai/AiProvider.js +66 -172
  26. package/lib/external/ai/providers/GoogleGeminiProvider.js +23 -1
  27. package/lib/external/mcp/McpServer.js +1 -0
  28. package/lib/external/mcp/handlers/bootstrap/BootstrapSession.js +1 -1
  29. package/lib/external/mcp/handlers/bootstrap/ExternalSubmissionTracker.js +3 -3
  30. package/lib/external/mcp/handlers/bootstrap/MissionBriefingBuilder.js +22 -1
  31. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +1 -1
  32. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-configs.js +2 -1
  33. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +8 -8
  34. package/lib/external/mcp/handlers/bootstrap/pipeline/noAiFallback.js +1 -1
  35. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +311 -162
  36. package/lib/external/mcp/handlers/bootstrap/shared/bootstrap-phases.js +102 -7
  37. package/lib/external/mcp/handlers/bootstrap/shared/dimension-sop.js +1 -1
  38. package/lib/external/mcp/handlers/bootstrap-external.js +9 -2
  39. package/lib/external/mcp/handlers/bootstrap-internal.js +19 -8
  40. package/lib/external/mcp/handlers/consolidated.js +9 -0
  41. package/lib/external/mcp/handlers/dimension-complete-external.js +6 -6
  42. package/lib/external/mcp/handlers/guard.js +3 -3
  43. package/lib/external/mcp/handlers/structure.js +62 -0
  44. package/lib/external/mcp/handlers/wiki-external.js +66 -3
  45. package/lib/external/mcp/tools.js +36 -1
  46. package/lib/http/HttpServer.js +1 -1
  47. package/lib/http/middleware/requestLogger.js +1 -0
  48. package/lib/http/routes/ai.js +240 -35
  49. package/lib/http/routes/candidates.js +2 -3
  50. package/lib/http/routes/extract.js +13 -11
  51. package/lib/http/routes/modules.js +2 -2
  52. package/lib/http/routes/recipes.js +9 -5
  53. package/lib/http/routes/remote.js +149 -270
  54. package/lib/http/routes/violations.js +0 -54
  55. package/lib/http/utils/sse-sessions.js +1 -1
  56. package/lib/infrastructure/logging/Logger.js +5 -4
  57. package/lib/infrastructure/monitoring/PerformanceMonitor.js +3 -2
  58. package/lib/injection/ServiceContainer.js +70 -28
  59. package/lib/platform/ScreenCaptureService.js +177 -0
  60. package/lib/platform/ios/index.js +2 -2
  61. package/lib/platform/ios/routes/spm.js +2 -2
  62. package/lib/platform/ios/spm/PackageSwiftParser.js +14 -3
  63. package/lib/platform/ios/spm/SpmDiscoverer.js +123 -17
  64. package/lib/platform/ios/spm/{SpmService.js → SpmHelper.js} +43 -675
  65. package/lib/platform/ios/xcode/XcodeWriteUtils.js +1 -1
  66. package/lib/service/agent/AgentEventBus.js +207 -0
  67. package/lib/service/agent/AgentFactory.js +490 -0
  68. package/lib/service/agent/AgentMessage.js +240 -0
  69. package/lib/service/agent/AgentRouter.js +228 -0
  70. package/lib/service/agent/AgentRuntime.js +1016 -0
  71. package/lib/service/agent/AgentState.js +217 -0
  72. package/lib/service/agent/IntentClassifier.js +331 -0
  73. package/lib/service/agent/LarkTransport.js +389 -0
  74. package/lib/service/agent/capabilities.js +408 -0
  75. package/lib/service/{chat → agent/context}/ContextWindow.js +37 -12
  76. package/lib/service/{chat → agent/context}/ExplorationTracker.js +77 -22
  77. package/lib/service/{chat → agent/core}/ChatAgentPrompts.js +14 -2
  78. package/lib/service/agent/core/LoopContext.js +170 -0
  79. package/lib/service/agent/core/MessageAdapter.js +223 -0
  80. package/lib/service/agent/core/ToolExecutionPipeline.js +376 -0
  81. package/lib/service/{chat → agent/domain}/ChatAgentTasks.js +19 -98
  82. package/lib/service/{chat → agent/domain}/EpisodicConsolidator.js +7 -7
  83. package/lib/service/{chat → agent/domain}/EvidenceCollector.js +4 -2
  84. package/lib/service/{chat/AnalystAgent.js → agent/domain/insight-analyst.js} +37 -172
  85. package/lib/service/{chat/HandoffProtocol.js → agent/domain/insight-gate.js} +91 -123
  86. package/lib/service/agent/domain/insight-producer.js +267 -0
  87. package/lib/service/agent/domain/scan-prompts.js +105 -0
  88. package/lib/service/agent/forced-summary.js +266 -0
  89. package/lib/service/agent/index.js +91 -0
  90. package/lib/service/{chat → agent}/memory/ActiveContext.js +3 -1
  91. package/lib/service/{chat → agent}/memory/MemoryCoordinator.js +7 -7
  92. package/lib/service/{chat/ProjectSemanticMemory.js → agent/memory/PersistentMemory.js} +359 -89
  93. package/lib/service/{chat → agent}/memory/SessionStore.js +5 -4
  94. package/lib/service/{chat → agent}/memory/index.js +1 -1
  95. package/lib/service/agent/policies.js +442 -0
  96. package/lib/service/agent/presets.js +303 -0
  97. package/lib/service/agent/strategies.js +717 -0
  98. package/lib/service/{chat → agent/tools}/ToolRegistry.js +3 -3
  99. package/lib/service/agent/tools/ai-analysis.js +75 -0
  100. package/lib/service/{chat → agent}/tools/ast-graph.js +229 -32
  101. package/lib/service/{chat → agent}/tools/composite.js +2 -1
  102. package/lib/service/{chat → agent}/tools/guard.js +1 -121
  103. package/lib/service/{chat → agent}/tools/index.js +33 -22
  104. package/lib/service/{chat → agent}/tools/infrastructure.js +6 -1
  105. package/lib/service/agent/tools/knowledge-graph.js +112 -0
  106. package/lib/service/agent/tools/scan-recipe.js +189 -0
  107. package/lib/service/agent/tools/system-interaction.js +476 -0
  108. package/lib/service/automation/DirectiveDetector.js +0 -1
  109. package/lib/service/automation/FileWatcher.js +0 -8
  110. package/lib/service/automation/handlers/CreateHandler.js +7 -3
  111. package/lib/service/automation/handlers/DraftHandler.js +7 -6
  112. package/lib/service/cursor/CursorDeliveryPipeline.js +167 -1
  113. package/lib/service/knowledge/CodeEntityGraph.js +327 -2
  114. package/lib/service/knowledge/KnowledgeService.js +5 -1
  115. package/lib/service/module/ModuleService.js +49 -73
  116. package/lib/service/skills/SignalCollector.js +26 -19
  117. package/lib/service/snippet/codecs/VSCodeCodec.js +1 -1
  118. package/lib/service/wiki/WikiGenerator.js +1 -1
  119. package/lib/shared/FieldSpec.js +1 -1
  120. package/lib/shared/PathGuard.js +1 -1
  121. package/lib/shared/StyleGuide.js +1 -1
  122. package/package.json +4 -1
  123. package/resources/native-ui/screenshot.swift +228 -0
  124. package/dashboard/dist/assets/index-BaGY7kJI.css +0 -1
  125. package/dashboard/dist/assets/index-DfHY_3ln.js +0 -128
  126. package/lib/core/discovery/SpmDiscoverer.js +0 -5
  127. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +0 -749
  128. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +0 -277
  129. package/lib/http/routes/spm.js +0 -5
  130. package/lib/infrastructure/external/XcodeAutomation.js +0 -15
  131. package/lib/service/chat/ChatAgent.js +0 -1602
  132. package/lib/service/chat/Memory.js +0 -161
  133. package/lib/service/chat/ProducerAgent.js +0 -431
  134. package/lib/service/chat/ReasoningTrace.js +0 -523
  135. package/lib/service/chat/TaskPipeline.js +0 -357
  136. package/lib/service/chat/WorkingMemory.js +0 -357
  137. package/lib/service/chat/memory/PersistentMemory.js +0 -450
  138. package/lib/service/chat/tools/ai-analysis.js +0 -267
  139. package/lib/service/chat/tools/knowledge-graph.js +0 -234
  140. package/lib/service/chat/tools.js +0 -18
  141. package/lib/service/snippet/PlaceholderConverter.js +0 -5
  142. package/lib/service/snippet/codecs/XcodeCodec.js +0 -5
  143. /package/lib/service/{chat → agent}/ConversationStore.js +0 -0
  144. /package/lib/service/{chat → agent}/tools/_shared.js +0 -0
  145. /package/lib/service/{chat → agent}/tools/lifecycle.js +0 -0
  146. /package/lib/service/{chat → agent}/tools/project-access.js +0 -0
  147. /package/lib/service/{chat → agent}/tools/query.js +0 -0
@@ -0,0 +1,402 @@
1
+ /**
2
+ * @module CallEdgeResolver
3
+ * @description Phase 5: 将调用点 (CallSite) 解析为调用边 (ResolvedEdge)
4
+ *
5
+ * 解析优先级 (4-priority system):
6
+ * 1. this.xxx() — 同类方法调用
7
+ * 2. ImportedType.method() / importedFunc() — import-based 解析
8
+ * 3. localFunc() — 同文件内函数调用
9
+ * 4. globalSearch(name) — 全局唯一匹配 (fallback, 低置信度)
10
+ *
11
+ * 数据流:
12
+ * SymbolTable + ImportPathResolver + CallSite[] → ResolvedEdge[]
13
+ */
14
+
15
+ /**
16
+ * @typedef {object} ResolvedEdge
17
+ * @property {string} caller — 调用者 FQN e.g. "src/service/UserService.ts::UserService.getUser"
18
+ * @property {string} callee — 被调用者 FQN e.g. "src/repository/UserRepo.ts::UserRepo.findById"
19
+ * @property {string} callType — 'function'|'method'|'constructor'|'super'|'static'
20
+ * @property {string} resolveMethod — 'direct'|'inferred'|'cha'
21
+ * @property {number} line — 调用点行号
22
+ * @property {string} file — 调用者文件
23
+ * @property {boolean} isAwait
24
+ */
25
+
26
+ export class CallEdgeResolver {
27
+ /**
28
+ * @param {import('./SymbolTableBuilder.js').SymbolTable} symbolTable
29
+ * @param {import('./ImportPathResolver.js').ImportPathResolver} importResolver
30
+ * @param {Array<{from: string, to: string, type: string}>} [inheritanceGraph=[]] — 继承图边
31
+ */
32
+ constructor(symbolTable, importResolver, inheritanceGraph = []) {
33
+ this.symbolTable = symbolTable;
34
+ this.importResolver = importResolver;
35
+ this.inheritanceGraph = inheritanceGraph;
36
+
37
+ // Phase 5.3: RTA — set of classes that are actually instantiated in the program
38
+ this.instantiatedClasses = symbolTable.instantiatedClasses || new Set();
39
+ // Phase 5.3: DI — property type annotations: className → (fieldName → typeName)
40
+ this.propertyTypes = symbolTable.propertyTypes || new Map();
41
+
42
+ // 构建反向索引: symbolName → [fqn1, fqn2, ...]
43
+ /** @type {Map<string, string[]>} */
44
+ this.nameIndex = new Map();
45
+ // 构建文件级索引: file → [{ name, qualifiedName, fqn }] (Issue #14 性能优化)
46
+ /** @type {Map<string, Array<{name: string, qualifiedName: string, fqn: string}>>} */
47
+ this.fileIndex = new Map();
48
+
49
+ // Phase 5.3: 类名集合索引 (用于 _inferFieldType 优化,避免全表扫描)
50
+ /** @type {Set<string>} */
51
+ this.classNames = new Set();
52
+
53
+ for (const [fqn, decl] of symbolTable.declarations) {
54
+ const names = [decl.name];
55
+ const qualifiedName = decl.className ? `${decl.className}.${decl.name}` : decl.name;
56
+ if (decl.className) {
57
+ names.push(qualifiedName);
58
+ }
59
+ for (const name of names) {
60
+ if (!this.nameIndex.has(name)) this.nameIndex.set(name, []);
61
+ this.nameIndex.get(name).push(fqn);
62
+ }
63
+ // 文件级索引
64
+ if (!this.fileIndex.has(decl.file)) this.fileIndex.set(decl.file, []);
65
+ this.fileIndex.get(decl.file).push({ name: decl.name, qualifiedName, fqn });
66
+
67
+ // Phase 5.3: 收集类名用于快速 DI 推断
68
+ if (decl.kind === 'class') {
69
+ this.classNames.add(decl.name);
70
+ }
71
+ }
72
+ }
73
+
74
+ /**
75
+ * 解析一个文件中的所有调用点为边
76
+ *
77
+ * @param {import('./CallSiteExtractor.js').CallSite[]} callSites — 来自某个文件的所有调用点
78
+ * @param {string} callerFile — 调用者文件路径 (相对)
79
+ * @returns {ResolvedEdge[]}
80
+ */
81
+ resolveFile(callSites, callerFile) {
82
+ const edges = [];
83
+ const fileImports = this.symbolTable.fileImports.get(callerFile) || [];
84
+
85
+ // 构建局部 import 映射: symbolName → { file, namespace }
86
+ const importedSymbols = this._buildImportMap(fileImports, callerFile);
87
+
88
+ // 去重集合: "caller→callee@line" 防止同一调用点产生重复边
89
+ const seen = new Set();
90
+
91
+ for (const cs of callSites) {
92
+ const resolved = this._resolveCallSite(cs, callerFile, importedSymbols);
93
+ if (resolved) {
94
+ const key = `${resolved.caller}→${resolved.callee}@${resolved.line}`;
95
+ if (!seen.has(key)) {
96
+ seen.add(key);
97
+ edges.push(resolved);
98
+ }
99
+ }
100
+ }
101
+
102
+ return edges;
103
+ }
104
+
105
+ /**
106
+ * @private 构建局部 import 映射
107
+ */
108
+ _buildImportMap(fileImports, callerFile) {
109
+ /** @type {Map<string, { file: string, namespace: boolean }>} */
110
+ const importedSymbols = new Map();
111
+
112
+ for (const imp of fileImports) {
113
+ const targetFile = this.importResolver.resolve(imp.path || String(imp), callerFile);
114
+ if (!targetFile) continue; // 外部依赖, 跳过
115
+
116
+ if (imp.symbols && imp.symbols.length > 0) {
117
+ for (const sym of imp.symbols) {
118
+ if (sym === '*' && imp.alias) {
119
+ importedSymbols.set(imp.alias, { file: targetFile, namespace: true });
120
+ } else if (sym !== '*') {
121
+ // named/default: symbols 已包含本地名 (alias baked-in), 直接使用
122
+ importedSymbols.set(sym, { file: targetFile, namespace: false });
123
+ }
124
+ }
125
+ } else {
126
+ // 无结构化信息时,使用路径最后一段作为 namespace hint
127
+ const pathParts = String(imp).split('/');
128
+ const lastPart = pathParts[pathParts.length - 1]?.replace(/\.\w+$/, '');
129
+ if (lastPart) {
130
+ importedSymbols.set(lastPart, { file: targetFile, namespace: true });
131
+ }
132
+ }
133
+ }
134
+
135
+ return importedSymbols;
136
+ }
137
+
138
+ /**
139
+ * @private 解析单个调用点
140
+ */
141
+ _resolveCallSite(cs, callerFile, importedSymbols) {
142
+ const callerFqn = `${callerFile}::${cs.callerClass ? `${cs.callerClass}.` : ''}${cs.callerMethod}`;
143
+
144
+ // Priority 0: super.xxx() — 父类方法调用 (CHA 解析,禁止 fallthrough 防止自引用边)
145
+ if (cs.callType === 'super' || cs.receiver === 'super' || cs.receiver === 'super()') {
146
+ if (cs.callerClass && cs.callee && cs.callee !== 'super') {
147
+ const chaResult = this._resolveByCHA(cs.callee, cs.callerClass);
148
+ if (chaResult) {
149
+ return this._makeEdge(callerFqn, chaResult, 'cha', cs, callerFile);
150
+ }
151
+ }
152
+ // CHA 无法解析时不 fallthrough (避免 local search 匹配到自己产生 self-edge)
153
+ return null;
154
+ }
155
+
156
+ // Priority 1: this.xxx() / self.xxx() — 同类方法调用
157
+ if (cs.receiver === 'this' || cs.receiver === 'self') {
158
+ if (cs.callerClass) {
159
+ const candidates = this._findInFile(`${cs.callerClass}.${cs.callee}`, callerFile);
160
+ if (candidates.length > 0) {
161
+ return this._makeEdge(callerFqn, candidates[0], 'direct', cs, callerFile);
162
+ }
163
+ // CHA fallback: 在继承链上查找方法
164
+ const chaResult = this._resolveByCHA(cs.callee, cs.callerClass);
165
+ if (chaResult) {
166
+ return this._makeEdge(callerFqn, chaResult, 'cha', cs, callerFile);
167
+ }
168
+ }
169
+ }
170
+
171
+ // Priority 1.5: this.field.method() — DI 注入字段方法调用
172
+ if (cs.receiver && (cs.receiver.startsWith('this.') || cs.receiver.startsWith('self.'))) {
173
+ const fieldName = cs.receiver.split('.').slice(1).join('.');
174
+
175
+ // Phase 5.3: First try explicit type annotation from property declarations (DI-aware)
176
+ if (cs.callerClass) {
177
+ const classProps = this.propertyTypes.get(cs.callerClass);
178
+ if (classProps) {
179
+ const fieldType = classProps.get(fieldName);
180
+ if (fieldType) {
181
+ const typeCandidates = this.nameIndex.get(`${fieldType}.${cs.callee}`) || [];
182
+ if (typeCandidates.length > 0) {
183
+ return this._makeEdge(callerFqn, typeCandidates[0], 'direct', cs, callerFile);
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ // 尝试从 receiverType 解析 (可能 extractCallSites 已推断)
190
+ if (cs.receiverType) {
191
+ const typeCandidates = this.nameIndex.get(`${cs.receiverType}.${cs.callee}`) || [];
192
+ if (typeCandidates.length > 0) {
193
+ return this._makeEdge(callerFqn, typeCandidates[0], 'direct', cs, callerFile);
194
+ }
195
+ }
196
+ // 尝试通过命名约定推断: userRepo → UserRepo, userService → UserService
197
+ const inferredType = this._inferFieldType(fieldName);
198
+ if (inferredType) {
199
+ const typeCandidates = this.nameIndex.get(`${inferredType}.${cs.callee}`) || [];
200
+ if (typeCandidates.length > 0) {
201
+ return this._makeEdge(callerFqn, typeCandidates[0], 'inferred', cs, callerFile);
202
+ }
203
+ }
204
+ }
205
+
206
+ // Priority 2: Import-based 解析
207
+ const importInfo = importedSymbols.get(cs.receiver || cs.callee);
208
+ if (importInfo) {
209
+ const targetFile = importInfo.file;
210
+
211
+ if (importInfo.namespace && cs.receiver) {
212
+ // namespace import: M.foo() → 在 targetFile 中查找 foo
213
+ const candidates = this._findInFile(cs.callee, targetFile);
214
+ if (candidates.length > 0) {
215
+ return this._makeEdge(callerFqn, candidates[0], 'direct', cs, callerFile);
216
+ }
217
+ } else {
218
+ // named import: 查找 import 的符号
219
+ const lookupName = cs.receiver
220
+ ? `${cs.receiver}.${cs.callee}`
221
+ : cs.callee;
222
+ let candidates = this._findInFile(lookupName, targetFile);
223
+ if (candidates.length === 0 && cs.receiver) {
224
+ // 可能 import 的是类名,方法是类的方法
225
+ candidates = this._findInFile(`${cs.receiver}.${cs.callee}`, targetFile);
226
+ }
227
+ if (candidates.length === 0 && !cs.receiver) {
228
+ // 可能是函数名
229
+ candidates = this._findInFile(cs.callee, targetFile);
230
+ }
231
+ if (candidates.length > 0) {
232
+ return this._makeEdge(callerFqn, candidates[0], 'direct', cs, callerFile);
233
+ }
234
+ }
235
+ }
236
+
237
+ // Priority 2.5: Implicit this — OOP 语言中 bare method() 即 this.method()
238
+ // 在 Dart/Java/Kotlin/Swift 等语言中, 类内调用 method() 等价于 this.method()
239
+ // 先查同类方法, 再 CHA 查父类方法
240
+ if (!cs.receiver && cs.callerClass && cs.callType !== 'constructor') {
241
+ // 2.5a: 同类方法 (精确匹配 Class.method)
242
+ const implicitThisCandidates = this._findInFile(
243
+ `${cs.callerClass}.${cs.callee}`, callerFile,
244
+ ).filter(fqn => fqn !== callerFqn);
245
+ if (implicitThisCandidates.length > 0) {
246
+ return this._makeEdge(callerFqn, implicitThisCandidates[0], 'direct', cs, callerFile);
247
+ }
248
+ // 2.5b: CHA 查父类 (继承链上的方法)
249
+ const chaImplicit = this._resolveByCHA(cs.callee, cs.callerClass);
250
+ if (chaImplicit) {
251
+ return this._makeEdge(callerFqn, chaImplicit, 'cha', cs, callerFile);
252
+ }
253
+ }
254
+
255
+ // Priority 3: 同文件内的函数调用
256
+ // 过滤 callerFqn 防止同名方法重载(overload)产生假自引用边
257
+ const localCandidates = this._findInFile(cs.callee, callerFile)
258
+ .filter(fqn => fqn !== callerFqn);
259
+ if (localCandidates.length > 0) {
260
+ return this._makeEdge(callerFqn, localCandidates[0], 'direct', cs, callerFile);
261
+ }
262
+ // 也尝试 Class.method 格式
263
+ if (cs.receiver && !importedSymbols.has(cs.receiver)) {
264
+ const qualifiedLocal = this._findInFile(`${cs.receiver}.${cs.callee}`, callerFile)
265
+ .filter(fqn => fqn !== callerFqn);
266
+ if (qualifiedLocal.length > 0) {
267
+ return this._makeEdge(callerFqn, qualifiedLocal[0], 'direct', cs, callerFile);
268
+ }
269
+ }
270
+
271
+ // Priority 4: 全局搜索 (唯一匹配才采用)
272
+ // 过滤 callerFqn 防止全局唯一命名碰撞自己
273
+ const globalCandidates = (this.nameIndex.get(cs.callee) || [])
274
+ .filter(fqn => fqn !== callerFqn);
275
+ if (globalCandidates.length === 1) {
276
+ return this._makeEdge(callerFqn, globalCandidates[0], 'inferred', cs, callerFile);
277
+ }
278
+
279
+ // Phase 5.3 RTA: 多个全局候选 → 用实例化集合过滤
280
+ if (globalCandidates.length > 1 && this.instantiatedClasses.size > 0) {
281
+ const rtaFiltered = globalCandidates.filter((fqn) => {
282
+ if (fqn === callerFqn) return false; // 排除自己
283
+ const decl = this.symbolTable.declarations.get(fqn);
284
+ if (!decl) return false;
285
+ // 非类方法 (顶层函数) 不做 RTA 过滤
286
+ if (!decl.className) return true;
287
+ // 类方法 → 仅保留实际实例化的类
288
+ return this.instantiatedClasses.has(decl.className);
289
+ });
290
+ if (rtaFiltered.length === 1) {
291
+ return this._makeEdge(callerFqn, rtaFiltered[0], 'rta', cs, callerFile);
292
+ }
293
+ }
294
+
295
+ // 无法解析 → 不创建边 (宁缺勿滥)
296
+ return null;
297
+ }
298
+
299
+ /**
300
+ * @private CHA (Class Hierarchy Analysis): 沿继承链向上搜索方法
301
+ *
302
+ * 使用 BFS 遍历 inheritanceGraph,从 className 向上搜索直到找到
303
+ * 定义了 methodName 的祖先类。只跟踪 'inherits' 类型的边。
304
+ *
305
+ * @param {string} methodName — 被调用的方法名
306
+ * @param {string} className — 起始类名
307
+ * @returns {string|null} — 找到的 FQN 或 null
308
+ */
309
+ _resolveByCHA(methodName, className) {
310
+ if (!this.inheritanceGraph || this.inheritanceGraph.length === 0) return null;
311
+
312
+ // BFS 向上遍历继承链 (最多 10 层防止循环)
313
+ const visited = new Set([className]);
314
+ const queue = [className];
315
+ const MAX_DEPTH = 10;
316
+ let depth = 0;
317
+
318
+ while (queue.length > 0 && depth < MAX_DEPTH) {
319
+ depth++;
320
+ const nextQueue = [];
321
+ for (const current of queue) {
322
+ // 查找 current 的所有父类 (inherits 和 conforms 类型的边)
323
+ for (const edge of this.inheritanceGraph) {
324
+ if (edge.from === current && !visited.has(edge.to)) {
325
+ visited.add(edge.to);
326
+
327
+ // 在全局符号表中查找 ParentClass.methodName
328
+ const qualifiedName = `${edge.to}.${methodName}`;
329
+ const candidates = this.nameIndex.get(qualifiedName) || [];
330
+ if (candidates.length > 0) {
331
+ return candidates[0];
332
+ }
333
+
334
+ nextQueue.push(edge.to);
335
+ }
336
+ }
337
+ }
338
+ queue.length = 0;
339
+ queue.push(...nextQueue);
340
+ }
341
+
342
+ return null;
343
+ }
344
+
345
+ /**
346
+ * @private 从字段名推断类型(DI/IoC 命名约定推断)
347
+ *
348
+ * 常见模式:
349
+ * - userRepo → UserRepo
350
+ * - userRepository → UserRepository
351
+ * - userService → UserService
352
+ * - _userRepo → UserRepo (Java/Kotlin private field)
353
+ *
354
+ * 只在符号表中存在匹配类时返回
355
+ *
356
+ * @param {string} fieldName
357
+ * @returns {string|null}
358
+ */
359
+ _inferFieldType(fieldName) {
360
+ // 去除前导下划线
361
+ const cleaned = fieldName.replace(/^_+/, '');
362
+ if (!cleaned) return null;
363
+
364
+ // camelCase → PascalCase
365
+ const pascalCase = cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
366
+
367
+ // Phase 5.3: 使用 classNames Set 快速查找 (O(1) 替代 O(n) 全表扫描)
368
+ return this.classNames.has(pascalCase) ? pascalCase : null;
369
+ }
370
+
371
+ /**
372
+ * @private 在指定文件中查找声明 (使用 fileIndex 优化,避免全表扫描)
373
+ * @param {string} name — 符号名 (可以是 "ClassName.methodName" 或 "functionName")
374
+ * @param {string} file
375
+ * @returns {string[]} 匹配的 FQN 列表
376
+ */
377
+ _findInFile(name, file) {
378
+ const fileDecls = this.fileIndex.get(file);
379
+ if (!fileDecls) return [];
380
+ return fileDecls
381
+ .filter((d) => d.name === name || d.qualifiedName === name)
382
+ .map((d) => d.fqn);
383
+ }
384
+
385
+ /**
386
+ * @private 构建 ResolvedEdge
387
+ */
388
+ _makeEdge(callerFqn, calleeFqn, resolveMethod, cs, callerFile) {
389
+ return {
390
+ caller: callerFqn,
391
+ callee: calleeFqn,
392
+ callType: cs.callType,
393
+ resolveMethod,
394
+ line: cs.line,
395
+ file: callerFile,
396
+ isAwait: cs.isAwait,
397
+ argCount: cs.argCount || 0,
398
+ };
399
+ }
400
+ }
401
+
402
+ export default CallEdgeResolver;