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
@@ -4,8 +4,13 @@
4
4
  *
5
5
  * 提取: class, interface, type alias, enum, function, method, property, import, export
6
6
  * 模式检测: Singleton, Factory, Observer, React Hook/Component, Middleware, Decorator
7
+ *
8
+ * Phase 5: 新增 ImportRecord 结构化导入 + extractCallSites 调用点提取
7
9
  */
8
10
 
11
+ import { ImportRecord } from '../analysis/ImportRecord.js';
12
+ import { extractCallSitesTS } from '../analysis/CallSiteExtractor.js';
13
+
9
14
  function walkTypeScript(root, ctx) {
10
15
  _walkTSNode(root, ctx, null);
11
16
  }
@@ -20,7 +25,9 @@ function _walkTSNode(node, ctx, parentClassName) {
20
25
  (c) => c.type === 'string' || c.type === 'string_fragment'
21
26
  );
22
27
  if (source) {
23
- ctx.imports.push(source.text.replace(/^['"]|['"]$/g, ''));
28
+ const importPath = source.text.replace(/^['"]|['"]$/g, '');
29
+ const importMeta = _parseImportClause(child);
30
+ ctx.imports.push(new ImportRecord(importPath, importMeta));
24
31
  }
25
32
  break;
26
33
  }
@@ -123,6 +130,12 @@ function _walkTSClassBody(body, ctx, className) {
123
130
  case 'method_definition': {
124
131
  const m = _parseTSMethod(child, className);
125
132
  ctx.methods.push(m);
133
+
134
+ // Phase 5.3: Extract constructor parameter properties (DI pattern)
135
+ if (m.name === 'constructor') {
136
+ const constructorProps = _extractTSConstructorProperties(child, className);
137
+ ctx.properties.push(...constructorProps);
138
+ }
126
139
  break;
127
140
  }
128
141
 
@@ -287,15 +300,111 @@ function _parseTSProperty(node, className) {
287
300
  const isStatic = node.text.trimStart().startsWith('static');
288
301
  const isReadonly = node.text.includes('readonly');
289
302
 
303
+ // Phase 5.3: Extract type annotation for DI/RTA resolution
304
+ const typeAnnotation = _extractTypeAnnotation(node);
305
+
306
+ // Phase 5.3: Extract decorators on properties (e.g. @Inject)
307
+ const decorators = [];
308
+ for (const child of node.namedChildren) {
309
+ if (child.type === 'decorator') {
310
+ decorators.push(child.text);
311
+ }
312
+ }
313
+
290
314
  return {
291
315
  name,
292
316
  className,
293
317
  isStatic,
294
318
  isReadonly,
319
+ typeAnnotation,
320
+ decorators: decorators.length > 0 ? decorators : undefined,
295
321
  line: node.startPosition.row + 1,
296
322
  };
297
323
  }
298
324
 
325
+ /**
326
+ * Phase 5.3: Extract type name from a type_annotation node
327
+ * Handles: type_identifier, generic_type, nested_type_identifier, union_type
328
+ * Strips generics: UserRepo<T> → UserRepo
329
+ *
330
+ * @param {object} parentNode — AST node that may contain a type_annotation child
331
+ * @returns {string|null}
332
+ */
333
+ function _extractTypeAnnotation(parentNode) {
334
+ const typeAnnotNode = parentNode.namedChildren.find((c) => c.type === 'type_annotation');
335
+ if (!typeAnnotNode) return null;
336
+
337
+ const typeNode = typeAnnotNode.namedChildren.find(
338
+ (c) =>
339
+ c.type === 'type_identifier' ||
340
+ c.type === 'generic_type' ||
341
+ c.type === 'nested_type_identifier'
342
+ );
343
+ if (!typeNode) return null;
344
+
345
+ // Strip generics: Repository<User> → Repository
346
+ const text = typeNode.text;
347
+ const bracketIdx = text.indexOf('<');
348
+ return bracketIdx > 0 ? text.slice(0, bracketIdx) : text;
349
+ }
350
+
351
+ /**
352
+ * Phase 5.3: Extract constructor parameter properties (TypeScript DI pattern)
353
+ *
354
+ * TypeScript shorthand: constructor(private userRepo: UserRepo)
355
+ * creates a class property `userRepo` with type `UserRepo`
356
+ *
357
+ * @param {object} constructorNode — method_definition node with name "constructor"
358
+ * @param {string} className
359
+ * @returns {Array} — property objects
360
+ */
361
+ function _extractTSConstructorProperties(constructorNode, className) {
362
+ const properties = [];
363
+ const params = constructorNode.namedChildren.find((c) => c.type === 'formal_parameters');
364
+ if (!params) return properties;
365
+
366
+ for (const param of params.namedChildren) {
367
+ if (param.type !== 'required_parameter' && param.type !== 'optional_parameter') continue;
368
+
369
+ // Check for accessibility modifier (public, private, protected) or readonly
370
+ // These turn constructor params into class properties
371
+ const hasAccessibility = param.namedChildren.some(
372
+ (c) => c.type === 'accessibility_modifier' || c.type === 'override_modifier'
373
+ );
374
+ const hasReadonly = param.text.includes('readonly');
375
+
376
+ if (!hasAccessibility && !hasReadonly) continue; // Not a property declaration
377
+
378
+ const nameNode = param.namedChildren.find((c) => c.type === 'identifier');
379
+ const name = nameNode?.text;
380
+ if (!name) continue;
381
+
382
+ // Extract type annotation
383
+ const typeAnnotation = _extractTypeAnnotation(param);
384
+
385
+ // Extract decorators on parameter (e.g. @Inject)
386
+ const decorators = [];
387
+ for (const child of param.namedChildren) {
388
+ if (child.type === 'decorator') {
389
+ decorators.push(child.text);
390
+ }
391
+ }
392
+
393
+ properties.push({
394
+ name,
395
+ className,
396
+ isStatic: false,
397
+ isReadonly: hasReadonly,
398
+ typeAnnotation,
399
+ isConstructorParam: true,
400
+ decorators: decorators.length > 0 ? decorators : undefined,
401
+ line: param.startPosition.row + 1,
402
+ });
403
+ }
404
+
405
+ return properties;
406
+ }
407
+
299
408
  function _parseTSVariableDecl(node, ctx, parentClassName) {
300
409
  for (const child of node.namedChildren) {
301
410
  if (child.type === 'variable_declarator') {
@@ -315,9 +424,187 @@ function _parseTSVariableDecl(node, ctx, parentClassName) {
315
424
  line: child.startPosition.row + 1,
316
425
  kind: 'definition',
317
426
  });
427
+ continue;
428
+ }
429
+
430
+ // CJS require(): const x = require('path') / const { a, b } = require('path')
431
+ // Dynamic import(): const mod = await import('./module')
432
+ const callNode = child.namedChildren.find((c) => c.type === 'call_expression');
433
+ if (callNode) {
434
+ const requireImport = _parseCJSRequire(callNode, child);
435
+ if (requireImport) {
436
+ ctx.imports.push(requireImport);
437
+ continue;
438
+ }
439
+ const dynamicImport = _parseDynamicImport(callNode, child);
440
+ if (dynamicImport) {
441
+ ctx.imports.push(dynamicImport);
442
+ continue;
443
+ }
444
+ }
445
+ // await import() — await wraps the call_expression
446
+ const awaitNode = child.namedChildren.find((c) => c.type === 'await_expression');
447
+ if (awaitNode) {
448
+ const awaitedCall = awaitNode.namedChildren.find((c) => c.type === 'call_expression');
449
+ if (awaitedCall) {
450
+ const dynamicImport = _parseDynamicImport(awaitedCall, child);
451
+ if (dynamicImport) {
452
+ ctx.imports.push(dynamicImport);
453
+ }
454
+ }
455
+ }
456
+ }
457
+ }
458
+ }
459
+
460
+ // ── TS/JS Import 解析 ──
461
+
462
+ /**
463
+ * 从 import_statement 节点解析导入子句的结构化信息
464
+ *
465
+ * @param {TreeSitterNode} importNode — import_statement 节点
466
+ * @returns {{ symbols: string[], kind: string, alias: string|null, isTypeOnly: boolean }}
467
+ */
468
+ function _parseImportClause(importNode) {
469
+ const symbols = [];
470
+ let kind = 'side-effect';
471
+ let alias = null;
472
+ let isTypeOnly = false;
473
+
474
+ // 检查 type-only import: import type { ... }
475
+ if (importNode.text.trimStart().startsWith('import type')) {
476
+ isTypeOnly = true;
477
+ }
478
+
479
+ for (const child of importNode.namedChildren) {
480
+ if (child.type === 'import_clause') {
481
+ for (const clauseChild of child.namedChildren) {
482
+ if (clauseChild.type === 'identifier') {
483
+ // default import: import Foo from '...'
484
+ symbols.push(clauseChild.text);
485
+ kind = 'default';
486
+ } else if (clauseChild.type === 'named_imports') {
487
+ // named imports: import { A, B as C } from '...'
488
+ kind = 'named';
489
+ for (const specifier of clauseChild.namedChildren) {
490
+ if (specifier.type === 'import_specifier') {
491
+ // 收集 specifier 中的所有 identifier / type_identifier
492
+ const identifiers = specifier.namedChildren.filter(
493
+ (c) => c.type === 'identifier' || c.type === 'type_identifier'
494
+ );
495
+ // import { A as B } → identifiers = [A, B] → push B (本地名)
496
+ // import { A } → identifiers = [A] → push A
497
+ if (identifiers.length > 0) {
498
+ symbols.push(identifiers[identifiers.length - 1].text);
499
+ }
500
+ }
501
+ }
502
+ } else if (clauseChild.type === 'namespace_import') {
503
+ // namespace import: import * as M from '...'
504
+ kind = 'namespace';
505
+ symbols.push('*');
506
+ const aliasNode = clauseChild.namedChildren.find((c) => c.type === 'identifier');
507
+ if (aliasNode) {
508
+ alias = aliasNode.text;
509
+ }
510
+ }
511
+ }
512
+ }
513
+ }
514
+
515
+ return { symbols, kind, alias, isTypeOnly };
516
+ }
517
+
518
+ /**
519
+ * 解析 CJS require() 调用: const x = require('path') / const { a, b } = require('path')
520
+ *
521
+ * @param {TreeSitterNode} callNode — call_expression 节点
522
+ * @param {TreeSitterNode} declaratorNode — variable_declarator 节点 (包含 lhs 绑定)
523
+ * @returns {ImportRecord|null}
524
+ */
525
+ function _parseCJSRequire(callNode, declaratorNode) {
526
+ // 检查 callee 是否为 'require'
527
+ const callee = callNode.namedChildren[0];
528
+ if (!callee || callee.type !== 'identifier' || callee.text !== 'require') return null;
529
+
530
+ // 提取 require 参数中的路径字符串
531
+ const args = callNode.namedChildren.find((c) => c.type === 'arguments');
532
+ if (!args || args.namedChildCount === 0) return null;
533
+
534
+ const firstArg = args.namedChildren[0];
535
+ if (!firstArg || (firstArg.type !== 'string' && firstArg.type !== 'template_string')) return null;
536
+
537
+ const importPath = firstArg.text.replace(/^['"`]|['"`]$/g, '');
538
+ if (!importPath) return null;
539
+
540
+ // 解析 lhs 绑定模式
541
+ const lhs = declaratorNode.namedChildren[0]; // identifier or object_pattern
542
+ if (!lhs) return new ImportRecord(importPath, { symbols: [], kind: 'side-effect' });
543
+
544
+ if (lhs.type === 'identifier') {
545
+ // const express = require('express') → namespace import
546
+ return new ImportRecord(importPath, {
547
+ symbols: ['*'],
548
+ kind: 'namespace',
549
+ alias: lhs.text,
550
+ });
551
+ }
552
+
553
+ if (lhs.type === 'object_pattern') {
554
+ // const { readFile, writeFile } = require('fs')
555
+ const symbols = [];
556
+ for (const prop of lhs.namedChildren) {
557
+ if (prop.type === 'shorthand_property_identifier_pattern' || prop.type === 'shorthand_property_identifier') {
558
+ symbols.push(prop.text);
559
+ } else if (prop.type === 'pair_pattern' || prop.type === 'pair') {
560
+ // { readFile: rf } → 使用本地名 rf
561
+ const identifiers = prop.namedChildren.filter((c) => c.type === 'identifier');
562
+ if (identifiers.length > 0) {
563
+ symbols.push(identifiers[identifiers.length - 1].text);
564
+ }
318
565
  }
319
566
  }
567
+ return new ImportRecord(importPath, {
568
+ symbols: symbols.length > 0 ? symbols : ['*'],
569
+ kind: symbols.length > 0 ? 'named' : 'namespace',
570
+ });
320
571
  }
572
+
573
+ // 其他 lhs 模式 (如数组解构), 作为 side-effect 处理
574
+ return new ImportRecord(importPath, { symbols: [], kind: 'side-effect' });
575
+ }
576
+
577
+ /**
578
+ * 解析动态 import() 表达式: const mod = await import('./module')
579
+ *
580
+ * @param {TreeSitterNode} callNode — call_expression 节点
581
+ * @param {TreeSitterNode} declaratorNode — variable_declarator 节点
582
+ * @returns {ImportRecord|null}
583
+ */
584
+ function _parseDynamicImport(callNode, declaratorNode) {
585
+ // 动态 import() 在 tree-sitter 中解析为 call_expression, callee 是 'import'
586
+ const callee = callNode.namedChildren[0];
587
+ if (!callee) return null;
588
+ // tree-sitter 可能将 import() 解析为 identifier('import') 或 special node
589
+ if (callee.text !== 'import') return null;
590
+
591
+ const args = callNode.namedChildren.find((c) => c.type === 'arguments');
592
+ if (!args || args.namedChildCount === 0) return null;
593
+
594
+ const firstArg = args.namedChildren[0];
595
+ if (!firstArg || (firstArg.type !== 'string' && firstArg.type !== 'template_string')) return null;
596
+
597
+ const importPath = firstArg.text.replace(/^['"`]|['"`]$/g, '');
598
+ if (!importPath) return null;
599
+
600
+ const lhs = declaratorNode?.namedChildren?.[0];
601
+ const alias = lhs?.type === 'identifier' ? lhs.text : null;
602
+
603
+ return new ImportRecord(importPath, {
604
+ symbols: ['*'],
605
+ kind: 'dynamic',
606
+ alias,
607
+ });
321
608
  }
322
609
 
323
610
  // ── TS/JS 模式检测 ──
@@ -477,6 +764,7 @@ export const plugin = {
477
764
  getGrammar,
478
765
  walk: walkTypeScript,
479
766
  detectPatterns: detectTSPatterns,
767
+ extractCallSites: extractCallSitesTS,
480
768
  extensions: ['.ts'],
481
769
  };
482
770
 
@@ -493,5 +781,6 @@ export const tsxPlugin = {
493
781
  getGrammar: getTsxGrammar,
494
782
  walk: walkTypeScript,
495
783
  detectPatterns: detectTSPatterns,
784
+ extractCallSites: extractCallSitesTS,
496
785
  extensions: ['.tsx'],
497
786
  };
@@ -11,7 +11,7 @@ import { JvmDiscoverer } from './JvmDiscoverer.js';
11
11
  import { NodeDiscoverer } from './NodeDiscoverer.js';
12
12
  import { PythonDiscoverer } from './PythonDiscoverer.js';
13
13
  import { RustDiscoverer } from './RustDiscoverer.js';
14
- import { SpmDiscoverer } from './SpmDiscoverer.js';
14
+ import { SpmDiscoverer } from '../../platform/ios/spm/SpmDiscoverer.js';
15
15
 
16
16
  /** @type {DiscovererRegistry|null} */
17
17
  let _registry = null;
@@ -53,4 +53,4 @@ export { NodeDiscoverer } from './NodeDiscoverer.js';
53
53
  export { ProjectDiscoverer } from './ProjectDiscoverer.js';
54
54
  export { PythonDiscoverer } from './PythonDiscoverer.js';
55
55
  export { RustDiscoverer } from './RustDiscoverer.js';
56
- export { SpmDiscoverer } from './SpmDiscoverer.js';
56
+ export { SpmDiscoverer } from '../../platform/ios/spm/SpmDiscoverer.js';
@@ -20,6 +20,51 @@ export class AiProvider {
20
20
  this._circuitThreshold = config.circuitThreshold || 5; // 触发熔断的连续失败次数
21
21
  this._circuitOpenedAt = 0; // 熔断打开时间
22
22
  this._circuitCooldownMs = 30_000; // 初始冷却 30 秒
23
+
24
+ // ── Provider 级全局并发闸门 + 429 冷却窗 ──
25
+ this._maxConcurrency = Math.max(1, Number(config.maxConcurrency || process.env.ASD_AI_MAX_CONCURRENCY || 4));
26
+ this._activeRequests = 0;
27
+ this._requestQueue = [];
28
+ this._rateLimitedUntil = 0;
29
+ }
30
+
31
+ async _acquireRequestSlot() {
32
+ if (this._activeRequests < this._maxConcurrency) {
33
+ this._activeRequests += 1;
34
+ return;
35
+ }
36
+ await new Promise((resolve) => this._requestQueue.push(resolve));
37
+ this._activeRequests += 1;
38
+ }
39
+
40
+ _releaseRequestSlot() {
41
+ this._activeRequests = Math.max(0, this._activeRequests - 1);
42
+ const next = this._requestQueue.shift();
43
+ if (next) {
44
+ next();
45
+ }
46
+ }
47
+
48
+ async _waitForRateLimitWindow() {
49
+ const waitMs = (this._rateLimitedUntil || 0) - Date.now();
50
+ if (waitMs > 0) {
51
+ await new Promise((r) => setTimeout(r, waitMs));
52
+ }
53
+ }
54
+
55
+ _setRateLimitWindow(waitMs) {
56
+ const safeWait = Math.max(0, Number(waitMs) || 0);
57
+ if (safeWait <= 0) {
58
+ return;
59
+ }
60
+ const until = Date.now() + safeWait;
61
+ if (until > (this._rateLimitedUntil || 0)) {
62
+ this._rateLimitedUntil = until;
63
+ this._log?.(
64
+ 'warn',
65
+ `[RateLimit] ${this.name} enters cooldown ${Math.round(safeWait / 1000)}s (global)`
66
+ );
67
+ }
23
68
  }
24
69
 
25
70
  /**
@@ -81,9 +126,9 @@ export class AiProvider {
81
126
  * 带工具声明的结构化对话 — 原生函数调用 API
82
127
  *
83
128
  * 支持原生函数调用的 Provider(Gemini / OpenAI / Claude)覆盖此方法,
84
- * 返回结构化 functionCall 而非文本,ChatAgent 据此跳过正则解析。
129
+ * 返回结构化 functionCall 而非文本,AgentRuntime 据此跳过正则解析。
85
130
  *
86
- * 默认实现降级为 chat(),由 ChatAgent 进行文本解析。
131
+ * 默认实现降级为 chat(),由 AgentRuntime 进行文本解析。
87
132
  *
88
133
  * 统一消息格式 (Provider-Agnostic):
89
134
  * - { role: 'user', content: 'text' }
@@ -150,35 +195,6 @@ export class AiProvider {
150
195
  return this.extractJSON(response, openChar, closeChar);
151
196
  }
152
197
 
153
- /**
154
- * 从源码文件批量提取 Recipe 结构(AI 驱动)
155
- * 默认实现使用 chat() + 标准提示词;子类可覆盖以使用专用 API
156
- * @param {string} targetName - SPM Target 名称
157
- * @param {Array<{name:string,content:string}>} filesContent
158
- * @param {object} [options] - 可选参数
159
- * @returns {Promise<Array<object>>}
160
- */
161
- async extractRecipes(targetName, filesContent, options = {}) {
162
- const prompt = this._buildExtractPrompt(targetName, filesContent, options);
163
- const parsed = await this.chatWithStructuredOutput(prompt, {
164
- openChar: '[',
165
- closeChar: ']',
166
- temperature: 0.3,
167
- maxTokens: 32768,
168
- });
169
- if (!Array.isArray(parsed)) {
170
- this._log(
171
- 'warn',
172
- `[extractRecipes] structured output parse failed for target: ${targetName}`
173
- );
174
- return [];
175
- }
176
- if (parsed.length === 0) {
177
- this._log('info', `[extractRecipes] AI returned empty array for target: ${targetName}`);
178
- }
179
- return parsed;
180
- }
181
-
182
198
  /**
183
199
  * 内部日志辅助(子类可通过 this.logger 覆盖)
184
200
  */
@@ -193,147 +209,6 @@ export class AiProvider {
193
209
  }
194
210
  }
195
211
 
196
- /**
197
- * 构建 extractRecipes 标准提示词(语言自适应 + Skill 增强)
198
- */
199
- _buildExtractPrompt(targetName, filesContent, options = {}) {
200
- const files = filesContent.map((f) => `--- FILE: ${f.name} ---\n${f.content}`).join('\n\n');
201
-
202
- // 检测文件主要语言
203
- const langProfile = this._detectLanguageProfile(filesContent);
204
-
205
- // AST 代码结构分析注入 — 帮助 AI 理解继承体系、设计模式、代码规模
206
- const astSection = options.astContext
207
- ? `\n# Code Structure Analysis (AST)\nThe following is a Tree-sitter AST analysis of the project. Use this structural context to better understand class hierarchies, design patterns, and code quality when extracting recipes:\n\n${options.astContext.substring(0, 3000)}\n`
208
- : '';
209
-
210
- // 用户语言偏好 — 控制 AI 输出人类可读字段的语言
211
- const langInstruction = this._buildLangInstruction(options.lang);
212
-
213
- // comprehensive 模式:全量分析整个文件,不跳过任何有意义的方法
214
- if (options.comprehensive) {
215
- return `# Role
216
- You are a ${langProfile.role} performing a **comprehensive full-file analysis**.
217
-
218
- # Goal
219
- Thoroughly analyze ALL code in "${targetName}" and create Recipe entries for **every significant method, function, or code block**.
220
- This is a full-file analysis — do NOT skip methods just because they seem "simple" or "standard".
221
- ${astSection}
222
-
223
- # What to extract
224
- - **Every** complete method/function with 5+ lines of implementation
225
- - Initialization and configuration methods (init, viewDidLoad, setup, configure)
226
- - Event handlers and action methods
227
- - Data processing and business logic
228
- - Protocol/delegate implementations
229
- - ANY code block that a developer might reference, learn from, or reuse
230
-
231
- # Extraction Rules
232
- - Extract **BROADLY** — include all meaningful code units, not just "clever" or "novel" patterns
233
- - Each recipe must be a **complete, standalone** code unit with full signature and body
234
- - Put the complete code in \`content.pattern\`, and a meaningful project writeup in \`content.markdown\`
235
- - Preserve the file's actual code. Use \`<#placeholder#>\` ONLY for literal strings/values a developer would customize
236
- - Every recipe must be traceable to real code in the file. Do NOT invent code
237
- - Include relevant \`headers\` (import/require lines) that the code depends on
238
- - You **MUST** extract at least ONE recipe — every source file has something worth capturing
239
- - For each recipe, provide a concise \`doClause\` (imperative sentence) and a \`topicHint\` group label
240
-
241
- ${langProfile.extractionExamples}
242
-
243
- # Output (JSON Array)
244
- Each item MUST use the following V3 KnowledgeEntry structure:
245
- - title (string): Descriptive English name
246
- - description (string): 2-3 sentences explaining what this code does, written as a project-specific guide
247
- - trigger (string): @shortcut (kebab-case, e.g. "@url-parser")
248
- - kind (string): "rule" | "pattern" | "fact" — rule = always-do/never-do, pattern = reusable recipe with code, fact = reference info
249
- - knowledgeType (string): "code-pattern" | "architecture" | "best-practice" | "code-standard" | "code-relation" | "data-flow" | "event-and-data-flow" | "module-dependency" | "boundary-constraint" | "code-style" | "solution" | "anti-pattern"
250
- - complexity (string): "basic" | "intermediate" | "advanced"
251
- - scope (string): "universal" | "project-specific" | "team-convention"
252
- - category: ${langProfile.categories}
253
- - language: "${langProfile.primaryLanguage}"
254
- - content (object): { "pattern": "<complete function/method/class from the file>", "markdown": "<project-specific writeup: what this pattern does, when to use it, key design decisions>", "rationale": "<why this pattern is designed this way — design trade-offs, alternatives considered>" }
255
- - reasoning (object): { "whyStandard": "<why this is a standard/best-practice worth following>", "sources": ["<source file names>"], "confidence": <0.0-1.0> }
256
- - headers (string[]): Required import/require lines
257
- - tags (string[]): Search keywords
258
- - doClause (string): One-sentence imperative: what to do (e.g. "Use dependency injection via constructor")
259
- - dontClause (string): What NOT to do (e.g. "Don't instantiate services with new directly")
260
- - whenClause (string): When this pattern applies (e.g. "When creating a new ViewController subclass")
261
- - topicHint (string): Group label for related patterns (e.g. "Networking", "UI-Layout", "Error-Handling")
262
- - coreCode (string): Minimal 3-10 line code skeleton that captures the essence. Must be syntactically complete — balanced brackets/parentheses, never start with } or ) and never end with { or (
263
- - usageGuide (string): ### 章节格式使用指南 — 描述何时使用此模式、步骤和注意事项
264
- - constraints (object, optional): { "preconditions": ["<conditions that must be true before using this pattern>"], "sideEffects": ["<observable side effects of using this code>"], "boundaries": ["<usage limitations or scope restrictions>"] }
265
- - aiInsight (string, optional): One-sentence concise insight — the single most important takeaway about this code pattern
266
-
267
- IMPORTANT: content.pattern must contain the COMPLETE source code. content.markdown must be a meaningful project-specific writeup, NOT just a copy of description. content.rationale must explain WHY this pattern is designed this way. reasoning.whyStandard must explain WHY this pattern matters.
268
- ${langInstruction}
269
- Return ONLY a JSON array. Do NOT return an empty array.
270
-
271
- Files Content:
272
- ${files}`;
273
- }
274
-
275
- return `# Role
276
- You are a ${langProfile.role} extracting production-quality reusable code patterns.
277
-
278
- # Goal
279
- Extract meaningful, complete code patterns from "${targetName}". Each recipe must provide real value to a developer.
280
- ${astSection}
281
-
282
- # What makes a GOOD recipe
283
- - A **complete function/method** or **logical code block** (10-40 lines typically), NOT individual statements
284
- - Code that demonstrates a **real design pattern**: ${langProfile.patternExamples}
285
- - Code that a developer would actually **copy-paste and adapt** for a new feature
286
-
287
- # What makes a BAD recipe (AVOID these)
288
- - Trivial 2-3 line snippets like just a single assignment or import
289
- - Overly generic code that doesn't reflect the file's actual logic
290
- - Breaking a single function into multiple tiny recipes
291
-
292
- # Extraction Strategy
293
- For each function/method/class in the file, ask: "Would a developer benefit from having this as a reusable template?" If yes, extract the **complete unit** with its full body.
294
-
295
- ${langProfile.extractionExamples}
296
-
297
- # Rules
298
- 1. \`content.pattern\` must contain a **complete function/method or logical unit** — include the signature and full body
299
- 2. \`content.markdown\` must be a meaningful project-specific writeup explaining what this code does and when to use it
300
- 3. Preserve the file's actual code. Use \`<#placeholder#>\` ONLY for literal strings/values a developer would customize
301
- 4. Every recipe must be traceable to real code in the file. Do NOT invent code
302
- 5. Include relevant \`headers\` (import/require lines) that the code depends on
303
- 6. Every recipe must have a concise \`doClause\` and a \`topicHint\` group label
304
-
305
- # Output (JSON Array)
306
- Each item MUST use the following V3 KnowledgeEntry structure:
307
- - title (string): Descriptive English name
308
- - description (string): 2-3 sentences explaining what this code does, written as a project-specific guide
309
- - trigger (string): @shortcut (kebab-case, e.g. "@url-parser")
310
- - kind (string): "rule" | "pattern" | "fact" — rule = always-do/never-do, pattern = reusable recipe with code, fact = reference info
311
- - knowledgeType (string): "code-pattern" | "architecture" | "best-practice" | "code-standard" | "code-relation" | "data-flow" | "event-and-data-flow" | "module-dependency" | "boundary-constraint" | "code-style" | "solution" | "anti-pattern"
312
- - complexity (string): "basic" | "intermediate" | "advanced"
313
- - scope (string): "universal" | "project-specific" | "team-convention"
314
- - category: ${langProfile.categories}
315
- - language: "${langProfile.primaryLanguage}"
316
- - content (object): { "pattern": "<complete function/method/class from the file>", "markdown": "<project-specific writeup: what this pattern does, when to use it, key design decisions>", "rationale": "<why this pattern is designed this way — design trade-offs, alternatives considered>" }
317
- - reasoning (object): { "whyStandard": "<why this is a standard/best-practice worth following>", "sources": ["<source file names>"], "confidence": <0.0-1.0> }
318
- - headers (string[]): Required import/require lines
319
- - tags (string[]): Search keywords
320
- - doClause (string): One-sentence imperative: what to do (e.g. "Use dependency injection via constructor")
321
- - dontClause (string): What NOT to do (e.g. "Don't instantiate services with new directly")
322
- - whenClause (string): When this pattern applies (e.g. "When creating a new ViewController subclass")
323
- - topicHint (string): Group label (e.g. "Networking", "UI-Layout")
324
- - coreCode (string): Minimal 3-10 line code skeleton that captures the essence. Must be syntactically complete — balanced brackets/parentheses, never start with } or ) and never end with { or (
325
- - usageGuide (string): ### 章节格式使用指南 — 描述何时使用此模式、步骤和注意事项
326
- - constraints (object, optional): { "preconditions": ["<conditions that must be true before using this pattern>"], "sideEffects": ["<observable side effects of using this code>"], "boundaries": ["<usage limitations or scope restrictions>"] }
327
- - aiInsight (string, optional): One-sentence concise insight — the single most important takeaway about this code pattern
328
-
329
- IMPORTANT: content.pattern must contain the COMPLETE source code. content.markdown must be a meaningful project-specific writeup, NOT just a copy of description. content.rationale must explain WHY this pattern is designed this way. reasoning.whyStandard must explain WHY this pattern matters.
330
- ${langInstruction}
331
- Return ONLY a JSON array. If no meaningful patterns found, return [].
332
-
333
- Files Content:
334
- ${files}`;
335
- }
336
-
337
212
  /**
338
213
  * 根据用户语言偏好生成输出语言指令
339
214
  * @param {string} [lang] - 语言代码,如 'zh', 'en'
@@ -856,7 +731,12 @@ ${items}`;
856
731
  }
857
732
 
858
733
  for (let attempt = 0; attempt <= retries; attempt++) {
734
+ let slotAcquired = false;
859
735
  try {
736
+ await this._waitForRateLimitWindow();
737
+ await this._acquireRequestSlot();
738
+ slotAcquired = true;
739
+
860
740
  const result = await fn();
861
741
  // 成功 → 完全重置熔断器(包括冷却时间)
862
742
  this._circuitFailures = 0;
@@ -886,6 +766,16 @@ ${items}`;
886
766
  causeCode === 'UND_ERR_SOCKET');
887
767
  const isRetryable = err.status === 429 || err.status >= 500 || isNetworkError;
888
768
 
769
+ // 429:触发 provider 级冷却窗,抑制并发重试风暴
770
+ if (err.status === 429) {
771
+ const retryAfterMs = Number(err.retryAfterMs || 0);
772
+ const adaptiveCooldown = Math.max(
773
+ retryAfterMs,
774
+ Math.round(baseDelay * 2 ** attempt * 1.5 + Math.random() * 1000)
775
+ );
776
+ this._setRateLimitWindow(adaptiveCooldown);
777
+ }
778
+
889
779
  // 首次失败记录详细诊断(含 cause)
890
780
  if (attempt === 0 && (isNetworkError || err.cause)) {
891
781
  this._log?.(
@@ -921,6 +811,10 @@ ${items}`;
921
811
  `[_withRetry] attempt ${attempt + 1} failed (${err.message}), retrying in ${Math.round(delay / 1000)}s…`
922
812
  );
923
813
  await new Promise((r) => setTimeout(r, delay));
814
+ } finally {
815
+ if (slotAcquired) {
816
+ this._releaseRequestSlot();
817
+ }
924
818
  }
925
819
  }
926
820
  }