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
@@ -5,8 +5,12 @@
5
5
  * 提取: struct, enum, trait, impl, function, method, mod, use, const/static
6
6
  * 模式: Builder, Newtype, Factory (new/from), Error Handling (Result/Option/?),
7
7
  * Async (tokio/async-std), Unsafe block, Derive macro
8
+ *
9
+ * Phase 5: 新增 ImportRecord 结构化导入 + extractCallSites 调用点提取
8
10
  */
9
11
 
12
+ import { ImportRecord } from '../analysis/ImportRecord.js';
13
+
10
14
  function walkRust(root, ctx) {
11
15
  for (let i = 0; i < root.namedChildCount; i++) {
12
16
  const child = root.namedChild(i);
@@ -83,7 +87,6 @@ function _walkNode(node, ctx) {
83
87
  // ── Use Declaration ──────────────────────────────────────────
84
88
 
85
89
  function _parseUseDecl(node, ctx) {
86
- // 提取 use 路径文本
87
90
  const argNode = node.namedChildren.find(
88
91
  (c) =>
89
92
  c.type === 'use_wildcard' ||
@@ -93,8 +96,57 @@ function _parseUseDecl(node, ctx) {
93
96
  c.type === 'identifier' ||
94
97
  c.type === 'scoped_use_list'
95
98
  );
96
- if (argNode) {
97
- ctx.imports.push(argNode.text);
99
+ if (!argNode) return;
100
+
101
+ const text = argNode.text;
102
+
103
+ if (argNode.type === 'use_as_clause') {
104
+ // use crate::mod::Foo as Bar
105
+ const pathNode = argNode.namedChildren.find(
106
+ (c) => c.type === 'scoped_identifier' || c.type === 'identifier'
107
+ );
108
+ const aliasNode = argNode.namedChildren.find((c) => c.type === 'identifier' && c !== pathNode);
109
+ const fullPath = pathNode?.text || text;
110
+ const segments = fullPath.split('::');
111
+ const lastName = segments[segments.length - 1];
112
+ ctx.imports.push(
113
+ new ImportRecord(fullPath, {
114
+ symbols: [lastName],
115
+ alias: aliasNode?.text || lastName,
116
+ kind: 'named',
117
+ })
118
+ );
119
+ } else if (argNode.type === 'use_wildcard') {
120
+ // use crate::mod::*
121
+ const pathPart = text.replace(/::\*$/, '');
122
+ ctx.imports.push(
123
+ new ImportRecord(pathPart, { symbols: ['*'], kind: 'namespace' })
124
+ );
125
+ } else if (argNode.type === 'use_list' || argNode.type === 'scoped_use_list') {
126
+ // use crate::mod::{A, B, C} or use {A, B}
127
+ // Extract path prefix and symbol list from text
128
+ const match = text.match(/^(.+)::\{(.+)\}$/s);
129
+ if (match) {
130
+ const prefix = match[1];
131
+ const symbolsStr = match[2];
132
+ const symbols = symbolsStr.split(',').map((s) => s.trim().split('::').pop().split(' as ')[0].trim()).filter(Boolean);
133
+ ctx.imports.push(
134
+ new ImportRecord(prefix, { symbols, kind: 'named' })
135
+ );
136
+ } else {
137
+ ctx.imports.push(new ImportRecord(text));
138
+ }
139
+ } else if (argNode.type === 'scoped_identifier') {
140
+ // use crate::mod::Struct
141
+ const segments = text.split('::');
142
+ const lastName = segments[segments.length - 1];
143
+ const prefix = segments.slice(0, -1).join('::');
144
+ ctx.imports.push(
145
+ new ImportRecord(prefix || text, { symbols: [lastName], alias: lastName, kind: 'named' })
146
+ );
147
+ } else {
148
+ // use identifier (rare: e.g. `use std;`)
149
+ ctx.imports.push(new ImportRecord(text, { symbols: ['*'], alias: text, kind: 'namespace' }));
98
150
  }
99
151
  }
100
152
 
@@ -698,6 +750,234 @@ function _maxNesting(node, depth) {
698
750
  return max;
699
751
  }
700
752
 
753
+ // ── Rust Call Site 提取 (Phase 5) ────────────────────────────
754
+
755
+ /**
756
+ * 从 Rust AST root 提取所有调用点
757
+ * 遍历 function_item / impl method 中的 block → call_expression / method_call_expression
758
+ *
759
+ * @param {TreeSitterNode} root
760
+ * @param {object} ctx
761
+ * @param {string} _lang
762
+ */
763
+ function extractCallSitesRust(root, ctx, _lang) {
764
+ const scopes = _collectRustScopes(root);
765
+ for (const scope of scopes) {
766
+ _extractRustCallSitesFromBody(scope.body, scope.className, scope.methodName, ctx);
767
+ }
768
+ }
769
+
770
+ /**
771
+ * 递归收集 Rust 中所有函数/方法体作用域
772
+ */
773
+ function _collectRustScopes(root) {
774
+ const scopes = [];
775
+
776
+ function visit(node, className) {
777
+ for (let i = 0; i < node.namedChildCount; i++) {
778
+ const child = node.namedChild(i);
779
+
780
+ if (child.type === 'impl_item') {
781
+ // impl Type { ... } or impl Trait for Type { ... }
782
+ const typeIdNodes = child.namedChildren.filter(
783
+ (c) => c.type === 'type_identifier' || c.type === 'scoped_type_identifier' || c.type === 'generic_type'
784
+ );
785
+ const hasFor = child.children?.some((c) => c.type === 'for');
786
+ let selfType = null;
787
+ if (hasFor && typeIdNodes.length >= 2) {
788
+ selfType = typeIdNodes[1]?.text;
789
+ } else if (typeIdNodes.length >= 1) {
790
+ selfType = typeIdNodes[0]?.text;
791
+ }
792
+ const body = child.namedChildren.find((c) => c.type === 'declaration_list');
793
+ if (body) {
794
+ visit(body, selfType || className);
795
+ }
796
+ } else if (child.type === 'function_item') {
797
+ const name = child.namedChildren.find((c) => c.type === 'identifier')?.text;
798
+ const body = child.namedChildren.find((c) => c.type === 'block');
799
+ if (name && body) {
800
+ scopes.push({ body, className, methodName: name });
801
+ }
802
+ } else if (child.type === 'mod_item') {
803
+ const body = child.namedChildren.find((c) => c.type === 'declaration_list');
804
+ if (body) {
805
+ visit(body, null);
806
+ }
807
+ }
808
+ }
809
+ }
810
+
811
+ visit(root, null);
812
+ return scopes;
813
+ }
814
+
815
+ /**
816
+ * 从 Rust block 中递归提取调用点
817
+ */
818
+ function _extractRustCallSitesFromBody(bodyNode, className, methodName, ctx) {
819
+ if (!bodyNode) return;
820
+
821
+ const RUST_NOISE = new Set([
822
+ 'println', 'eprintln', 'print', 'eprint', 'dbg', 'format',
823
+ 'vec', 'panic', 'assert', 'assert_eq', 'assert_ne', 'debug_assert',
824
+ 'todo', 'unimplemented', 'unreachable', 'cfg',
825
+ 'write', 'writeln', 'log', 'info', 'warn', 'error', 'debug', 'trace',
826
+ ]);
827
+
828
+ function walk(node, isAwaited) {
829
+ if (!node || node.type === 'ERROR' || node.isMissing) return;
830
+
831
+ // await expression: expr.await
832
+ if (node.type === 'await_expression') {
833
+ for (let i = 0; i < node.namedChildCount; i++) {
834
+ walk(node.namedChild(i), true);
835
+ }
836
+ return;
837
+ }
838
+
839
+ // call_expression: func(args) or Struct::method(args)
840
+ if (node.type === 'call_expression') {
841
+ const func = node.namedChildren[0];
842
+ if (!func) { walkChildren(node, false); return; }
843
+
844
+ let callee, receiver = null, receiverType = null, callType;
845
+
846
+ if (func.type === 'scoped_identifier' || func.type === 'scoped_type_identifier') {
847
+ // Struct::method() or crate::mod::func()
848
+ const parts = func.text.split('::');
849
+ if (parts.length >= 2) {
850
+ callee = parts[parts.length - 1];
851
+ receiver = parts.slice(0, -1).join('::');
852
+ // Check if receiver looks like a type (PascalCase)
853
+ const lastReceiver = parts[parts.length - 2];
854
+ if (/^[A-Z]/.test(lastReceiver)) {
855
+ receiverType = lastReceiver;
856
+ callType = callee === 'new' || callee === 'default' ? 'constructor' : 'static';
857
+ } else {
858
+ callType = 'function'; // module-qualified function
859
+ }
860
+ } else {
861
+ callee = func.text;
862
+ callType = 'function';
863
+ }
864
+ } else if (func.type === 'field_expression') {
865
+ // obj.func() — though Rust usually uses method_call_expression for this
866
+ const parts = func.text.split('.');
867
+ if (parts.length >= 2) {
868
+ receiver = parts.slice(0, -1).join('.');
869
+ callee = parts[parts.length - 1];
870
+ callType = 'method';
871
+ if (receiver === 'self' || receiver === '&self' || receiver === '&mut self') {
872
+ receiverType = className;
873
+ }
874
+ } else {
875
+ callee = func.text;
876
+ callType = 'function';
877
+ }
878
+ } else if (func.type === 'identifier') {
879
+ callee = func.text;
880
+ if (RUST_NOISE.has(callee)) { walkChildren(node, false); return; }
881
+ // PascalCase → constructor pattern (rare in Rust — turbofish/struct literal more common)
882
+ callType = /^[A-Z]/.test(callee) ? 'constructor' : 'function';
883
+ if (callType === 'constructor') receiverType = callee;
884
+ } else {
885
+ callee = func.text?.slice(0, 80) || 'unknown';
886
+ callType = 'function';
887
+ }
888
+
889
+ const args = node.namedChildren.find((c) => c.type === 'arguments');
890
+ const argCount = args ? args.namedChildCount : 0;
891
+
892
+ ctx.callSites.push({
893
+ callee,
894
+ callerMethod: methodName,
895
+ callerClass: className,
896
+ callType,
897
+ receiver,
898
+ receiverType,
899
+ argCount,
900
+ line: node.startPosition.row + 1,
901
+ isAwait: isAwaited,
902
+ });
903
+
904
+ if (args) walkChildren(args, false);
905
+ return;
906
+ }
907
+
908
+ // method_call_expression: obj.method(args) — Rust-specific
909
+ if (node.type === 'method_call_expression') {
910
+ const valueNode = node.namedChildren.find(
911
+ (c) => c.type !== 'field_identifier' && c.type !== 'arguments' && c.type !== 'type_arguments'
912
+ );
913
+ const nameNode = node.namedChildren.find((c) => c.type === 'field_identifier');
914
+ const args = node.namedChildren.find((c) => c.type === 'arguments');
915
+
916
+ const callee = nameNode?.text || 'unknown';
917
+ const receiver = valueNode?.text?.slice(0, 80) || null;
918
+ let receiverType = null;
919
+ let callType = 'method';
920
+
921
+ if (receiver === 'self' || receiver === '&self' || receiver === '&mut self') {
922
+ receiverType = className;
923
+ } else if (receiver && /^[A-Z]/.test(receiver)) {
924
+ receiverType = receiver;
925
+ }
926
+
927
+ // Skip noise methods
928
+ if (RUST_NOISE.has(callee)) { walkChildren(node, false); return; }
929
+
930
+ const argCount = args ? args.namedChildCount : 0;
931
+
932
+ ctx.callSites.push({
933
+ callee,
934
+ callerMethod: methodName,
935
+ callerClass: className,
936
+ callType,
937
+ receiver,
938
+ receiverType,
939
+ argCount,
940
+ line: node.startPosition.row + 1,
941
+ isAwait: isAwaited,
942
+ });
943
+
944
+ if (args) walkChildren(args, false);
945
+ return;
946
+ }
947
+
948
+ // macro_invocation: some_macro!(args) — skip noise macros
949
+ if (node.type === 'macro_invocation') {
950
+ const macroName = node.namedChildren.find((c) => c.type === 'identifier')?.text;
951
+ if (macroName && !RUST_NOISE.has(macroName) && !RUST_NOISE.has(macroName.replace(/!$/, ''))) {
952
+ // Only record non-noise macros as function calls
953
+ ctx.callSites.push({
954
+ callee: macroName,
955
+ callerMethod: methodName,
956
+ callerClass: className,
957
+ callType: 'function',
958
+ receiver: null,
959
+ receiverType: null,
960
+ argCount: 0,
961
+ line: node.startPosition.row + 1,
962
+ isAwait: false,
963
+ });
964
+ }
965
+ // Don't recurse into macro bodies
966
+ return;
967
+ }
968
+
969
+ walkChildren(node, false);
970
+ }
971
+
972
+ function walkChildren(node, isAwaited) {
973
+ for (let i = 0; i < node.namedChildCount; i++) {
974
+ walk(node.namedChild(i), isAwaited);
975
+ }
976
+ }
977
+
978
+ walk(bodyNode, false);
979
+ }
980
+
701
981
  // ── Plugin Export ────────────────────────────────────────────
702
982
 
703
983
  let _grammar = null;
@@ -712,5 +992,6 @@ export const plugin = {
712
992
  getGrammar,
713
993
  walk: walkRust,
714
994
  detectPatterns: detectRustPatterns,
995
+ extractCallSites: extractCallSitesRust,
715
996
  extensions: ['.rs'],
716
997
  };
@@ -1,8 +1,12 @@
1
1
  /**
2
2
  * @module lang-swift
3
3
  * @description Swift AST Walker 插件 — 从 AstAnalyzer.js 迁移
4
+ *
5
+ * Phase 5: 新增 ImportRecord 结构化导入 + extractCallSites 调用点提取
4
6
  */
5
7
 
8
+ import { ImportRecord } from '../analysis/ImportRecord.js';
9
+
6
10
  // ── Swift AST 遍历 ──
7
11
 
8
12
  function walkSwift(root, ctx) {
@@ -19,7 +23,9 @@ function _walkSwiftNode(node, ctx, parentClassName) {
19
23
  (c) => c.type === 'identifier' || c.type === 'simple_identifier'
20
24
  );
21
25
  if (mod) {
22
- ctx.imports.push(mod.text);
26
+ ctx.imports.push(
27
+ new ImportRecord(mod.text, { symbols: ['*'], alias: mod.text, kind: 'namespace' })
28
+ );
23
29
  }
24
30
  break;
25
31
  }
@@ -315,6 +321,178 @@ function _maxNesting(node, depth) {
315
321
  return max;
316
322
  }
317
323
 
324
+ // ── Swift Call Site 提取 (Phase 5) ───────────────────────────
325
+
326
+ /**
327
+ * 从 Swift AST root 提取所有调用点
328
+ * 遍历 function_declaration 中的 function_body → call_expression
329
+ *
330
+ * @param {TreeSitterNode} root
331
+ * @param {object} ctx
332
+ * @param {string} _lang
333
+ */
334
+ function extractCallSitesSwift(root, ctx, _lang) {
335
+ const scopes = _collectSwiftScopes(root);
336
+ for (const scope of scopes) {
337
+ _extractSwiftCallSitesFromBody(scope.body, scope.className, scope.methodName, ctx);
338
+ }
339
+ }
340
+
341
+ /**
342
+ * 递归收集 Swift 中所有函数体作用域
343
+ */
344
+ function _collectSwiftScopes(root) {
345
+ const scopes = [];
346
+
347
+ function visit(node, className) {
348
+ for (let i = 0; i < node.namedChildCount; i++) {
349
+ const child = node.namedChild(i);
350
+
351
+ if (child.type === 'class_declaration' || child.type === 'struct_declaration' || child.type === 'enum_declaration') {
352
+ const name = child.namedChildren.find(
353
+ (c) => c.type === 'type_identifier' || c.type === 'simple_identifier'
354
+ )?.text;
355
+ const body = child.namedChildren.find(
356
+ (c) => c.type === 'class_body' || c.type === 'struct_body' || c.type === 'enum_body'
357
+ );
358
+ if (body) {
359
+ visit(body, name || className);
360
+ }
361
+ } else if (child.type === 'extension_declaration') {
362
+ const extName = child.namedChildren.find(
363
+ (c) => c.type === 'user_type' || c.type === 'type_identifier'
364
+ )?.text;
365
+ const body = child.namedChildren.find((c) => c.type === 'extension_body');
366
+ if (body) {
367
+ visit(body, extName || className);
368
+ }
369
+ } else if (child.type === 'function_declaration') {
370
+ const name = child.namedChildren.find((c) => c.type === 'simple_identifier')?.text || 'unknown';
371
+ const body = child.namedChildren.find((c) => c.type === 'function_body');
372
+ if (body) {
373
+ scopes.push({ body, className, methodName: name });
374
+ }
375
+ } else if (child.type === 'property_declaration') {
376
+ // computed property with getter
377
+ const computed = child.namedChildren.find(
378
+ (c) => c.type === 'computed_property' || c.type === 'willSet_didSet_block'
379
+ );
380
+ if (computed) {
381
+ const propName = child.namedChildren.find(
382
+ (c) => c.type === 'simple_identifier' || c.type === 'pattern'
383
+ )?.text;
384
+ scopes.push({ body: computed, className, methodName: `get_${propName || 'prop'}` });
385
+ }
386
+ }
387
+ }
388
+ }
389
+
390
+ visit(root, null);
391
+ return scopes;
392
+ }
393
+
394
+ /**
395
+ * 从 Swift function body 中递归提取调用点
396
+ */
397
+ function _extractSwiftCallSitesFromBody(bodyNode, className, methodName, ctx) {
398
+ if (!bodyNode) return;
399
+
400
+ const SWIFT_NOISE = new Set([
401
+ 'print', 'debugPrint', 'dump', 'fatalError', 'precondition', 'preconditionFailure',
402
+ 'assert', 'assertionFailure', 'NSLog',
403
+ 'min', 'max', 'abs', 'stride', 'zip', 'type',
404
+ ]);
405
+
406
+ function walk(node) {
407
+ if (!node || node.type === 'ERROR' || node.isMissing) return;
408
+
409
+ // call_expression in Swift tree-sitter
410
+ if (node.type === 'call_expression') {
411
+ const func = node.namedChildren[0];
412
+ if (!func) { walkChildren(node); return; }
413
+
414
+ let callee, receiver = null, receiverType = null, callType;
415
+ let isAwait = false;
416
+
417
+ // Check if parent is await
418
+ if (node.parent?.type === 'await_expression') {
419
+ isAwait = true;
420
+ }
421
+
422
+ if (func.type === 'navigation_expression' || func.type === 'member_access') {
423
+ // obj.method() or Type.staticMethod()
424
+ const parts = func.text.split('.');
425
+ if (parts.length >= 2) {
426
+ receiver = parts.slice(0, -1).join('.');
427
+ callee = parts[parts.length - 1];
428
+ if (receiver === 'self') {
429
+ receiverType = className;
430
+ callType = 'method';
431
+ } else if (receiver === 'super') {
432
+ receiverType = className;
433
+ callType = 'super';
434
+ } else if (/^[A-Z]/.test(receiver)) {
435
+ receiverType = receiver;
436
+ callType = 'static';
437
+ } else {
438
+ callType = 'method';
439
+ }
440
+ } else {
441
+ callee = func.text;
442
+ callType = 'function';
443
+ }
444
+ } else if (func.type === 'simple_identifier' || func.type === 'identifier') {
445
+ callee = func.text;
446
+ if (SWIFT_NOISE.has(callee)) { walkChildren(node); return; }
447
+ // PascalCase → constructor (Swift initializer)
448
+ callType = /^[A-Z]/.test(callee) ? 'constructor' : 'function';
449
+ if (callType === 'constructor') receiverType = callee;
450
+ } else {
451
+ callee = func.text?.slice(0, 80) || 'unknown';
452
+ callType = 'function';
453
+ }
454
+
455
+ // Count arguments
456
+ const callSuffix = node.namedChildren.find(
457
+ (c) => c.type === 'call_suffix' || c.type === 'value_arguments'
458
+ );
459
+ const argCount = callSuffix ? callSuffix.namedChildCount : 0;
460
+
461
+ ctx.callSites.push({
462
+ callee,
463
+ callerMethod: methodName,
464
+ callerClass: className,
465
+ callType,
466
+ receiver,
467
+ receiverType,
468
+ argCount,
469
+ line: node.startPosition.row + 1,
470
+ isAwait,
471
+ });
472
+
473
+ // walk arguments for nested calls
474
+ if (callSuffix) walkChildren(callSuffix);
475
+ return;
476
+ }
477
+
478
+ // try expression → walk into children
479
+ if (node.type === 'try_expression' || node.type === 'await_expression') {
480
+ walkChildren(node);
481
+ return;
482
+ }
483
+
484
+ walkChildren(node);
485
+ }
486
+
487
+ function walkChildren(node) {
488
+ for (let i = 0; i < node.namedChildCount; i++) {
489
+ walk(node.namedChild(i));
490
+ }
491
+ }
492
+
493
+ walk(bodyNode);
494
+ }
495
+
318
496
  // ── 插件导出 ──
319
497
 
320
498
  let _grammar = null;
@@ -329,5 +507,6 @@ export const plugin = {
329
507
  getGrammar,
330
508
  walk: walkSwift,
331
509
  detectPatterns: detectSwiftPatterns,
510
+ extractCallSites: extractCallSitesSwift,
332
511
  extensions: ['.swift'],
333
512
  };