autosnippet 3.0.1 → 3.0.2

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 (290) hide show
  1. package/README.md +230 -324
  2. package/bin/api-server.js +1 -1
  3. package/bin/cli.js +204 -244
  4. package/bin/mcp-server.js +5 -3
  5. package/config/knowledge-base.config.js +132 -132
  6. package/dashboard/dist/assets/{icons-CEfgGaZi.js → icons-Cdq22n2i.js} +95 -100
  7. package/dashboard/dist/assets/index-ClkyPkDX.js +133 -0
  8. package/dashboard/dist/assets/index-t4QrJwv1.css +1 -0
  9. package/dashboard/dist/index.html +3 -3
  10. package/lib/bootstrap.js +8 -8
  11. package/lib/cli/AiScanService.js +86 -40
  12. package/lib/cli/KnowledgeSyncService.js +113 -74
  13. package/lib/cli/SetupService.js +439 -277
  14. package/lib/cli/UpgradeService.js +63 -100
  15. package/lib/core/AstAnalyzer.js +276 -597
  16. package/lib/core/ast/ProjectGraph.js +101 -40
  17. package/lib/core/ast/ensure-grammars.js +232 -0
  18. package/lib/core/ast/index.js +115 -0
  19. package/lib/core/ast/lang-dart.js +661 -0
  20. package/lib/core/ast/lang-go.js +530 -0
  21. package/lib/core/ast/lang-java.js +435 -0
  22. package/lib/core/ast/lang-javascript.js +272 -0
  23. package/lib/core/ast/lang-kotlin.js +423 -0
  24. package/lib/core/ast/lang-objc.js +388 -0
  25. package/lib/core/ast/lang-python.js +371 -0
  26. package/lib/core/ast/lang-swift.js +337 -0
  27. package/lib/core/ast/lang-typescript.js +503 -0
  28. package/lib/core/capability/CapabilityProbe.js +18 -9
  29. package/lib/core/constitution/Constitution.js +2 -3
  30. package/lib/core/constitution/ConstitutionValidator.js +65 -24
  31. package/lib/core/discovery/DartDiscoverer.js +534 -0
  32. package/lib/core/discovery/DiscovererRegistry.js +83 -0
  33. package/lib/core/discovery/GenericDiscoverer.js +225 -0
  34. package/lib/core/discovery/GoDiscoverer.js +541 -0
  35. package/lib/core/discovery/JvmDiscoverer.js +506 -0
  36. package/lib/core/discovery/NodeDiscoverer.js +466 -0
  37. package/lib/core/discovery/ProjectDiscoverer.js +93 -0
  38. package/lib/core/discovery/PythonDiscoverer.js +338 -0
  39. package/lib/core/discovery/SpmDiscoverer.js +5 -0
  40. package/lib/core/discovery/index.js +53 -0
  41. package/lib/core/enhancement/EnhancementPack.js +71 -0
  42. package/lib/core/enhancement/EnhancementRegistry.js +47 -0
  43. package/lib/core/enhancement/android-enhancement.js +102 -0
  44. package/lib/core/enhancement/django-enhancement.js +70 -0
  45. package/lib/core/enhancement/fastapi-enhancement.js +63 -0
  46. package/lib/core/enhancement/go-grpc-enhancement.js +152 -0
  47. package/lib/core/enhancement/go-web-enhancement.js +201 -0
  48. package/lib/core/enhancement/index.js +65 -0
  49. package/lib/core/enhancement/node-server-enhancement.js +88 -0
  50. package/lib/core/enhancement/react-enhancement.js +86 -0
  51. package/lib/core/enhancement/spring-enhancement.js +112 -0
  52. package/lib/core/enhancement/vue-enhancement.js +96 -0
  53. package/lib/core/gateway/Gateway.js +8 -9
  54. package/lib/core/gateway/GatewayActionRegistry.js +1 -1
  55. package/lib/core/permission/PermissionManager.js +12 -8
  56. package/lib/domain/index.js +13 -9
  57. package/lib/domain/knowledge/KnowledgeEntry.js +111 -101
  58. package/lib/domain/knowledge/KnowledgeRepository.js +0 -1
  59. package/lib/domain/knowledge/Lifecycle.js +22 -22
  60. package/lib/domain/knowledge/index.js +9 -12
  61. package/lib/domain/knowledge/values/Constraints.js +31 -21
  62. package/lib/domain/knowledge/values/Content.js +21 -13
  63. package/lib/domain/knowledge/values/Quality.js +31 -18
  64. package/lib/domain/knowledge/values/Reasoning.js +20 -12
  65. package/lib/domain/knowledge/values/Relations.js +37 -25
  66. package/lib/domain/knowledge/values/Stats.js +18 -12
  67. package/lib/domain/knowledge/values/index.js +4 -3
  68. package/lib/domain/snippet/Snippet.js +35 -10
  69. package/lib/external/ai/AiFactory.js +48 -16
  70. package/lib/external/ai/AiProvider.js +184 -90
  71. package/lib/external/ai/providers/ClaudeProvider.js +25 -12
  72. package/lib/external/ai/providers/GoogleGeminiProvider.js +59 -30
  73. package/lib/external/ai/providers/MockProvider.js +9 -3
  74. package/lib/external/ai/providers/OpenAiProvider.js +51 -29
  75. package/lib/external/mcp/McpServer.js +66 -36
  76. package/lib/external/mcp/errorHandler.js +23 -11
  77. package/lib/external/mcp/handlers/LanguageExtensions.js +138 -53
  78. package/lib/external/mcp/handlers/TargetClassifier.js +52 -16
  79. package/lib/external/mcp/handlers/bootstrap/pipeline/BootstrapSnapshot.js +81 -20
  80. package/lib/external/mcp/handlers/bootstrap/pipeline/EpisodicMemory.js +71 -42
  81. package/lib/external/mcp/handlers/bootstrap/pipeline/IncrementalBootstrap.js +9 -17
  82. package/lib/external/mcp/handlers/bootstrap/pipeline/ToolResultCache.js +14 -9
  83. package/lib/external/mcp/handlers/bootstrap/pipeline/dimension-context.js +15 -7
  84. package/lib/external/mcp/handlers/bootstrap/pipeline/orchestrator.js +352 -153
  85. package/lib/external/mcp/handlers/bootstrap/pipeline/tier-scheduler.js +52 -12
  86. package/lib/external/mcp/handlers/bootstrap/skills.js +143 -39
  87. package/lib/external/mcp/handlers/bootstrap.js +691 -168
  88. package/lib/external/mcp/handlers/browse.js +66 -22
  89. package/lib/external/mcp/handlers/candidate.js +118 -35
  90. package/lib/external/mcp/handlers/consolidated.js +49 -17
  91. package/lib/external/mcp/handlers/guard.js +104 -39
  92. package/lib/external/mcp/handlers/knowledge.js +60 -36
  93. package/lib/external/mcp/handlers/search.js +43 -14
  94. package/lib/external/mcp/handlers/skill.js +120 -45
  95. package/lib/external/mcp/handlers/structure.js +240 -86
  96. package/lib/external/mcp/handlers/system.js +42 -12
  97. package/lib/external/mcp/handlers/wiki.js +58 -33
  98. package/lib/external/mcp/tools.js +306 -123
  99. package/lib/http/HttpServer.js +72 -47
  100. package/lib/http/middleware/RateLimiter.js +5 -3
  101. package/lib/http/middleware/errorHandler.js +6 -1
  102. package/lib/http/middleware/requestLogger.js +14 -3
  103. package/lib/http/middleware/roleResolver.js +30 -23
  104. package/lib/http/routes/ai.js +387 -265
  105. package/lib/http/routes/auth.js +81 -61
  106. package/lib/http/routes/candidates.js +430 -320
  107. package/lib/http/routes/commands.js +289 -189
  108. package/lib/http/routes/extract.js +158 -125
  109. package/lib/http/routes/guardRules.js +309 -217
  110. package/lib/http/routes/knowledge.js +213 -154
  111. package/lib/http/routes/modules.js +578 -0
  112. package/lib/http/routes/monitoring.js +6 -6
  113. package/lib/http/routes/recipes.js +104 -93
  114. package/lib/http/routes/search.js +361 -305
  115. package/lib/http/routes/skills.js +145 -98
  116. package/lib/http/routes/snippets.js +42 -30
  117. package/lib/http/routes/spm.js +3 -405
  118. package/lib/http/routes/violations.js +113 -93
  119. package/lib/http/routes/wiki.js +211 -170
  120. package/lib/http/utils/routeHelpers.js +3 -1
  121. package/lib/http/utils/sse-sessions.js +16 -6
  122. package/lib/http/utils/sse.js +15 -5
  123. package/lib/infrastructure/audit/AuditLogger.js +5 -2
  124. package/lib/infrastructure/audit/AuditStore.js +10 -7
  125. package/lib/infrastructure/cache/CacheService.js +3 -1
  126. package/lib/infrastructure/cache/GraphCache.js +8 -4
  127. package/lib/infrastructure/cache/UnifiedCacheAdapter.js +1 -1
  128. package/lib/infrastructure/config/ConfigLoader.js +9 -5
  129. package/lib/infrastructure/config/Defaults.js +30 -10
  130. package/lib/infrastructure/config/Paths.js +28 -8
  131. package/lib/infrastructure/config/TriggerSymbol.js +22 -10
  132. package/lib/infrastructure/database/DatabaseConnection.js +15 -10
  133. package/lib/infrastructure/database/migrations/001_initial_schema.js +0 -1
  134. package/lib/infrastructure/external/ClipboardManager.js +6 -2
  135. package/lib/infrastructure/external/NativeUi.js +50 -43
  136. package/lib/infrastructure/external/OpenBrowser.js +14 -17
  137. package/lib/infrastructure/external/XcodeAutomation.js +14 -258
  138. package/lib/infrastructure/logging/Logger.js +46 -30
  139. package/lib/infrastructure/monitoring/ErrorTracker.js +7 -5
  140. package/lib/infrastructure/monitoring/PerformanceMonitor.js +12 -4
  141. package/lib/infrastructure/paths/HeaderResolver.js +25 -9
  142. package/lib/infrastructure/paths/PathFinder.js +34 -12
  143. package/lib/infrastructure/plugin/PluginManager.js +26 -8
  144. package/lib/infrastructure/realtime/RealtimeService.js +2 -2
  145. package/lib/infrastructure/vector/Chunker.js +22 -7
  146. package/lib/infrastructure/vector/IndexingPipeline.js +46 -22
  147. package/lib/infrastructure/vector/JsonVectorAdapter.js +90 -53
  148. package/lib/infrastructure/vector/VectorStore.js +28 -10
  149. package/lib/injection/ServiceContainer.js +247 -93
  150. package/lib/platform/ios/index.js +63 -0
  151. package/lib/platform/ios/routes/spm.js +437 -0
  152. package/lib/platform/ios/snippet/PlaceholderConverter.js +55 -0
  153. package/lib/platform/ios/snippet/XcodeCodec.js +112 -0
  154. package/lib/{service → platform/ios}/spm/DependencyGraph.js +41 -17
  155. package/lib/{service → platform/ios}/spm/PackageSwiftParser.js +41 -14
  156. package/lib/{service → platform/ios}/spm/PolicyEngine.js +9 -4
  157. package/lib/platform/ios/spm/SpmDiscoverer.js +122 -0
  158. package/lib/{service → platform/ios}/spm/SpmService.js +385 -127
  159. package/lib/{service/automation → platform/ios/xcode}/SaveEventFilter.js +8 -7
  160. package/lib/platform/ios/xcode/XcodeAutomation.js +350 -0
  161. package/lib/{service/automation → platform/ios/xcode}/XcodeIntegration.js +325 -145
  162. package/lib/repository/base/BaseRepository.js +7 -9
  163. package/lib/repository/knowledge/KnowledgeRepository.impl.js +98 -75
  164. package/lib/repository/token/TokenUsageStore.js +4 -2
  165. package/lib/service/automation/ActionPipeline.js +1 -1
  166. package/lib/service/automation/AutomationOrchestrator.js +8 -4
  167. package/lib/service/automation/ContextCollector.js +7 -5
  168. package/lib/service/automation/DirectiveDetector.js +23 -16
  169. package/lib/service/automation/FileWatcher.js +112 -56
  170. package/lib/service/automation/TriggerResolver.js +6 -4
  171. package/lib/service/automation/handlers/AlinkHandler.js +24 -12
  172. package/lib/service/automation/handlers/CreateHandler.js +19 -20
  173. package/lib/service/automation/handlers/DraftHandler.js +14 -8
  174. package/lib/service/automation/handlers/GuardHandler.js +93 -63
  175. package/lib/service/automation/handlers/HeaderHandler.js +1 -6
  176. package/lib/service/automation/handlers/SearchHandler.js +155 -88
  177. package/lib/service/bootstrap/BootstrapTaskManager.js +77 -35
  178. package/lib/service/candidate/SimilarityService.js +25 -9
  179. package/lib/service/chat/AnalystAgent.js +50 -24
  180. package/lib/service/chat/CandidateGuardrail.js +143 -17
  181. package/lib/service/chat/ChatAgent.js +655 -260
  182. package/lib/service/chat/ContextWindow.js +116 -71
  183. package/lib/service/chat/ConversationStore.js +77 -36
  184. package/lib/service/chat/EpisodicConsolidator.js +47 -23
  185. package/lib/service/chat/HandoffProtocol.js +98 -22
  186. package/lib/service/chat/Memory.js +34 -14
  187. package/lib/service/chat/ProducerAgent.js +40 -20
  188. package/lib/service/chat/ProjectSemanticMemory.js +109 -78
  189. package/lib/service/chat/ReasoningLayer.js +148 -70
  190. package/lib/service/chat/ReasoningTrace.js +44 -32
  191. package/lib/service/chat/TaskPipeline.js +39 -19
  192. package/lib/service/chat/ToolRegistry.js +48 -29
  193. package/lib/service/chat/WorkingMemory.js +44 -18
  194. package/lib/service/chat/tools.js +1096 -494
  195. package/lib/service/context/RecipeExtractor.js +132 -51
  196. package/lib/service/cursor/CursorDeliveryPipeline.js +82 -37
  197. package/lib/service/cursor/KnowledgeCompressor.js +25 -22
  198. package/lib/service/cursor/RulesGenerator.js +13 -7
  199. package/lib/service/cursor/SkillsSyncer.js +77 -27
  200. package/lib/service/cursor/TokenBudget.js +2 -2
  201. package/lib/service/cursor/TopicClassifier.js +54 -20
  202. package/lib/service/guard/ComplianceReporter.js +55 -43
  203. package/lib/service/guard/ExclusionManager.js +67 -29
  204. package/lib/service/guard/GuardCheckEngine.js +381 -86
  205. package/lib/service/guard/GuardFeedbackLoop.js +22 -10
  206. package/lib/service/guard/GuardService.js +29 -19
  207. package/lib/service/guard/RuleLearner.js +55 -23
  208. package/lib/service/guard/SourceFileCollector.js +27 -20
  209. package/lib/service/guard/ViolationsStore.js +43 -38
  210. package/lib/service/knowledge/CodeEntityGraph.js +147 -82
  211. package/lib/service/knowledge/ConfidenceRouter.js +12 -10
  212. package/lib/service/knowledge/KnowledgeFileWriter.js +147 -56
  213. package/lib/service/knowledge/KnowledgeGraphService.js +81 -34
  214. package/lib/service/knowledge/KnowledgeService.js +222 -112
  215. package/lib/service/module/ModuleService.js +969 -0
  216. package/lib/service/quality/FeedbackCollector.js +27 -15
  217. package/lib/service/quality/QualityScorer.js +78 -24
  218. package/lib/service/recipe/RecipeCandidateValidator.js +110 -44
  219. package/lib/service/recipe/RecipeParser.js +78 -45
  220. package/lib/service/search/CoarseRanker.js +43 -28
  221. package/lib/service/search/CrossEncoderReranker.js +32 -21
  222. package/lib/service/search/InvertedIndex.js +21 -7
  223. package/lib/service/search/MultiSignalRanker.js +90 -28
  224. package/lib/service/search/RetrievalFunnel.js +45 -24
  225. package/lib/service/search/SearchEngine.js +255 -103
  226. package/lib/service/skills/EventAggregator.js +32 -15
  227. package/lib/service/skills/SignalCollector.js +140 -64
  228. package/lib/service/skills/SkillAdvisor.js +79 -42
  229. package/lib/service/skills/SkillHooks.js +16 -14
  230. package/lib/service/snippet/PlaceholderConverter.js +5 -0
  231. package/lib/service/snippet/SnippetFactory.js +116 -99
  232. package/lib/service/snippet/SnippetInstaller.js +234 -62
  233. package/lib/service/snippet/codecs/SnippetCodec.js +67 -0
  234. package/lib/service/snippet/codecs/VSCodeCodec.js +102 -0
  235. package/lib/service/snippet/codecs/XcodeCodec.js +5 -0
  236. package/lib/service/wiki/WikiGenerator.js +637 -263
  237. package/lib/shared/DimensionCopyRegistry.js +472 -0
  238. package/lib/shared/LanguageService.js +399 -0
  239. package/lib/shared/PathGuard.js +45 -28
  240. package/lib/shared/RecipeReadinessChecker.js +72 -12
  241. package/lib/shared/constants.js +41 -41
  242. package/lib/shared/errors/BaseError.js +2 -2
  243. package/lib/shared/errors/index.js +4 -4
  244. package/lib/shared/similarity.js +25 -8
  245. package/lib/shared/token-utils.js +6 -2
  246. package/lib/shared/utils/common.js +12 -4
  247. package/package.json +49 -13
  248. package/scripts/bench-real-projects.mjs +256 -0
  249. package/scripts/build-native-ui.js +30 -30
  250. package/scripts/clear-old-vector-index.js +5 -35
  251. package/scripts/clear-vector-cache.js +7 -37
  252. package/scripts/collect-test-project-stats.mjs +160 -0
  253. package/scripts/diagnose-mcp.js +41 -32
  254. package/scripts/ensure-parse-package.js +6 -9
  255. package/scripts/generate-recipe-drafts.js +116 -77
  256. package/scripts/init-db.js +3 -20
  257. package/scripts/init-snippets.js +305 -0
  258. package/scripts/init-vector-db.js +173 -170
  259. package/scripts/install-cursor-skill.js +148 -104
  260. package/scripts/install-full.js +8 -21
  261. package/scripts/install-vscode-copilot.js +146 -145
  262. package/scripts/migrate-md-to-knowledge.mjs +139 -151
  263. package/scripts/postinstall-safe.js +5 -17
  264. package/scripts/recipe-audit.js +106 -82
  265. package/scripts/release.js +283 -323
  266. package/scripts/setup-mcp-config.js +60 -52
  267. package/scripts/verify-context-api.js +20 -20
  268. package/skills/autosnippet-analysis/SKILL.md +10 -6
  269. package/skills/autosnippet-candidates/SKILL.md +27 -26
  270. package/skills/autosnippet-coldstart/SKILL.md +555 -38
  271. package/skills/autosnippet-concepts/SKILL.md +349 -337
  272. package/skills/autosnippet-create/SKILL.md +5 -5
  273. package/skills/autosnippet-reference-dart/SKILL.md +543 -0
  274. package/skills/autosnippet-reference-go/SKILL.md +539 -0
  275. package/skills/autosnippet-reference-java/SKILL.md +534 -0
  276. package/skills/autosnippet-reference-jsts/SKILL.md +41 -9
  277. package/skills/autosnippet-reference-kotlin/SKILL.md +526 -0
  278. package/skills/autosnippet-reference-objc/SKILL.md +29 -6
  279. package/skills/autosnippet-reference-python/SKILL.md +800 -0
  280. package/skills/autosnippet-reference-swift/SKILL.md +70 -14
  281. package/skills/autosnippet-structure/SKILL.md +4 -4
  282. package/templates/cursor-rules/autosnippet-conventions.mdc +2 -2
  283. package/templates/recipes-setup/README.md +2 -2
  284. package/templates/recipes-setup/_template.md +1 -1
  285. package/dashboard/dist/assets/index-Bun3ld_J.css +0 -1
  286. package/dashboard/dist/assets/index-_Sk_Dmg3.js +0 -143
  287. package/resources/asd-entry/main.swift +0 -159
  288. package/scripts/build-asd-entry.js +0 -51
  289. package/scripts/init-xcode-snippets.js +0 -311
  290. package/template.json +0 -39
@@ -3,6 +3,8 @@
3
3
  * 所有具体 Provider 必须实现这3个方法
4
4
  */
5
5
 
6
+ import { LanguageService } from '../../shared/LanguageService.js';
7
+
6
8
  export class AiProvider {
7
9
  constructor(config = {}) {
8
10
  this.model = config.model || '';
@@ -13,11 +15,11 @@ export class AiProvider {
13
15
  this.name = 'abstract';
14
16
 
15
17
  // ── CircuitBreaker 状态 ──
16
- this._circuitState = 'CLOSED'; // 'CLOSED' | 'OPEN' | 'HALF_OPEN'
17
- this._circuitFailures = 0; // 连续失败计数
18
- this._circuitThreshold = config.circuitThreshold || 5; // 触发熔断的连续失败次数
19
- this._circuitOpenedAt = 0; // 熔断打开时间
20
- this._circuitCooldownMs = 30_000; // 初始冷却 30 秒
18
+ this._circuitState = 'CLOSED'; // 'CLOSED' | 'OPEN' | 'HALF_OPEN'
19
+ this._circuitFailures = 0; // 连续失败计数
20
+ this._circuitThreshold = config.circuitThreshold || 5; // 触发熔断的连续失败次数
21
+ this._circuitOpenedAt = 0; // 熔断打开时间
22
+ this._circuitCooldownMs = 30_000; // 初始冷却 30 秒
21
23
  }
22
24
 
23
25
  /**
@@ -102,8 +104,11 @@ export class AiProvider {
102
104
  // 默认降级: 忽略 tools/toolChoice,走纯文本 chat()
103
105
  const messages = opts.messages || [];
104
106
  const history = messages
105
- .filter(m => m.role === 'user' || m.role === 'assistant')
106
- .map(m => ({ role: m.role === 'assistant' ? 'assistant' : 'user', content: m.content || '' }));
107
+ .filter((m) => m.role === 'user' || m.role === 'assistant')
108
+ .map((m) => ({
109
+ role: m.role === 'assistant' ? 'assistant' : 'user',
110
+ content: m.content || '',
111
+ }));
107
112
  const text = await this.chat(prompt, {
108
113
  history,
109
114
  systemPrompt: opts.systemPrompt,
@@ -137,7 +142,9 @@ export class AiProvider {
137
142
  maxTokens: opts.maxTokens ?? 32768,
138
143
  systemPrompt: opts.systemPrompt,
139
144
  });
140
- if (!response || response.trim().length === 0) return null;
145
+ if (!response || response.trim().length === 0) {
146
+ return null;
147
+ }
141
148
  const openChar = opts.openChar || '{';
142
149
  const closeChar = opts.closeChar || '}';
143
150
  return this.extractJSON(response, openChar, closeChar);
@@ -161,7 +168,10 @@ export class AiProvider {
161
168
  maxTokens: 32768,
162
169
  });
163
170
  if (!Array.isArray(parsed)) {
164
- this._log('warn', `[extractRecipes] structured output parse failed for target: ${targetName}`);
171
+ this._log(
172
+ 'warn',
173
+ `[extractRecipes] structured output parse failed for target: ${targetName}`
174
+ );
165
175
  return [];
166
176
  }
167
177
  if (parsed.length === 0) {
@@ -178,16 +188,17 @@ export class AiProvider {
178
188
  if (this.logger && typeof this.logger[level] === 'function') {
179
189
  this.logger[level](message);
180
190
  } else {
181
- console[level === 'warn' ? 'warn' : 'log'](message);
182
191
  }
183
- } catch { /* best effort */ }
192
+ } catch {
193
+ /* best effort */
194
+ }
184
195
  }
185
196
 
186
197
  /**
187
198
  * 构建 extractRecipes 标准提示词(语言自适应 + Skill 增强)
188
199
  */
189
200
  _buildExtractPrompt(targetName, filesContent, options = {}) {
190
- const files = filesContent.map(f => `--- FILE: ${f.name} ---\n${f.content}`).join('\n\n');
201
+ const files = filesContent.map((f) => `--- FILE: ${f.name} ---\n${f.content}`).join('\n\n');
191
202
 
192
203
  // 检测文件主要语言
193
204
  const langProfile = this._detectLanguageProfile(filesContent);
@@ -251,7 +262,7 @@ Each item MUST use the following V3 KnowledgeEntry structure:
251
262
  - dontClause (string): What NOT to do (e.g. "Don't instantiate services with new directly")
252
263
  - whenClause (string): When this pattern applies (e.g. "When creating a new ViewController subclass")
253
264
  - topicHint (string): Group label for related patterns (e.g. "Networking", "UI-Layout", "Error-Handling")
254
- - coreCode (string): Minimal 3-10 line code skeleton that captures the essence
265
+ - 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 (
255
266
  - 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>"] }
256
267
  - aiInsight (string, optional): One-sentence concise insight — the single most important takeaway about this code pattern
257
268
 
@@ -312,7 +323,7 @@ Each item MUST use the following V3 KnowledgeEntry structure:
312
323
  - dontClause (string): What NOT to do (e.g. "Don't instantiate services with new directly")
313
324
  - whenClause (string): When this pattern applies (e.g. "When creating a new ViewController subclass")
314
325
  - topicHint (string): Group label (e.g. "Networking", "UI-Layout")
315
- - coreCode (string): Minimal 3-10 line code skeleton that captures the essence
326
+ - 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 (
316
327
  - 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>"] }
317
328
  - aiInsight (string, optional): One-sentence concise insight — the single most important takeaway about this code pattern
318
329
 
@@ -334,14 +345,17 @@ ${files}`;
334
345
  extCounts[ext] = (extCounts[ext] || 0) + 1;
335
346
  }
336
347
 
348
+ // 使用 LanguageService 推断主语言
349
+ const primaryLang = LanguageService.detectPrimary(extCounts);
337
350
  const dominant = Object.entries(extCounts).sort((a, b) => b[1] - a[1])[0]?.[0] || '';
338
351
 
339
352
  // iOS/macOS (Swift / Objective-C)
340
- if (['swift', 'm', 'mm', 'h'].includes(dominant)) {
353
+ if (primaryLang === 'swift' || primaryLang === 'objectivec') {
341
354
  return {
342
- primaryLanguage: dominant === 'swift' ? 'swift' : 'objectivec',
355
+ primaryLanguage: primaryLang,
343
356
  role: 'Senior iOS/macOS Architect',
344
- patternExamples: 'how to set up a ViewController, configure a TableView with delegate/datasource, build a login UI, handle network responses',
357
+ patternExamples:
358
+ 'how to set up a ViewController, configure a TableView with delegate/datasource, build a login UI, handle network responses',
345
359
  extractionExamples: `Examples of good extractions:
346
360
  - Complete \`init\` method with all tabBarItem/navigationItem configuration
347
361
  - Complete \`viewDidLoad\` with all setup calls
@@ -353,11 +367,12 @@ ${files}`;
353
367
  }
354
368
 
355
369
  // JavaScript / TypeScript
356
- if (['js', 'mjs', 'cjs', 'ts', 'tsx', 'jsx'].includes(dominant)) {
370
+ if (primaryLang === 'javascript' || primaryLang === 'typescript') {
357
371
  return {
358
- primaryLanguage: ['ts', 'tsx'].includes(dominant) ? 'typescript' : 'javascript',
372
+ primaryLanguage: primaryLang,
359
373
  role: 'Senior Software Engineer',
360
- patternExamples: 'Express/Koa middleware, React component patterns, service class with dependency injection, data processing pipeline, error handling wrapper, factory/strategy patterns',
374
+ patternExamples:
375
+ 'Express/Koa middleware, React component patterns, service class with dependency injection, data processing pipeline, error handling wrapper, factory/strategy patterns',
361
376
  extractionExamples: `Examples of good extractions:
362
377
  - Complete class with constructor and key methods
363
378
  - Express route handler with validation and error handling
@@ -369,11 +384,12 @@ ${files}`;
369
384
  }
370
385
 
371
386
  // Python
372
- if (['py'].includes(dominant)) {
387
+ if (primaryLang === 'python') {
373
388
  return {
374
389
  primaryLanguage: 'python',
375
390
  role: 'Senior Python Engineer',
376
- patternExamples: 'Django/Flask views, data processing with pandas, async handlers, decorator patterns, class-based services',
391
+ patternExamples:
392
+ 'Django/Flask views, data processing with pandas, async handlers, decorator patterns, class-based services',
377
393
  extractionExamples: `Examples of good extractions:
378
394
  - Complete class with __init__ and key methods
379
395
  - Decorator factory function
@@ -385,11 +401,12 @@ ${files}`;
385
401
  }
386
402
 
387
403
  // Go
388
- if (['go'].includes(dominant)) {
404
+ if (primaryLang === 'go') {
389
405
  return {
390
406
  primaryLanguage: 'go',
391
407
  role: 'Senior Go Engineer',
392
- patternExamples: 'HTTP handler with middleware, goroutine patterns, interface implementations, struct methods with error handling',
408
+ patternExamples:
409
+ 'HTTP handler with middleware, goroutine patterns, interface implementations, struct methods with error handling',
393
410
  extractionExamples: `Examples of good extractions:
394
411
  - Complete struct with constructor and methods
395
412
  - HTTP handler function with error propagation
@@ -400,11 +417,12 @@ ${files}`;
400
417
  }
401
418
 
402
419
  // Kotlin / Java
403
- if (['kt', 'java'].includes(dominant)) {
420
+ if (primaryLang === 'kotlin' || primaryLang === 'java') {
404
421
  return {
405
- primaryLanguage: dominant === 'kt' ? 'kotlin' : 'java',
422
+ primaryLanguage: primaryLang,
406
423
  role: 'Senior Android/Backend Engineer',
407
- patternExamples: 'Activity/Fragment lifecycle, repository pattern, ViewModel with LiveData, Retrofit service, dependency injection setup',
424
+ patternExamples:
425
+ 'Activity/Fragment lifecycle, repository pattern, ViewModel with LiveData, Retrofit service, dependency injection setup',
408
426
  extractionExamples: `Examples of good extractions:
409
427
  - Complete class with constructor and key methods
410
428
  - Repository with CRUD operations
@@ -416,11 +434,12 @@ ${files}`;
416
434
  }
417
435
 
418
436
  // Rust
419
- if (['rs'].includes(dominant)) {
437
+ if (primaryLang === 'rust') {
420
438
  return {
421
439
  primaryLanguage: 'rust',
422
440
  role: 'Senior Rust Engineer',
423
- patternExamples: 'trait implementations, error handling with Result, async functions, builder patterns, iterator chains',
441
+ patternExamples:
442
+ 'trait implementations, error handling with Result, async functions, builder patterns, iterator chains',
424
443
  extractionExamples: `Examples of good extractions:
425
444
  - Complete impl block with key methods
426
445
  - Trait implementation with all required methods
@@ -432,11 +451,12 @@ ${files}`;
432
451
  }
433
452
 
434
453
  // Vue
435
- if (['vue'].includes(dominant)) {
454
+ if (dominant === 'vue') {
436
455
  return {
437
456
  primaryLanguage: 'vue',
438
457
  role: 'Senior Frontend Engineer',
439
- patternExamples: 'Vue component with composition API, composable functions, Vuex/Pinia store modules, router guards',
458
+ patternExamples:
459
+ 'Vue component with composition API, composable functions, Vuex/Pinia store modules, router guards',
440
460
  extractionExamples: `Examples of good extractions:
441
461
  - Complete Vue component with setup/template
442
462
  - Composable function with reactive state
@@ -447,11 +467,12 @@ ${files}`;
447
467
  }
448
468
 
449
469
  // Ruby
450
- if (['rb'].includes(dominant)) {
470
+ if (primaryLang === 'ruby') {
451
471
  return {
452
472
  primaryLanguage: 'ruby',
453
473
  role: 'Senior Ruby Engineer',
454
- patternExamples: 'Rails controller actions, model concerns, service objects, background jobs, API serializers',
474
+ patternExamples:
475
+ 'Rails controller actions, model concerns, service objects, background jobs, API serializers',
455
476
  extractionExamples: `Examples of good extractions:
456
477
  - Complete controller with CRUD actions
457
478
  - Service object with call method
@@ -465,7 +486,8 @@ ${files}`;
465
486
  return {
466
487
  primaryLanguage: dominant || 'unknown',
467
488
  role: 'Senior Software Engineer',
468
- patternExamples: 'design patterns, service abstractions, data flow handling, error management, configuration setup',
489
+ patternExamples:
490
+ 'design patterns, service abstractions, data flow handling, error management, configuration setup',
469
491
  extractionExamples: `Examples of good extractions:
470
492
  - Complete class/function with full implementation
471
493
  - Service method with error handling and retries
@@ -494,17 +516,33 @@ ${files}`;
494
516
  * 构建 enrichCandidates 提示词
495
517
  */
496
518
  _buildEnrichPrompt(candidates) {
497
- const items = candidates.map((c, i) => {
498
- const existing = [];
499
- if (c.rationale) existing.push(`rationale: ${c.rationale}`);
500
- if (c.knowledgeType) existing.push(`knowledgeType: ${c.knowledgeType}`);
501
- if (c.complexity) existing.push(`complexity: ${c.complexity}`);
502
- if (c.scope) existing.push(`scope: ${c.scope}`);
503
- if (c.steps?.length) existing.push(`steps: [${c.steps.length} steps already]`);
504
- if (c.constraints?.preconditions?.length) existing.push(`preconditions: [${c.constraints.preconditions.length} items]`);
505
- const existingStr = existing.length > 0 ? `\nAlready filled: ${existing.join(', ')}` : '\nNo semantic fields filled yet.';
506
-
507
- return `--- CANDIDATE #${i + 1} ---
519
+ const items = candidates
520
+ .map((c, i) => {
521
+ const existing = [];
522
+ if (c.rationale) {
523
+ existing.push(`rationale: ${c.rationale}`);
524
+ }
525
+ if (c.knowledgeType) {
526
+ existing.push(`knowledgeType: ${c.knowledgeType}`);
527
+ }
528
+ if (c.complexity) {
529
+ existing.push(`complexity: ${c.complexity}`);
530
+ }
531
+ if (c.scope) {
532
+ existing.push(`scope: ${c.scope}`);
533
+ }
534
+ if (c.steps?.length) {
535
+ existing.push(`steps: [${c.steps.length} steps already]`);
536
+ }
537
+ if (c.constraints?.preconditions?.length) {
538
+ existing.push(`preconditions: [${c.constraints.preconditions.length} items]`);
539
+ }
540
+ const existingStr =
541
+ existing.length > 0
542
+ ? `\nAlready filled: ${existing.join(', ')}`
543
+ : '\nNo semantic fields filled yet.';
544
+
545
+ return `--- CANDIDATE #${i + 1} ---
508
546
  Title: ${c.title || '(untitled)'}
509
547
  Language: ${c.language || 'unknown'}
510
548
  Category: ${c.category || ''}
@@ -512,7 +550,8 @@ Description: ${c.description || c.summary || ''}
512
550
  ${existingStr}
513
551
  Code:
514
552
  ${(c.code || '').substring(0, 2000)}`;
515
- }).join('\n\n');
553
+ })
554
+ .join('\n\n');
516
555
 
517
556
  return `# Role
518
557
  You are a Senior Software Architect performing deep semantic analysis on code candidates.
@@ -561,21 +600,28 @@ ${items}`;
561
600
  _resolveProxyUrl() {
562
601
  // Provider-specific vars: ASD_GOOGLE_PROXY_HTTPS, ASD_OPENAI_PROXY_HTTPS, etc.
563
602
  const tag = (this.name || '')
564
- .replace(/-gemini$/, '') // google-gemini → google
565
- .replace(/-/g, '_') // 其他连字符 → 下划线
566
- .toUpperCase(); // google → GOOGLE
603
+ .replace(/-gemini$/, '') // google-gemini → google
604
+ .replace(/-/g, '_') // 其他连字符 → 下划线
605
+ .toUpperCase(); // google → GOOGLE
567
606
 
568
607
  if (tag) {
569
- const specific = process.env[`ASD_${tag}_PROXY_HTTPS`]
570
- || process.env[`ASD_${tag}_PROXY_HTTP`];
571
- if (specific) return specific;
608
+ const specific =
609
+ process.env[`ASD_${tag}_PROXY_HTTPS`] || process.env[`ASD_${tag}_PROXY_HTTP`];
610
+ if (specific) {
611
+ return specific;
612
+ }
572
613
  }
573
614
 
574
- return process.env.ASD_AI_PROXY
575
- || process.env.HTTPS_PROXY || process.env.https_proxy
576
- || process.env.HTTP_PROXY || process.env.http_proxy
577
- || process.env.ALL_PROXY || process.env.all_proxy
578
- || '';
615
+ return (
616
+ process.env.ASD_AI_PROXY ||
617
+ process.env.HTTPS_PROXY ||
618
+ process.env.https_proxy ||
619
+ process.env.HTTP_PROXY ||
620
+ process.env.http_proxy ||
621
+ process.env.ALL_PROXY ||
622
+ process.env.all_proxy ||
623
+ ''
624
+ );
579
625
  }
580
626
 
581
627
  /**
@@ -604,11 +650,15 @@ ${items}`;
604
650
  * 支持截断修复:当 AI 输出被 token 限制截断时,尝试关闭未完成的 JSON 结构
605
651
  */
606
652
  extractJSON(text, openChar = '{', closeChar = '}') {
607
- if (!text) return null;
653
+ if (!text) {
654
+ return null;
655
+ }
608
656
  // 去除 markdown 代码块
609
657
  const cleaned = text.replace(/```(?:json)?\s*/gi, '').replace(/```/g, '');
610
658
  const start = cleaned.indexOf(openChar);
611
- if (start === -1) return null;
659
+ if (start === -1) {
660
+ return null;
661
+ }
612
662
  const end = cleaned.lastIndexOf(closeChar);
613
663
 
614
664
  // 1. 常规路径:找到完整的 JSON 边界
@@ -637,11 +687,15 @@ ${items}`;
637
687
  _repairTruncatedArray(text) {
638
688
  // ── 策略 1:字符级深度追踪 ──
639
689
  const charResult = this._repairByCharTracking(text);
640
- if (charResult) return charResult;
690
+ if (charResult) {
691
+ return charResult;
692
+ }
641
693
 
642
694
  // ── 策略 2:正则回退 — 找所有 "}," 或 "}\n" 位置,从后向前逐一尝试 JSON.parse ──
643
695
  const regexResult = this._repairByRegexFallback(text);
644
- if (regexResult) return regexResult;
696
+ if (regexResult) {
697
+ return regexResult;
698
+ }
645
699
 
646
700
  return null;
647
701
  }
@@ -652,18 +706,30 @@ ${items}`;
652
706
  _repairByCharTracking(text) {
653
707
  let depth = 0;
654
708
  let inString = false;
655
- let escape = false;
709
+ let isEscaped = false;
656
710
  let lastCompleteObjEnd = -1;
657
711
 
658
712
  for (let i = 0; i < text.length; i++) {
659
713
  const ch = text[i];
660
- if (escape) { escape = false; continue; }
661
- if (ch === '\\' && inString) { escape = true; continue; }
662
- if (ch === '"') { inString = !inString; continue; }
663
- if (inString) continue;
714
+ if (isEscaped) {
715
+ isEscaped = false;
716
+ continue;
717
+ }
718
+ if (ch === '\\' && inString) {
719
+ isEscaped = true;
720
+ continue;
721
+ }
722
+ if (ch === '"') {
723
+ inString = !inString;
724
+ continue;
725
+ }
726
+ if (inString) {
727
+ continue;
728
+ }
664
729
 
665
- if (ch === '{' || ch === '[') depth++;
666
- else if (ch === '}' || ch === ']') {
730
+ if (ch === '{' || ch === '[') {
731
+ depth++;
732
+ } else if (ch === '}' || ch === ']') {
667
733
  depth--;
668
734
  // depth === 1 表示回到数组顶层,刚关闭了一个完整对象
669
735
  if (depth === 1 && ch === '}') {
@@ -672,7 +738,9 @@ ${items}`;
672
738
  }
673
739
  }
674
740
 
675
- if (lastCompleteObjEnd === -1) return null;
741
+ if (lastCompleteObjEnd === -1) {
742
+ return null;
743
+ }
676
744
  return this._tryRepairAt(text, lastCompleteObjEnd);
677
745
  }
678
746
 
@@ -683,7 +751,7 @@ ${items}`;
683
751
  _repairByRegexFallback(text) {
684
752
  // 收集所有 "}" 后跟 "," 或空白的位置(可能是对象边界)
685
753
  const candidates = [];
686
- const re = /\}[\s,]*(?=\s*[\[{]|$)/g;
754
+ const re = /\}[\s,]*(?=\s*[[{]|$)/g;
687
755
  let m;
688
756
  while ((m = re.exec(text)) !== null) {
689
757
  candidates.push(m.index); // "}" 的位置
@@ -692,7 +760,9 @@ ${items}`;
692
760
  // 从后往前尝试
693
761
  for (let i = candidates.length - 1; i >= 0; i--) {
694
762
  const result = this._tryRepairAt(text, candidates[i]);
695
- if (result) return result;
763
+ if (result) {
764
+ return result;
765
+ }
696
766
  }
697
767
  return null;
698
768
  }
@@ -711,10 +781,15 @@ ${items}`;
711
781
  try {
712
782
  const result = JSON.parse(repaired);
713
783
  if (Array.isArray(result) && result.length > 0) {
714
- this._log('warn', `[extractJSON] Repaired truncated JSON array: recovered ${result.length} items from truncated response`);
784
+ this._log(
785
+ 'warn',
786
+ `[extractJSON] Repaired truncated JSON array: recovered ${result.length} items from truncated response`
787
+ );
715
788
  return result;
716
789
  }
717
- } catch { /* this position didn't work, try next */ }
790
+ } catch {
791
+ /* this position didn't work, try next */
792
+ }
718
793
  return null;
719
794
  }
720
795
 
@@ -733,7 +808,9 @@ ${items}`;
733
808
  if (this._circuitState === 'OPEN') {
734
809
  const elapsed = Date.now() - (this._circuitOpenedAt || 0);
735
810
  if (elapsed < (this._circuitCooldownMs || 30000)) {
736
- const err = new Error(`AI 服务熔断中 (连续 ${this._circuitFailures} 次失败),${Math.ceil(((this._circuitCooldownMs || 30000) - elapsed) / 1000)}s 后恢复`);
811
+ const err = new Error(
812
+ `AI 服务熔断中 (连续 ${this._circuitFailures} 次失败),${Math.ceil(((this._circuitCooldownMs || 30000) - elapsed) / 1000)}s 后恢复`
813
+ );
737
814
  err.code = 'CIRCUIT_OPEN';
738
815
  throw err;
739
816
  }
@@ -753,27 +830,38 @@ ${items}`;
753
830
  // ── 综合判断是否为可重试的网络/服务端错误 ──
754
831
  const causeCode = err.cause?.code || '';
755
832
  // 网络级错误:无 HTTP status,底层连接失败
756
- const isNetworkError = !err.status && (
757
- err.message === 'fetch failed'
758
- || err.code === 'ECONNRESET' || causeCode === 'ECONNRESET'
759
- || err.code === 'ECONNREFUSED' || causeCode === 'ECONNREFUSED'
760
- || err.code === 'ENOTFOUND' || causeCode === 'ENOTFOUND'
761
- || err.code === 'ECONNABORTED' || causeCode === 'ECONNABORTED'
762
- || err.code === 'ETIMEDOUT' || causeCode === 'ETIMEDOUT'
763
- || err.code === 'UND_ERR_CONNECT_TIMEOUT' || causeCode === 'UND_ERR_CONNECT_TIMEOUT'
764
- || err.code === 'UND_ERR_SOCKET' || causeCode === 'UND_ERR_SOCKET'
765
- );
833
+ const isNetworkError =
834
+ !err.status &&
835
+ (err.message === 'fetch failed' ||
836
+ err.code === 'ECONNRESET' ||
837
+ causeCode === 'ECONNRESET' ||
838
+ err.code === 'ECONNREFUSED' ||
839
+ causeCode === 'ECONNREFUSED' ||
840
+ err.code === 'ENOTFOUND' ||
841
+ causeCode === 'ENOTFOUND' ||
842
+ err.code === 'ECONNABORTED' ||
843
+ causeCode === 'ECONNABORTED' ||
844
+ err.code === 'ETIMEDOUT' ||
845
+ causeCode === 'ETIMEDOUT' ||
846
+ err.code === 'UND_ERR_CONNECT_TIMEOUT' ||
847
+ causeCode === 'UND_ERR_CONNECT_TIMEOUT' ||
848
+ err.code === 'UND_ERR_SOCKET' ||
849
+ causeCode === 'UND_ERR_SOCKET');
766
850
  const isRetryable = err.status === 429 || err.status >= 500 || isNetworkError;
767
851
 
768
852
  // 首次失败记录详细诊断(含 cause)
769
853
  if (attempt === 0 && (isNetworkError || err.cause)) {
770
- this._log?.('warn', `[_withRetry] ${err.message} — cause: ${err.cause?.message || causeCode || 'unknown'}`);
854
+ this._log?.(
855
+ 'warn',
856
+ `[_withRetry] ${err.message} — cause: ${err.cause?.message || causeCode || 'unknown'}`
857
+ );
771
858
  }
772
859
 
773
860
  if (attempt >= retries || !isRetryable) {
774
861
  // 只有服务端错误 / 网络错误才累计熔断计数
775
862
  // 客户端错误 (4xx 非 429) 不应触发熔断 — 那是请求本身的问题
776
- const isServerError = isNetworkError || err.status === 429 || err.status >= 500 || !err.status;
863
+ const isServerError =
864
+ isNetworkError || err.status === 429 || err.status >= 500 || !err.status;
777
865
  if (isServerError) {
778
866
  this._circuitFailures = (this._circuitFailures || 0) + 1;
779
867
  if (this._circuitFailures >= (this._circuitThreshold || 5)) {
@@ -781,15 +869,21 @@ ${items}`;
781
869
  this._circuitOpenedAt = Date.now();
782
870
  // 先用当前冷却值,再递增给下次: 30s → 60s → 120s(最大 5 分钟)
783
871
  const cooldown = this._circuitCooldownMs || 30_000;
784
- this._log?.('warn', `[CircuitBreaker] OPEN — ${this._circuitFailures} consecutive failures, cooldown ${cooldown / 1000}s`);
872
+ this._log?.(
873
+ 'warn',
874
+ `[CircuitBreaker] OPEN — ${this._circuitFailures} consecutive failures, cooldown ${cooldown / 1000}s`
875
+ );
785
876
  this._circuitCooldownMs = Math.min(cooldown * 2, 300_000);
786
877
  }
787
878
  }
788
879
  throw err;
789
880
  }
790
- const delay = baseDelay * Math.pow(2, attempt) + Math.random() * 1000;
791
- this._log?.('info', `[_withRetry] attempt ${attempt + 1} failed (${err.message}), retrying in ${Math.round(delay / 1000)}s…`);
792
- await new Promise(r => setTimeout(r, delay));
881
+ const delay = baseDelay * 2 ** attempt + Math.random() * 1000;
882
+ this._log?.(
883
+ 'info',
884
+ `[_withRetry] attempt ${attempt + 1} failed (${err.message}), retrying in ${Math.round(delay / 1000)}s…`
885
+ );
886
+ await new Promise((r) => setTimeout(r, delay));
793
887
  }
794
888
  }
795
889
  }
@@ -7,8 +7,8 @@
7
7
  * - tool_result content blocks 用于回传工具执行结果
8
8
  */
9
9
 
10
- import { AiProvider } from '../AiProvider.js';
11
10
  import Logger from '../../../infrastructure/logging/Logger.js';
11
+ import { AiProvider } from '../AiProvider.js';
12
12
 
13
13
  const CLAUDE_BASE = 'https://api.anthropic.com/v1';
14
14
  const ANTHROPIC_VERSION = '2023-06-01';
@@ -47,7 +47,7 @@ export class ClaudeProvider extends AiProvider {
47
47
  };
48
48
 
49
49
  const data = await this._post(`${CLAUDE_BASE}/messages`, body);
50
- const textBlock = (data?.content || []).find(c => c.type === 'text');
50
+ const textBlock = (data?.content || []).find((c) => c.type === 'text');
51
51
  return textBlock?.text || '';
52
52
  }
53
53
 
@@ -77,9 +77,8 @@ export class ClaudeProvider extends AiProvider {
77
77
  } = opts;
78
78
 
79
79
  // 统一消息 → Anthropic Messages 格式
80
- const srcMessages = unifiedMessages?.length > 0
81
- ? unifiedMessages
82
- : [{ role: 'user', content: prompt }];
80
+ const srcMessages =
81
+ unifiedMessages?.length > 0 ? unifiedMessages : [{ role: 'user', content: prompt }];
83
82
 
84
83
  const messages = this.#convertMessages(srcMessages);
85
84
 
@@ -98,7 +97,7 @@ export class ClaudeProvider extends AiProvider {
98
97
  // 工具声明 + tool_choice
99
98
  // toolChoice='none' 时不传 tools(Anthropic 没有 'none' tool_choice)
100
99
  if (toolChoice !== 'none' && toolSchemas?.length > 0) {
101
- body.tools = toolSchemas.map(s => ({
100
+ body.tools = toolSchemas.map((s) => ({
102
101
  name: s.name,
103
102
  description: s.description || '',
104
103
  input_schema: s.parameters || { type: 'object', properties: {} },
@@ -159,7 +158,9 @@ export class ClaudeProvider extends AiProvider {
159
158
  i++;
160
159
  } else if (msg.role === 'assistant') {
161
160
  const content = [];
162
- if (msg.content) content.push({ type: 'text', text: msg.content });
161
+ if (msg.content) {
162
+ content.push({ type: 'text', text: msg.content });
163
+ }
163
164
  if (msg.toolCalls?.length > 0) {
164
165
  for (const tc of msg.toolCalls) {
165
166
  content.push({
@@ -232,7 +233,9 @@ export class ClaudeProvider extends AiProvider {
232
233
  }
233
234
 
234
235
  if (functionCalls.length > 0) {
235
- this.logger.debug(`[Claude] native function calls: ${functionCalls.map(fc => fc.name).join(', ')}`);
236
+ this.logger.debug(
237
+ `[Claude] native function calls: ${functionCalls.map((fc) => fc.name).join(', ')}`
238
+ );
236
239
  return {
237
240
  text: textParts.length > 0 ? textParts.join('\n') : null,
238
241
  functionCalls,
@@ -249,10 +252,12 @@ export class ClaudeProvider extends AiProvider {
249
252
 
250
253
  async summarize(code) {
251
254
  const prompt = `请对以下代码生成结构化摘要,返回 JSON 格式 {title, description, language, patterns: [], keyAPIs: []}:\n\n${code}`;
252
- return await this.chatWithStructuredOutput(prompt, {
253
- temperature: 0.3,
254
- maxTokens: 4096,
255
- }) || { title: '', description: '' };
255
+ return (
256
+ (await this.chatWithStructuredOutput(prompt, {
257
+ temperature: 0.3,
258
+ maxTokens: 4096,
259
+ })) || { title: '', description: '' }
260
+ );
256
261
  }
257
262
 
258
263
  async embed(_text) {
@@ -265,6 +270,14 @@ export class ClaudeProvider extends AiProvider {
265
270
  }
266
271
 
267
272
  async _post(url, body) {
273
+ if (!this.apiKey) {
274
+ const err = new Error(
275
+ 'Claude API Key 未配置。请在 .env 中设置 ASD_CLAUDE_API_KEY,或运行 asd setup 完成配置。'
276
+ );
277
+ err.code = 'API_KEY_MISSING';
278
+ throw err;
279
+ }
280
+
268
281
  const controller = new AbortController();
269
282
  const timer = setTimeout(() => controller.abort(), this.timeout);
270
283