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
@@ -6,6 +6,8 @@
6
6
  * 模式: Singleton, Builder, Factory, DI, Stream Pipeline
7
7
  */
8
8
 
9
+ import { ImportRecord } from '../analysis/ImportRecord.js';
10
+
9
11
  function walkJava(root, ctx) {
10
12
  _walkJavaNode(root, ctx, null);
11
13
  }
@@ -16,9 +18,29 @@ function _walkJavaNode(node, ctx, parentClassName) {
16
18
 
17
19
  switch (child.type) {
18
20
  case 'import_declaration': {
21
+ const isStatic = child.namedChildren.some((c) => c.text === 'static');
19
22
  const path = child.namedChildren.find((c) => c.type === 'scoped_identifier');
20
23
  if (path) {
21
- ctx.imports.push(path.text);
24
+ const fullPath = path.text; // e.g. com.example.MyClass or com.example.MyClass.myMethod
25
+ const segments = fullPath.split('.');
26
+ const lastName = segments[segments.length - 1];
27
+ // Java wildcard: import com.example.*
28
+ const isWildcard = child.text.includes('.*');
29
+ if (isWildcard) {
30
+ ctx.imports.push(
31
+ new ImportRecord(fullPath, { symbols: ['*'], kind: 'namespace' })
32
+ );
33
+ } else if (isStatic) {
34
+ // static import: import static com.example.MyClass.method
35
+ ctx.imports.push(
36
+ new ImportRecord(fullPath, { symbols: [lastName], kind: 'named', isTypeOnly: false })
37
+ );
38
+ } else {
39
+ // regular: import com.example.MyClass
40
+ ctx.imports.push(
41
+ new ImportRecord(fullPath, { symbols: [lastName], alias: lastName, kind: 'named' })
42
+ );
43
+ }
22
44
  }
23
45
  break;
24
46
  }
@@ -255,6 +277,22 @@ function _parseJavaField(node, className) {
255
277
  .filter((c) => c.type === 'marker_annotation' || c.type === 'annotation')
256
278
  .map((a) => a.text);
257
279
 
280
+ // Phase 5.3: Extract field type for DI resolution
281
+ // field_declaration: [modifiers] type_identifier variable_declarator
282
+ let typeAnnotation = null;
283
+ const typeNode = node.namedChildren.find(
284
+ (c) =>
285
+ c.type === 'type_identifier' ||
286
+ c.type === 'generic_type' ||
287
+ c.type === 'scoped_type_identifier'
288
+ );
289
+ if (typeNode) {
290
+ // Strip generics: List<User> → List, Repository<User, Long> → Repository
291
+ const text = typeNode.text;
292
+ const bracketIdx = text.indexOf('<');
293
+ typeAnnotation = bracketIdx > 0 ? text.slice(0, bracketIdx) : text;
294
+ }
295
+
258
296
  return {
259
297
  name,
260
298
  className,
@@ -262,6 +300,7 @@ function _parseJavaField(node, className) {
262
300
  isFinal,
263
301
  isPrivate,
264
302
  annotations,
303
+ typeAnnotation,
265
304
  line: node.startPosition.row + 1,
266
305
  };
267
306
  }
@@ -413,6 +452,170 @@ function _maxNesting(node, depth) {
413
452
  return max;
414
453
  }
415
454
 
455
+ // ── Java Call Site 提取 (Phase 5) ─────────────────────────────
456
+
457
+ /**
458
+ * 从 Java AST root 提取所有调用点
459
+ * 遍历 method_declaration / constructor_declaration 中的 block → method_invocation / object_creation_expression
460
+ */
461
+ function extractCallSitesJava(root, ctx, _lang) {
462
+ const scopes = _collectJavaScopes(root);
463
+ for (const scope of scopes) {
464
+ _extractJavaCallSitesFromBody(scope.body, scope.className, scope.methodName, ctx);
465
+ }
466
+ }
467
+
468
+ /**
469
+ * 递归收集 Java 中所有方法体作用域
470
+ */
471
+ function _collectJavaScopes(root) {
472
+ const scopes = [];
473
+
474
+ function visit(node, className) {
475
+ for (let i = 0; i < node.namedChildCount; i++) {
476
+ const child = node.namedChild(i);
477
+
478
+ if (child.type === 'class_declaration' || child.type === 'enum_declaration' || child.type === 'record_declaration') {
479
+ const name = child.namedChildren.find((c) => c.type === 'identifier')?.text;
480
+ const body = child.namedChildren.find((c) => c.type === 'class_body' || c.type === 'enum_body');
481
+ if (body) {
482
+ visit(body, name || className);
483
+ }
484
+ } else if (child.type === 'interface_declaration') {
485
+ const name = child.namedChildren.find((c) => c.type === 'identifier')?.text;
486
+ const body = child.namedChildren.find((c) => c.type === 'interface_body');
487
+ if (body) {
488
+ visit(body, name || className);
489
+ }
490
+ } else if (child.type === 'method_declaration' || child.type === 'constructor_declaration') {
491
+ const name = child.namedChildren.find((c) => c.type === 'identifier')?.text || '<init>';
492
+ const body = child.namedChildren.find((c) => c.type === 'block');
493
+ if (body) {
494
+ scopes.push({ body, className, methodName: name });
495
+ }
496
+ }
497
+ }
498
+ }
499
+
500
+ visit(root, null);
501
+ return scopes;
502
+ }
503
+
504
+ /**
505
+ * 从 Java method body 中递归提取调用点
506
+ */
507
+ function _extractJavaCallSitesFromBody(bodyNode, className, methodName, ctx) {
508
+ if (!bodyNode) return;
509
+
510
+ const JAVA_NOISE = new Set([
511
+ 'System', 'Math', 'String', 'Integer', 'Long', 'Double', 'Float',
512
+ 'Boolean', 'Character', 'Byte', 'Short', 'Arrays', 'Collections',
513
+ 'Objects', 'Optional', 'Stream', 'Collectors', 'List', 'Map', 'Set',
514
+ ]);
515
+
516
+ function walk(node) {
517
+ if (!node || node.type === 'ERROR' || node.isMissing) return;
518
+
519
+ if (node.type === 'method_invocation') {
520
+ // obj.method(args) or method(args)
521
+ // In Java tree-sitter: namedChildren = [object?, name(identifier), argument_list]
522
+ // The method name is the LAST identifier before argument_list
523
+ const identifiers = node.namedChildren.filter((c) => c.type === 'identifier');
524
+ const args = node.namedChildren.find((c) => c.type === 'argument_list');
525
+ const argCount = args ? args.namedChildCount : 0;
526
+
527
+ let callee, receiver = null, receiverType = null, callType;
528
+
529
+ if (identifiers.length >= 2) {
530
+ // obj.method() — first identifier is receiver, last is method name
531
+ const objectNode = identifiers[0];
532
+ const methodNode = identifiers[identifiers.length - 1];
533
+ receiver = objectNode.text?.slice(0, 80);
534
+ callee = methodNode.text;
535
+ callType = 'method';
536
+
537
+ // Check known noise
538
+ if (receiver && JAVA_NOISE.has(receiver.split('.')[0])) {
539
+ receiverType = receiver;
540
+ callType = 'static';
541
+ }
542
+ } else if (identifiers.length === 1) {
543
+ // Could be: unqualified method(args) or receiver is an expression
544
+ const objectNode = node.namedChildren[0];
545
+ if (objectNode && objectNode.type !== 'identifier' && objectNode.type !== 'argument_list') {
546
+ // Expression receiver: e.g. getService().doWork() or super.method()
547
+ receiver = objectNode.text?.slice(0, 80);
548
+ callee = identifiers[0].text;
549
+ callType = 'method';
550
+ // super.xxx() → CHA 解析到父类
551
+ if (receiver === 'super') {
552
+ callType = 'super';
553
+ receiverType = className;
554
+ }
555
+ } else {
556
+ callee = identifiers[0].text;
557
+ callType = 'function'; // unqualified method call within same class
558
+ }
559
+ } else {
560
+ callee = node.text?.split('(')[0]?.slice(0, 80) || 'unknown';
561
+ callType = 'function';
562
+ }
563
+
564
+ ctx.callSites.push({
565
+ callee,
566
+ callerMethod: methodName,
567
+ callerClass: className,
568
+ callType,
569
+ receiver,
570
+ receiverType,
571
+ argCount,
572
+ line: node.startPosition.row + 1,
573
+ isAwait: false,
574
+ });
575
+
576
+ // walk arguments for nested calls
577
+ if (args) walkChildren(args);
578
+ return;
579
+ }
580
+
581
+ if (node.type === 'object_creation_expression') {
582
+ // new ClassName(args)
583
+ const typeNode = node.namedChildren.find(
584
+ (c) => c.type === 'type_identifier' || c.type === 'generic_type' || c.type === 'scoped_type_identifier'
585
+ );
586
+ const args = node.namedChildren.find((c) => c.type === 'argument_list');
587
+ const argCount = args ? args.namedChildCount : 0;
588
+
589
+ const typeName = typeNode?.text || 'Unknown';
590
+
591
+ ctx.callSites.push({
592
+ callee: typeName,
593
+ callerMethod: methodName,
594
+ callerClass: className,
595
+ callType: 'constructor',
596
+ receiver: null,
597
+ receiverType: typeName,
598
+ argCount,
599
+ line: node.startPosition.row + 1,
600
+ isAwait: false,
601
+ });
602
+
603
+ if (args) walkChildren(args);
604
+ return;
605
+ }
606
+
607
+ walkChildren(node);
608
+ }
609
+
610
+ function walkChildren(node) {
611
+ for (let i = 0; i < node.namedChildCount; i++) {
612
+ walk(node.namedChild(i));
613
+ }
614
+ }
615
+
616
+ walk(bodyNode);
617
+ }
618
+
416
619
  // ── 插件导出 ──
417
620
 
418
621
  let _grammar = null;
@@ -427,5 +630,6 @@ export const plugin = {
427
630
  getGrammar,
428
631
  walk: walkJava,
429
632
  detectPatterns: detectJavaPatterns,
633
+ extractCallSites: extractCallSitesJava,
430
634
  extensions: ['.java'],
431
635
  };
@@ -6,6 +6,8 @@
6
6
  * 模式: Singleton (object), Factory (companion), DSL, Flow, Sealed
7
7
  */
8
8
 
9
+ import { ImportRecord } from '../analysis/ImportRecord.js';
10
+
9
11
  function walkKotlin(root, ctx) {
10
12
  _walkKtNode(root, ctx, null);
11
13
  }
@@ -21,7 +23,28 @@ function _walkKtNode(node, ctx, parentClassName) {
21
23
  (c) => c.type === 'identifier' || c.type === 'user_type'
22
24
  );
23
25
  if (id) {
24
- ctx.imports.push(id.text);
26
+ const fullPath = id.text; // e.g. com.example.MyClass or com.example.myFunc
27
+ const segments = fullPath.split('.');
28
+ const lastName = segments[segments.length - 1];
29
+ // Kotlin: import com.example.* (wildcard)
30
+ const isWildcard = child.text.includes('.*');
31
+ // Kotlin: import com.example.MyClass as Alias
32
+ const aliasNode = child.namedChildren.find((c) => c.type === 'import_alias');
33
+ const alias = aliasNode?.namedChildren?.find((c) => c.type === 'type_identifier' || c.type === 'simple_identifier')?.text;
34
+
35
+ if (isWildcard) {
36
+ ctx.imports.push(
37
+ new ImportRecord(fullPath, { symbols: ['*'], kind: 'namespace' })
38
+ );
39
+ } else {
40
+ ctx.imports.push(
41
+ new ImportRecord(fullPath, {
42
+ symbols: [lastName],
43
+ alias: alias || lastName,
44
+ kind: 'named',
45
+ })
46
+ );
47
+ }
25
48
  }
26
49
  break;
27
50
  }
@@ -29,6 +52,11 @@ function _walkKtNode(node, ctx, parentClassName) {
29
52
  case 'class_declaration': {
30
53
  const classInfo = _parseKtClass(child);
31
54
  ctx.classes.push(classInfo);
55
+
56
+ // Phase 5.3: Extract primary constructor parameter properties (Kotlin DI pattern)
57
+ // class UserService(private val repo: UserRepo) → property { name: 'repo', typeAnnotation: 'UserRepo' }
58
+ _extractKtConstructorProperties(child, ctx, classInfo.name);
59
+
32
60
  const body = child.namedChildren.find((c) => c.type === 'class_body');
33
61
  if (body) {
34
62
  _walkKtClassBody(body, ctx, classInfo.name);
@@ -107,6 +135,8 @@ function _walkKtClassBody(body, ctx, className) {
107
135
  const inner = _parseKtClass(child);
108
136
  inner.outerClass = className;
109
137
  ctx.classes.push(inner);
138
+ // Phase 5.3: Extract primary constructor params for inner classes too
139
+ _extractKtConstructorProperties(child, ctx, inner.name);
110
140
  const innerBody = child.namedChildren.find((c) => c.type === 'class_body');
111
141
  if (innerBody) {
112
142
  _walkKtClassBody(innerBody, ctx, inner.name);
@@ -246,6 +276,50 @@ function _parseKtProperty(node, className) {
246
276
  const isLazy = node.text.includes('by lazy');
247
277
  const isLateinit = node.text.includes('lateinit');
248
278
 
279
+ // Phase 5.3: Extract property type for DI resolution
280
+ // Kotlin property types can be in variable_declaration or directly on the node:
281
+ // val userRepo: UserRepo → variable_declaration { simple_identifier, user_type }
282
+ // lateinit var service: UserService → user_type directly on property_declaration
283
+ let typeAnnotation = null;
284
+
285
+ // 1. Try from variable_declaration child
286
+ const varDecl = node.namedChildren.find((c) => c.type === 'variable_declaration');
287
+ if (varDecl) {
288
+ const typeNode = varDecl.namedChildren.find(
289
+ (c) => c.type === 'user_type' || c.type === 'nullable_type'
290
+ );
291
+ if (typeNode) {
292
+ typeAnnotation = _extractKtTypeName(typeNode);
293
+ }
294
+ }
295
+
296
+ // 2. Fallback: try direct children (lateinit var patterns)
297
+ if (!typeAnnotation) {
298
+ const typeNode = node.namedChildren.find(
299
+ (c) => c.type === 'user_type' || c.type === 'nullable_type'
300
+ );
301
+ if (typeNode) {
302
+ typeAnnotation = _extractKtTypeName(typeNode);
303
+ }
304
+ }
305
+
306
+ // Phase 5.3: Extract annotations on property (e.g. @Inject, @Autowired)
307
+ const annotations = [];
308
+ for (const child of node.namedChildren) {
309
+ if (child.type === 'annotation' || child.type === 'single_annotation') {
310
+ annotations.push(child.text);
311
+ }
312
+ }
313
+ // Also check modifiers block
314
+ const modifiers = node.namedChildren.find((c) => c.type === 'modifiers');
315
+ if (modifiers) {
316
+ for (const child of modifiers.namedChildren) {
317
+ if (child.type === 'annotation' || child.type === 'single_annotation') {
318
+ annotations.push(child.text);
319
+ }
320
+ }
321
+ }
322
+
249
323
  return {
250
324
  name,
251
325
  className,
@@ -253,10 +327,105 @@ function _parseKtProperty(node, className) {
253
327
  isMutable: isVar,
254
328
  isLazy,
255
329
  isLateinit,
330
+ typeAnnotation,
331
+ annotations: annotations.length > 0 ? annotations : undefined,
256
332
  line: node.startPosition.row + 1,
257
333
  };
258
334
  }
259
335
 
336
+ /**
337
+ * Phase 5.3: Extract Kotlin primary constructor parameter properties
338
+ *
339
+ * Kotlin constructors: class Svc(private val repo: UserRepo, val logger: Logger)
340
+ * Each val/var parameter in a primary_constructor becomes a class property.
341
+ *
342
+ * AST: class_declaration → primary_constructor → class_parameter[]
343
+ * Each class_parameter may contain: modifiers, val/var keyword, simple_identifier, user_type
344
+ *
345
+ * @param {object} classNode — class_declaration AST node
346
+ * @param {object} ctx — walker context
347
+ * @param {string} className
348
+ */
349
+ function _extractKtConstructorProperties(classNode, ctx, className) {
350
+ const primaryCtor = classNode.namedChildren.find(
351
+ (c) => c.type === 'primary_constructor'
352
+ );
353
+ if (!primaryCtor) return;
354
+
355
+ for (const param of primaryCtor.namedChildren) {
356
+ if (param.type !== 'class_parameter') continue;
357
+
358
+ // Only val/var params become properties
359
+ const text = param.text;
360
+ const isVal = text.includes('val ');
361
+ const isVar = text.includes('var ');
362
+ if (!isVal && !isVar) continue;
363
+
364
+ const name = param.namedChildren.find(
365
+ (c) => c.type === 'simple_identifier'
366
+ )?.text;
367
+ if (!name) continue;
368
+
369
+ // Extract type annotation
370
+ let typeAnnotation = null;
371
+ const typeNode = param.namedChildren.find(
372
+ (c) => c.type === 'user_type' || c.type === 'nullable_type'
373
+ );
374
+ if (typeNode) {
375
+ typeAnnotation = _extractKtTypeName(typeNode);
376
+ }
377
+
378
+ // Extract annotations (e.g. @Inject)
379
+ const annotations = [];
380
+ for (const child of param.namedChildren) {
381
+ if (child.type === 'annotation' || child.type === 'single_annotation' || child.type === 'modifiers') {
382
+ if (child.type === 'modifiers') {
383
+ for (const mod of child.namedChildren) {
384
+ if (mod.type === 'annotation' || mod.type === 'single_annotation') {
385
+ annotations.push(mod.text);
386
+ }
387
+ }
388
+ } else {
389
+ annotations.push(child.text);
390
+ }
391
+ }
392
+ }
393
+
394
+ ctx.properties.push({
395
+ name,
396
+ className,
397
+ isConstant: isVal,
398
+ isMutable: isVar,
399
+ isLazy: false,
400
+ isLateinit: false,
401
+ isConstructorParam: true,
402
+ typeAnnotation,
403
+ annotations: annotations.length > 0 ? annotations : undefined,
404
+ line: param.startPosition.row + 1,
405
+ });
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Phase 5.3: Extract Kotlin type name from user_type or nullable_type node
411
+ * Strips generics and nullable marker
412
+ */
413
+ function _extractKtTypeName(typeNode) {
414
+ if (typeNode.type === 'nullable_type') {
415
+ const inner = typeNode.namedChildren.find((c) => c.type === 'user_type');
416
+ if (inner) {
417
+ const text = inner.text;
418
+ const bracketIdx = text.indexOf('<');
419
+ return bracketIdx > 0 ? text.slice(0, bracketIdx) : text;
420
+ }
421
+ return null;
422
+ }
423
+ // user_type
424
+ const text = typeNode.text;
425
+ const bracketIdx = text.indexOf('<');
426
+ return bracketIdx > 0 ? text.slice(0, bracketIdx) : text;
427
+ }
428
+
260
429
  function _collectTypeRefs(node) {
261
430
  const refs = [];
262
431
  function walk(n) {
@@ -401,6 +570,165 @@ function _maxNesting(node, depth) {
401
570
  return max;
402
571
  }
403
572
 
573
+ // ── Kotlin Call Site 提取 (Phase 5) ──────────────────────────
574
+
575
+ /**
576
+ * 从 Kotlin AST root 提取所有调用点
577
+ */
578
+ function extractCallSitesKotlin(root, ctx, _lang) {
579
+ const scopes = _collectKtScopes(root);
580
+ for (const scope of scopes) {
581
+ _extractKtCallSitesFromBody(scope.body, scope.className, scope.methodName, ctx);
582
+ }
583
+ }
584
+
585
+ /**
586
+ * 递归收集 Kotlin 中所有函数体作用域
587
+ */
588
+ function _collectKtScopes(root) {
589
+ const scopes = [];
590
+
591
+ function visit(node, className) {
592
+ for (let i = 0; i < node.namedChildCount; i++) {
593
+ const child = node.namedChild(i);
594
+
595
+ if (child.type === 'class_declaration' || child.type === 'object_declaration') {
596
+ const name = child.namedChildren.find(
597
+ (c) => c.type === 'type_identifier' || c.type === 'simple_identifier'
598
+ )?.text;
599
+ const body = child.namedChildren.find((c) => c.type === 'class_body');
600
+ if (body) {
601
+ visit(body, name || className);
602
+ }
603
+ } else if (child.type === 'companion_object') {
604
+ const body = child.namedChildren.find((c) => c.type === 'class_body');
605
+ if (body) {
606
+ visit(body, className);
607
+ }
608
+ } else if (child.type === 'function_declaration') {
609
+ const name = child.namedChildren.find((c) => c.type === 'simple_identifier')?.text || 'unknown';
610
+ const body = child.namedChildren.find((c) => c.type === 'function_body' || c.type === 'block');
611
+ if (body) {
612
+ scopes.push({ body, className, methodName: name });
613
+ }
614
+ } else if (child.type === 'property_declaration') {
615
+ // property with getter/setter or initializer with lambda
616
+ const getter = child.namedChildren.find((c) => c.type === 'getter');
617
+ const setter = child.namedChildren.find((c) => c.type === 'setter');
618
+ const propName = child.namedChildren.find((c) => c.type === 'simple_identifier' || c.type === 'variable_declaration')?.text;
619
+ if (getter) {
620
+ const body = getter.namedChildren.find((c) => c.type === 'function_body' || c.type === 'block');
621
+ if (body) scopes.push({ body, className, methodName: `get_${propName || 'prop'}` });
622
+ }
623
+ if (setter) {
624
+ const body = setter.namedChildren.find((c) => c.type === 'function_body' || c.type === 'block');
625
+ if (body) scopes.push({ body, className, methodName: `set_${propName || 'prop'}` });
626
+ }
627
+ }
628
+ }
629
+ }
630
+
631
+ visit(root, null);
632
+ return scopes;
633
+ }
634
+
635
+ /**
636
+ * 从 Kotlin function body 中递归提取调用点
637
+ */
638
+ function _extractKtCallSitesFromBody(bodyNode, className, methodName, ctx) {
639
+ if (!bodyNode) return;
640
+
641
+ const KT_NOISE = new Set([
642
+ 'println', 'print', 'require', 'check', 'error', 'TODO',
643
+ 'listOf', 'mapOf', 'setOf', 'mutableListOf', 'mutableMapOf', 'mutableSetOf',
644
+ 'arrayOf', 'intArrayOf', 'emptyList', 'emptyMap', 'emptySet',
645
+ 'lazy', 'repeat', 'run', 'let', 'also', 'apply', 'with',
646
+ ]);
647
+
648
+ function walk(node) {
649
+ if (!node || node.type === 'ERROR' || node.isMissing) return;
650
+
651
+ if (node.type === 'call_expression') {
652
+ const func = node.namedChildren[0];
653
+ if (!func) { walkChildren(node); return; }
654
+
655
+ let callee, receiver = null, receiverType = null, callType;
656
+
657
+ if (func.type === 'navigation_expression') {
658
+ // obj.method() or Pkg.func()
659
+ const parts = func.text.split('.');
660
+ if (parts.length >= 2) {
661
+ receiver = parts.slice(0, -1).join('.');
662
+ callee = parts[parts.length - 1];
663
+ if (receiver === 'this' || receiver === 'self') {
664
+ receiverType = className;
665
+ callType = 'method';
666
+ } else if (receiver === 'super') {
667
+ receiverType = className;
668
+ callType = 'super';
669
+ } else if (/^[A-Z]/.test(receiver)) {
670
+ receiverType = receiver;
671
+ callType = 'static';
672
+ } else {
673
+ callType = 'method';
674
+ receiverType = null;
675
+ }
676
+ } else {
677
+ callee = func.text;
678
+ callType = 'function';
679
+ }
680
+ } else if (func.type === 'simple_identifier') {
681
+ callee = func.text;
682
+ if (KT_NOISE.has(callee)) {
683
+ walkChildren(node);
684
+ return;
685
+ }
686
+ // PascalCase → constructor
687
+ callType = /^[A-Z]/.test(callee) ? 'constructor' : 'function';
688
+ if (callType === 'constructor') {
689
+ receiverType = callee;
690
+ }
691
+ } else {
692
+ callee = func.text?.slice(0, 80) || 'unknown';
693
+ callType = 'function';
694
+ }
695
+
696
+ // 计算参数数量
697
+ const valueArgs = node.namedChildren.find((c) => c.type === 'call_suffix' || c.type === 'value_arguments');
698
+ const argCount = valueArgs ? valueArgs.namedChildCount : 0;
699
+
700
+ ctx.callSites.push({
701
+ callee,
702
+ callerMethod: methodName,
703
+ callerClass: className,
704
+ callType,
705
+ receiver,
706
+ receiverType,
707
+ argCount,
708
+ line: node.startPosition.row + 1,
709
+ isAwait: false,
710
+ });
711
+
712
+ // walk arguments for nested calls
713
+ if (valueArgs) walkChildren(valueArgs);
714
+ // also check trailing lambda
715
+ const lambda = node.namedChildren.find((c) => c.type === 'annotated_lambda' || c.type === 'lambda_literal');
716
+ if (lambda) walkChildren(lambda);
717
+ return;
718
+ }
719
+
720
+ walkChildren(node);
721
+ }
722
+
723
+ function walkChildren(node) {
724
+ for (let i = 0; i < node.namedChildCount; i++) {
725
+ walk(node.namedChild(i));
726
+ }
727
+ }
728
+
729
+ walk(bodyNode);
730
+ }
731
+
404
732
  // ── 插件导出 ──
405
733
 
406
734
  let _grammar = null;
@@ -415,5 +743,6 @@ export const plugin = {
415
743
  getGrammar,
416
744
  walk: walkKotlin,
417
745
  detectPatterns: detectKtPatterns,
746
+ extractCallSites: extractCallSitesKotlin,
418
747
  extensions: ['.kt', '.kts'],
419
748
  };
@@ -4,8 +4,13 @@
4
4
  *
5
5
  * 提取: class, function, import, decorator, docstring, module-level assignments
6
6
  * 模式: Singleton, Factory, Context Manager, Decorator pattern, Data Class
7
+ *
8
+ * Phase 5: 新增 ImportRecord 结构化导入 + extractCallSites 调用点提取
7
9
  */
8
10
 
11
+ import { ImportRecord } from '../analysis/ImportRecord.js';
12
+ import { extractCallSitesPython } from '../analysis/CallSiteExtractor.js';
13
+
9
14
  function walkPython(root, ctx) {
10
15
  _walkPyNode(root, ctx, null);
11
16
  }
@@ -18,7 +23,14 @@ function _walkPyNode(node, ctx, parentClassName) {
18
23
  case 'import_statement': {
19
24
  const modNode = child.namedChildren.find((c) => c.type === 'dotted_name');
20
25
  if (modNode) {
21
- ctx.imports.push(modNode.text);
26
+ // import mod / import mod as alias
27
+ const aliasNode = child.namedChildren.find((c) => c.type === 'aliased_import');
28
+ const alias = aliasNode?.namedChildren?.find((c) => c.type === 'identifier')?.text || null;
29
+ ctx.imports.push(new ImportRecord(modNode.text, {
30
+ symbols: ['*'],
31
+ alias: alias || modNode.text.split('.').pop(),
32
+ kind: 'namespace',
33
+ }));
22
34
  }
23
35
  break;
24
36
  }
@@ -28,7 +40,23 @@ function _walkPyNode(node, ctx, parentClassName) {
28
40
  (c) => c.type === 'dotted_name' || c.type === 'relative_import'
29
41
  );
30
42
  if (modNode) {
31
- ctx.imports.push(modNode.text);
43
+ const importPath = modNode.text;
44
+ // from mod import A, B, C
45
+ const importedNames = [];
46
+ for (const c of child.namedChildren) {
47
+ if (c.type === 'dotted_name' && c !== modNode) {
48
+ importedNames.push(c.text);
49
+ } else if (c.type === 'aliased_import') {
50
+ const nameNode = c.namedChildren.find((n) => n.type === 'dotted_name' || n.type === 'identifier');
51
+ if (nameNode) importedNames.push(nameNode.text);
52
+ } else if (c.type === 'wildcard_import') {
53
+ importedNames.push('*');
54
+ }
55
+ }
56
+ ctx.imports.push(new ImportRecord(importPath, {
57
+ symbols: importedNames.length > 0 ? importedNames : [],
58
+ kind: importedNames.includes('*') ? 'namespace' : 'named',
59
+ }));
32
60
  }
33
61
  break;
34
62
  }
@@ -363,5 +391,6 @@ export const plugin = {
363
391
  getGrammar,
364
392
  walk: walkPython,
365
393
  detectPatterns: detectPyPatterns,
394
+ extractCallSites: extractCallSitesPython,
366
395
  extensions: ['.py'],
367
396
  };